From 7390899a38f0eeaa038c59b5b08a4833bfe982fa Mon Sep 17 00:00:00 2001 From: Linar Yusupov Date: Wed, 6 Nov 2024 11:43:09 +0300 Subject: [PATCH] add RadioLib 7.1.0 source code [skip ci] --- README.md | 23 +- .../source/libraries/RadioLib/.gitattributes | 3 + .../source/libraries/RadioLib/.gitignore | 28 + .../source/libraries/RadioLib/CMakeLists.txt | 51 + .../libraries/RadioLib/CODE_OF_CONDUCT.md | 3 + .../source/libraries/RadioLib/CONTRIBUTING.md | 110 + .../source/libraries/RadioLib/README.md | 92 + .../source/libraries/RadioLib/SECURITY.md | 5 + .../libraries/RadioLib/idf_component.yml | 43 + .../source/libraries/RadioLib/keywords.txt | 517 +++ .../source/libraries/RadioLib/library.json | 26 + .../libraries/RadioLib/library.properties | 10 + .../source/libraries/RadioLib/license.txt | 21 + .../source/libraries/RadioLib/src/BuildOpt.h | 587 +++ .../libraries/RadioLib/src/BuildOptUser.h | 15 + .../source/libraries/RadioLib/src/Hal.cpp | 35 + .../source/libraries/RadioLib/src/Hal.h | 212 + .../source/libraries/RadioLib/src/Module.cpp | 524 +++ .../source/libraries/RadioLib/src/Module.h | 543 +++ .../source/libraries/RadioLib/src/RadioLib.h | 117 + .../source/libraries/RadioLib/src/TypeDef.h | 654 +++ .../RadioLib/src/hal/Arduino/ArduinoHal.cpp | 209 + .../RadioLib/src/hal/Arduino/ArduinoHal.h | 80 + .../RadioLib/src/hal/ESP-IDF/EspHal.h | 322 ++ .../libraries/RadioLib/src/hal/RPi/PiHal.h | 261 ++ .../RadioLib/src/hal/RPiPico/PicoHal.h | 198 + .../RadioLib/src/hal/Tock/libtockHal.h | 225 + .../RadioLib/src/modules/CC1101/CC1101.cpp | 1174 +++++ .../RadioLib/src/modules/CC1101/CC1101.h | 1025 +++++ .../RadioLib/src/modules/LLCC68/LLCC68.cpp | 135 + .../RadioLib/src/modules/LLCC68/LLCC68.h | 89 + .../RadioLib/src/modules/LR11x0/LR1110.cpp | 129 + .../RadioLib/src/modules/LR11x0/LR1110.h | 145 + .../RadioLib/src/modules/LR11x0/LR1120.cpp | 149 + .../RadioLib/src/modules/LR11x0/LR1120.h | 158 + .../RadioLib/src/modules/LR11x0/LR1121.cpp | 8 + .../RadioLib/src/modules/LR11x0/LR1121.h | 32 + .../RadioLib/src/modules/LR11x0/LR11x0.cpp | 3946 +++++++++++++++++ .../RadioLib/src/modules/LR11x0/LR11x0.h | 1849 ++++++++ .../src/modules/LR11x0/LR11x0_firmware.h | 38 + .../RadioLib/src/modules/RF69/RF69.cpp | 1080 +++++ .../RadioLib/src/modules/RF69/RF69.h | 1038 +++++ .../RadioLib/src/modules/RFM2x/RFM22.h | 20 + .../RadioLib/src/modules/RFM2x/RFM23.h | 20 + .../RadioLib/src/modules/SX123x/SX1231.cpp | 92 + .../RadioLib/src/modules/SX123x/SX1231.h | 121 + .../RadioLib/src/modules/SX123x/SX1233.cpp | 127 + .../RadioLib/src/modules/SX123x/SX1233.h | 61 + .../RadioLib/src/modules/SX126x/STM32WLx.cpp | 154 + .../RadioLib/src/modules/SX126x/STM32WLx.h | 168 + .../src/modules/SX126x/STM32WLx_Module.cpp | 125 + .../src/modules/SX126x/STM32WLx_Module.h | 38 + .../RadioLib/src/modules/SX126x/SX1261.cpp | 38 + .../RadioLib/src/modules/SX126x/SX1261.h | 53 + .../RadioLib/src/modules/SX126x/SX1262.cpp | 133 + .../RadioLib/src/modules/SX126x/SX1262.h | 133 + .../RadioLib/src/modules/SX126x/SX1268.cpp | 134 + .../RadioLib/src/modules/SX126x/SX1268.h | 131 + .../RadioLib/src/modules/SX126x/SX126x.cpp | 2419 ++++++++++ .../RadioLib/src/modules/SX126x/SX126x.h | 1337 ++++++ .../src/modules/SX126x/SX126x_LR_FHSS.cpp | 393 ++ .../SX126x/patches/SX126x_patch_scan.h | 110 + .../RadioLib/src/modules/SX127x/SX1272.cpp | 601 +++ .../RadioLib/src/modules/SX127x/SX1272.h | 334 ++ .../RadioLib/src/modules/SX127x/SX1273.cpp | 131 + .../RadioLib/src/modules/SX127x/SX1273.h | 82 + .../RadioLib/src/modules/SX127x/SX1276.cpp | 95 + .../RadioLib/src/modules/SX127x/SX1276.h | 94 + .../RadioLib/src/modules/SX127x/SX1277.cpp | 173 + .../RadioLib/src/modules/SX127x/SX1277.h | 109 + .../RadioLib/src/modules/SX127x/SX1278.cpp | 721 +++ .../RadioLib/src/modules/SX127x/SX1278.h | 352 ++ .../RadioLib/src/modules/SX127x/SX1279.cpp | 95 + .../RadioLib/src/modules/SX127x/SX1279.h | 82 + .../RadioLib/src/modules/SX127x/SX127x.cpp | 1881 ++++++++ .../RadioLib/src/modules/SX127x/SX127x.h | 1282 ++++++ .../RadioLib/src/modules/SX128x/SX1280.cpp | 184 + .../RadioLib/src/modules/SX128x/SX1280.h | 56 + .../RadioLib/src/modules/SX128x/SX1281.cpp | 8 + .../RadioLib/src/modules/SX128x/SX1281.h | 31 + .../RadioLib/src/modules/SX128x/SX1282.cpp | 9 + .../RadioLib/src/modules/SX128x/SX1282.h | 32 + .../RadioLib/src/modules/SX128x/SX128x.cpp | 1678 +++++++ .../RadioLib/src/modules/SX128x/SX128x.h | 945 ++++ .../RadioLib/src/modules/Si443x/Si4430.cpp | 39 + .../RadioLib/src/modules/Si443x/Si4430.h | 67 + .../RadioLib/src/modules/Si443x/Si4431.cpp | 32 + .../RadioLib/src/modules/Si443x/Si4431.h | 60 + .../RadioLib/src/modules/Si443x/Si4432.cpp | 39 + .../RadioLib/src/modules/Si443x/Si4432.h | 67 + .../RadioLib/src/modules/Si443x/Si443x.cpp | 809 ++++ .../RadioLib/src/modules/Si443x/Si443x.h | 860 ++++ .../RadioLib/src/modules/nRF24/nRF24.cpp | 657 +++ .../RadioLib/src/modules/nRF24/nRF24.h | 491 ++ .../RadioLib/src/protocols/AFSK/AFSK.cpp | 42 + .../RadioLib/src/protocols/AFSK/AFSK.h | 70 + .../RadioLib/src/protocols/APRS/APRS.cpp | 297 ++ .../RadioLib/src/protocols/APRS/APRS.h | 184 + .../RadioLib/src/protocols/AX25/AX25.cpp | 513 +++ .../RadioLib/src/protocols/AX25/AX25.h | 334 ++ .../src/protocols/BellModem/BellModem.cpp | 98 + .../src/protocols/BellModem/BellModem.h | 128 + .../protocols/ExternalRadio/ExternalRadio.cpp | 70 + .../protocols/ExternalRadio/ExternalRadio.h | 87 + .../RadioLib/src/protocols/FSK4/FSK4.cpp | 129 + .../RadioLib/src/protocols/FSK4/FSK4.h | 99 + .../protocols/Hellschreiber/Hellschreiber.cpp | 102 + .../protocols/Hellschreiber/Hellschreiber.h | 156 + .../src/protocols/LoRaWAN/LoRaWAN.cpp | 3453 +++++++++++++++ .../RadioLib/src/protocols/LoRaWAN/LoRaWAN.h | 1152 +++++ .../src/protocols/LoRaWAN/LoRaWANBands.cpp | 879 ++++ .../RadioLib/src/protocols/Morse/Morse.cpp | 187 + .../RadioLib/src/protocols/Morse/Morse.h | 181 + .../RadioLib/src/protocols/Pager/Pager.cpp | 594 +++ .../RadioLib/src/protocols/Pager/Pager.h | 206 + .../protocols/PhysicalLayer/PhysicalLayer.cpp | 550 +++ .../protocols/PhysicalLayer/PhysicalLayer.h | 733 +++ .../src/protocols/Print/ITA2String.cpp | 137 + .../RadioLib/src/protocols/Print/ITA2String.h | 82 + .../RadioLib/src/protocols/Print/Print.cpp | 312 ++ .../RadioLib/src/protocols/Print/Print.h | 70 + .../RadioLib/src/protocols/RTTY/RTTY.cpp | 127 + .../RadioLib/src/protocols/RTTY/RTTY.h | 85 + .../RadioLib/src/protocols/SSTV/SSTV.cpp | 376 ++ .../RadioLib/src/protocols/SSTV/SSTV.h | 208 + .../libraries/RadioLib/src/utils/CRC.cpp | 35 + .../source/libraries/RadioLib/src/utils/CRC.h | 65 + .../RadioLib/src/utils/Cryptography.cpp | 294 ++ .../RadioLib/src/utils/Cryptography.h | 174 + .../libraries/RadioLib/src/utils/FEC.cpp | 365 ++ .../source/libraries/RadioLib/src/utils/FEC.h | 154 + .../libraries/RadioLib/src/utils/Utils.cpp | 105 + .../libraries/RadioLib/src/utils/Utils.h | 42 + 133 files changed, 46678 insertions(+), 9 deletions(-) create mode 100644 software/firmware/source/libraries/RadioLib/.gitattributes create mode 100644 software/firmware/source/libraries/RadioLib/.gitignore create mode 100644 software/firmware/source/libraries/RadioLib/CMakeLists.txt create mode 100644 software/firmware/source/libraries/RadioLib/CODE_OF_CONDUCT.md create mode 100644 software/firmware/source/libraries/RadioLib/CONTRIBUTING.md create mode 100644 software/firmware/source/libraries/RadioLib/README.md create mode 100644 software/firmware/source/libraries/RadioLib/SECURITY.md create mode 100644 software/firmware/source/libraries/RadioLib/idf_component.yml create mode 100644 software/firmware/source/libraries/RadioLib/keywords.txt create mode 100644 software/firmware/source/libraries/RadioLib/library.json create mode 100644 software/firmware/source/libraries/RadioLib/library.properties create mode 100644 software/firmware/source/libraries/RadioLib/license.txt create mode 100644 software/firmware/source/libraries/RadioLib/src/BuildOpt.h create mode 100644 software/firmware/source/libraries/RadioLib/src/BuildOptUser.h create mode 100644 software/firmware/source/libraries/RadioLib/src/Hal.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/Hal.h create mode 100644 software/firmware/source/libraries/RadioLib/src/Module.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/Module.h create mode 100644 software/firmware/source/libraries/RadioLib/src/RadioLib.h create mode 100644 software/firmware/source/libraries/RadioLib/src/TypeDef.h create mode 100644 software/firmware/source/libraries/RadioLib/src/hal/Arduino/ArduinoHal.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/hal/Arduino/ArduinoHal.h create mode 100644 software/firmware/source/libraries/RadioLib/src/hal/ESP-IDF/EspHal.h create mode 100644 software/firmware/source/libraries/RadioLib/src/hal/RPi/PiHal.h create mode 100644 software/firmware/source/libraries/RadioLib/src/hal/RPiPico/PicoHal.h create mode 100644 software/firmware/source/libraries/RadioLib/src/hal/Tock/libtockHal.h create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/CC1101/CC1101.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/CC1101/CC1101.h create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/LLCC68/LLCC68.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/LLCC68/LLCC68.h create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/LR11x0/LR1110.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/LR11x0/LR1110.h create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/LR11x0/LR1120.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/LR11x0/LR1120.h create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/LR11x0/LR1121.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/LR11x0/LR1121.h create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/LR11x0/LR11x0.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/LR11x0/LR11x0.h create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/LR11x0/LR11x0_firmware.h create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/RF69/RF69.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/RF69/RF69.h create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/RFM2x/RFM22.h create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/RFM2x/RFM23.h create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/SX123x/SX1231.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/SX123x/SX1231.h create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/SX123x/SX1233.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/SX123x/SX1233.h create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/SX126x/STM32WLx.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/SX126x/STM32WLx.h create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/SX126x/STM32WLx_Module.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/SX126x/STM32WLx_Module.h create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/SX126x/SX1261.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/SX126x/SX1261.h create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/SX126x/SX1262.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/SX126x/SX1262.h create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/SX126x/SX1268.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/SX126x/SX1268.h create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/SX126x/SX126x.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/SX126x/SX126x.h create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/SX126x/SX126x_LR_FHSS.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/SX126x/patches/SX126x_patch_scan.h create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1272.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1272.h create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1273.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1273.h create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1276.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1276.h create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1277.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1277.h create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1278.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1278.h create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1279.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1279.h create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX127x.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX127x.h create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/SX128x/SX1280.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/SX128x/SX1280.h create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/SX128x/SX1281.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/SX128x/SX1281.h create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/SX128x/SX1282.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/SX128x/SX1282.h create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/SX128x/SX128x.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/SX128x/SX128x.h create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/Si443x/Si4430.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/Si443x/Si4430.h create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/Si443x/Si4431.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/Si443x/Si4431.h create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/Si443x/Si4432.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/Si443x/Si4432.h create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/Si443x/Si443x.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/Si443x/Si443x.h create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/nRF24/nRF24.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/modules/nRF24/nRF24.h create mode 100644 software/firmware/source/libraries/RadioLib/src/protocols/AFSK/AFSK.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/protocols/AFSK/AFSK.h create mode 100644 software/firmware/source/libraries/RadioLib/src/protocols/APRS/APRS.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/protocols/APRS/APRS.h create mode 100644 software/firmware/source/libraries/RadioLib/src/protocols/AX25/AX25.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/protocols/AX25/AX25.h create mode 100644 software/firmware/source/libraries/RadioLib/src/protocols/BellModem/BellModem.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/protocols/BellModem/BellModem.h create mode 100644 software/firmware/source/libraries/RadioLib/src/protocols/ExternalRadio/ExternalRadio.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/protocols/ExternalRadio/ExternalRadio.h create mode 100644 software/firmware/source/libraries/RadioLib/src/protocols/FSK4/FSK4.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/protocols/FSK4/FSK4.h create mode 100644 software/firmware/source/libraries/RadioLib/src/protocols/Hellschreiber/Hellschreiber.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/protocols/Hellschreiber/Hellschreiber.h create mode 100644 software/firmware/source/libraries/RadioLib/src/protocols/LoRaWAN/LoRaWAN.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/protocols/LoRaWAN/LoRaWAN.h create mode 100644 software/firmware/source/libraries/RadioLib/src/protocols/LoRaWAN/LoRaWANBands.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/protocols/Morse/Morse.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/protocols/Morse/Morse.h create mode 100644 software/firmware/source/libraries/RadioLib/src/protocols/Pager/Pager.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/protocols/Pager/Pager.h create mode 100644 software/firmware/source/libraries/RadioLib/src/protocols/PhysicalLayer/PhysicalLayer.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/protocols/PhysicalLayer/PhysicalLayer.h create mode 100644 software/firmware/source/libraries/RadioLib/src/protocols/Print/ITA2String.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/protocols/Print/ITA2String.h create mode 100644 software/firmware/source/libraries/RadioLib/src/protocols/Print/Print.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/protocols/Print/Print.h create mode 100644 software/firmware/source/libraries/RadioLib/src/protocols/RTTY/RTTY.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/protocols/RTTY/RTTY.h create mode 100644 software/firmware/source/libraries/RadioLib/src/protocols/SSTV/SSTV.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/protocols/SSTV/SSTV.h create mode 100644 software/firmware/source/libraries/RadioLib/src/utils/CRC.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/utils/CRC.h create mode 100644 software/firmware/source/libraries/RadioLib/src/utils/Cryptography.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/utils/Cryptography.h create mode 100644 software/firmware/source/libraries/RadioLib/src/utils/FEC.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/utils/FEC.h create mode 100644 software/firmware/source/libraries/RadioLib/src/utils/Utils.cpp create mode 100644 software/firmware/source/libraries/RadioLib/src/utils/Utils.h diff --git a/README.md b/README.md index 240aa9461..9819ac571 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ DIY, multi-functional, compatible, sub-1 GHz ISM band radio based proximity awar * for **education** purpose * [**Academy Edition**](https://github.com/lyusupov/SoftRF/wiki/Academy-Edition) ![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/updated-icon.gif) * for **amateur radio** operators - * [**Ham Edition**](https://github.com/lyusupov/SoftRF/wiki/Ham-Edition) ![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/new-icon.jpg) + * [**Ham Edition**](https://github.com/lyusupov/SoftRF/wiki/Ham-Edition) * **made in** EU * [**Balkan Edition**](https://github.com/lyusupov/SoftRF/wiki/Balkan-Edition) @@ -110,7 +110,7 @@ Model(s)|Platform|First appearance|       Sta 1 [Retro](https://github.com/lyusupov/SoftRF/wiki/Retro-Edition)
2 [**Dongle**](https://github.com/lyusupov/SoftRF/wiki/Dongle-Edition)
3 [**Bracelet**](https://github.com/lyusupov/SoftRF/wiki/Bracelet-Edition)|[STMicroelectronics](https://en.wikipedia.org/wiki/STMicroelectronics)
[STM32**F103**](https://www.st.com/en/microcontrollers-microprocessors/stm32f103.html) and
[STM32**L073**](https://www.st.com/en/microcontrollers-microprocessors/stm32l073rz.html)|Q3 2019|![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/icon_good.png)| [**Uni**](https://github.com/lyusupov/SoftRF/wiki/Uni-Edition)|[Texas Instruments
CC13x2R](http://www.ti.com/product/CC1352R)|Q2 2020|![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/icon_good.png)|1 Unique RF radio specs are useful for UAT978 **ADS-B** reception ;
2 holds [**FCC/CE** mark](https://github.com/lyusupov/SoftRF/wiki/Uni-Edition#certificates) [**Mini**](https://github.com/lyusupov/SoftRF/wiki/Mini-Edition)|[Cypress
PSoC 4100**S**](https://en.wikipedia.org/wiki/Cypress_PSoC)|Q3 2020|![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/icon_good.png)|1 good add-on candidate for modded Kobo e-Readers ;
2 holds [**FCC/CE** mark](https://github.com/lyusupov/SoftRF/wiki/Mini-Edition#certificates) -1 [**Badge**](https://github.com/lyusupov/SoftRF/wiki/Badge-Edition)
2 [**Card**](https://github.com/lyusupov/SoftRF/wiki/Card-Edition) ![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/new-icon.jpg)|[Nordic Semiconductor
nRF52840](https://www.nordicsemi.com/Products/Low-power-short-range-wireless/nRF52840)|Q4 2020|![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/icon_good.png)| holds [**FCC/CE** mark](https://github.com/lyusupov/SoftRF/wiki/Badge-Edition#certificates) +1 [**Badge**](https://github.com/lyusupov/SoftRF/wiki/Badge-Edition)
2 [**Card**](https://github.com/lyusupov/SoftRF/wiki/Card-Edition) ![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/new-icon.jpg)
3 [**Cozy**](https://github.com/lyusupov/SoftRF/wiki/Cozy-Edition) ![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/new-icon.jpg)|[Nordic Semiconductor
nRF52840](https://www.nordicsemi.com/Products/Low-power-short-range-wireless/nRF52840)|Q4 2020|![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/icon_good.png)| holds [**FCC/CE** mark](https://github.com/lyusupov/SoftRF/wiki/Badge-Edition#certificates) [**ES**](https://github.com/lyusupov/SoftRF/wiki/ES-Edition)|[NXP Semiconductors
LPC4320](https://en.wikipedia.org/wiki/NXP_LPC#LPC4000_series)|Q2 2021|![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/icon_may_need_imp.png)|Unique RF radio specs are useful for 1090ES **ADS-B** reception [ [1](https://user-images.githubusercontent.com/5849637/140610617-7c74582e-b0d7-4610-8ac2-d51f55c9086d.png) , [2](https://raw.githubusercontent.com/lyusupov/SoftRF/master/documents/images/ES-1.jpg) ] 1 [**Academy**](https://github.com/lyusupov/SoftRF/wiki/Academy-Edition)
2 [USB2BT](https://github.com/lyusupov/SoftRF/wiki/USB-to-Bluetooth-adapter)|[Microchip (Atmel)
SAM D21](https://www.microchip.com/en-us/products/microcontrollers-and-microprocessors/32-bit-mcus/sam-32-bit-mcus/sam-d) |Q4 2021|![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/icon_good.png) |good for teaching students of air traffic proximity awareness [**Octave**](https://github.com/lyusupov/SoftRF/wiki/Octave-Concept)|[ASR Microelectronics
ASR6601](https://asriot.readthedocs.io/en/latest/ASR6601/index.html)|Q1 2022|![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/icon_good.png)| **the best 'price per radio protocol' ratio** on the market @@ -118,11 +118,11 @@ Model(s)|Platform|First appearance|       Sta [**Balkan**](https://github.com/lyusupov/SoftRF/wiki/Balkan-Edition)|[STMicroelectronics](https://en.wikipedia.org/wiki/STMicroelectronics)
[STM32**WLE5**](https://www.st.com/en/microcontrollers-microprocessors/stm32wle5cc.html)|Q3 2022|![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/icon_good.png)| holds [**CE** mark](https://github.com/lyusupov/SoftRF/wiki/Balkan-Edition#certificates) [**WebTop USB**](https://github.com/lyusupov/SoftRF/wiki/WebTop-USB)|[Espressif
ESP32-S2](https://en.wikipedia.org/wiki/ESP32#ESP32-S2)|Q4 2022|![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/icon_good.png)| [**Standalone**](https://github.com/lyusupov/SoftRF/wiki/Standalone-Edition) **upgrade**![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/hot_icon.jpg)|[Espressif
ESP32-C3](https://en.wikipedia.org/wiki/ESP32#ESP32-C3)|Q1 2023|![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/icon_good.png)|[RISC-V](https://en.wikipedia.org/wiki/RISC-V) -1 [**Prime Mark III**](https://github.com/lyusupov/SoftRF/wiki/Prime-Edition-MkIII)![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/hot_icon.jpg)
2 [**SkyView Pico**](https://github.com/lyusupov/SoftRF/wiki/SkyView-Pico#alternative-hardware-option) ![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/new-icon.jpg)
3 [**Ham**](https://github.com/lyusupov/SoftRF/wiki/Ham-Edition) ![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/new-icon.jpg)
4 [**Midi**](https://github.com/lyusupov/SoftRF/wiki/Midi-Edition) ![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/new-icon.jpg)|[Espressif
ESP32-S3](https://en.wikipedia.org/wiki/ESP32#ESP32-S3)|Q1 2023|![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/icon_may_need_imp.png)| +1 [**Prime Mark III**](https://github.com/lyusupov/SoftRF/wiki/Prime-Edition-MkIII)![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/hot_icon.jpg)
2 [**SkyView Pico**](https://github.com/lyusupov/SoftRF/wiki/SkyView-Pico#alternative-hardware-option) ![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/new-icon.jpg)
3 [**Ham**](https://github.com/lyusupov/SoftRF/wiki/Ham-Edition)
4 [**Midi**](https://github.com/lyusupov/SoftRF/wiki/Midi-Edition)|[Espressif
ESP32-S3](https://en.wikipedia.org/wiki/ESP32#ESP32-S3)|Q1 2023|![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/icon_may_need_imp.png)| 1 [**Standalone**](https://github.com/lyusupov/SoftRF/wiki/Standalone-Edition) **upgrade**![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/new-icon.jpg)
2 [**Eco**](https://github.com/lyusupov/SoftRF/wiki/Eco-Edition)![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/new-icon.jpg)
3 [**Duo**](https://github.com/lyusupov/SoftRF/wiki/Eco-Edition#duo-concept)![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/new-icon.jpg)|[Espressif
ESP32-C6](https://en.wikipedia.org/wiki/ESP32#ESP32-C6)|Q1 2024|![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/icon_may_need_imp.png)|2 [RISC-V](https://en.wikipedia.org/wiki/RISC-V) cores
big.LITTLE [**Academy**](https://github.com/lyusupov/SoftRF/wiki/Academy-Edition) ![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/updated-icon.gif) **upgrade**|[Renesas
RA4M1](https://en.wikipedia.org/wiki/Renesas_Electronics#The_RA_MCU_family)|Q1 2024|![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/icon_may_need_imp.png)| Academy|[Silicon Labs](https://en.wikipedia.org/wiki/Silicon_Labs)
[EFx32](https://en.wikipedia.org/wiki/EFM32)|Q3 2024|![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/icon_in_progress.png)| -Academy|[Nanjing Qinheng Microelectronics](https://www.wch-ic.com/)
[CH32V307](https://www.wch-ic.com/products/CH32V307.html)|Q3 2024|![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/icon_may_need_imp.png)|[RISC-V](https://en.wikipedia.org/wiki/RISC-V) IMAFC +Academy|[Nanjing Qinheng Microelectronics](https://www.wch-ic.com/)
[CH32V307](https://www.wch-ic.com/products/CH32V307.html)|Q3 2024|![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/icon_may_need_imp.png)|[RISC-V](https://en.wikipedia.org/wiki/RISC-V)
IMAFC ## By sub-1 GHz radio Radio|Model(s)|First appearance|       Status       |Notes @@ -183,17 +183,22 @@ Generic
NMEA|[Standalone](https://github.com/lyusupov/SoftRF/wiki/Standalone- * [Assembly](https://github.com/lyusupov/SoftRF/tree/master/hardware) * [Enclosure](https://github.com/lyusupov/SoftRF/tree/master/case/v4) * [ESP32 adapter](https://github.com/lyusupov/ESP32-NODEMCU-ADAPTER) - * [ESP32-C3 upgrade](https://github.com/lyusupov/SoftRF/wiki/ESP32%E2%80%90C3-upgrade-for-Standalone-Edition) ![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/new-icon.jpg) + * [ESP32-C3 upgrade](https://github.com/lyusupov/SoftRF/wiki/ESP32%E2%80%90C3-upgrade-for-Standalone-Edition) ![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/hot_icon.jpg) + * [ESP32-C6 upgrade](https://github.com/lyusupov/SoftRF/wiki/ESP32%E2%80%90C6-upgrade-for-Standalone-Edition) ![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/new-icon.jpg) * [Card Edition](https://github.com/lyusupov/SoftRF/wiki/Card-Edition) ![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/new-icon.jpg) * [Quick start](https://github.com/lyusupov/SoftRF/wiki/Card-Edition.-Quick-start) -* [Ham Edition](https://github.com/lyusupov/SoftRF/wiki/Ham-Edition) ![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/new-icon.jpg) +* [Ham Edition](https://github.com/lyusupov/SoftRF/wiki/Ham-Edition) * [Quick start](https://github.com/lyusupov/SoftRF/wiki/Ham-Edition.-Quick-start) * [ES Edition](https://github.com/lyusupov/SoftRF/wiki/ES-Edition) +* [Eco Edition](https://github.com/lyusupov/SoftRF/wiki/Eco-Edition) ![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/new-icon.jpg) +* [Duo Concept](https://github.com/lyusupov/SoftRF/wiki/Eco-Edition#duo-concept) ![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/new-icon.jpg) * [Octave Concept](https://github.com/lyusupov/SoftRF/wiki/Octave-Concept) * [Uni Edition](https://github.com/lyusupov/SoftRF/wiki/Uni-Edition) * [Firmware installation](https://github.com/lyusupov/SoftRF/wiki/Uni-Edition.-Firmware-maintenance-procedures) * [Enclosure](https://github.com/lyusupov/SoftRF/tree/master/case/Uni) -* [Midi Edition](https://github.com/lyusupov/SoftRF/wiki/Midi-Edition) ![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/new-icon.jpg) +* [Cozy Edition](https://github.com/lyusupov/SoftRF/wiki/Cozy-Edition) ![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/new-icon.jpg) + * [Quick start](https://github.com/lyusupov/SoftRF/wiki/Cozy-Edition.-Quick-start) +* [Midi Edition](https://github.com/lyusupov/SoftRF/wiki/Midi-Edition) ![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/hot_icon.jpg) * [Quick start](https://github.com/lyusupov/SoftRF/wiki/Midi-Edition.-Quick-start) * [Mini Edition](https://github.com/lyusupov/SoftRF/wiki/Mini-Edition) * [Firmware installation](https://github.com/lyusupov/SoftRF/tree/master/software/firmware/binaries#cubecell) @@ -300,11 +305,11 @@ Generic
NMEA|[Standalone](https://github.com/lyusupov/SoftRF/wiki/Standalone- ![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/t-motion-22.jpg) -* [**Ham Edition**](https://github.com/lyusupov/SoftRF/wiki/Ham-Edition) ![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/new-icon.jpg) +* [**Ham Edition**](https://github.com/lyusupov/SoftRF/wiki/Ham-Edition)

-* [**Midi Edition**](https://github.com/lyusupov/SoftRF/wiki/Midi-Edition) ![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/new-icon.jpg) +* [**Midi Edition**](https://github.com/lyusupov/SoftRF/wiki/Midi-Edition) ![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/hot_icon.jpg) ![](https://github.com/lyusupov/SoftRF/raw/master/documents/images/midi-9.jpg) diff --git a/software/firmware/source/libraries/RadioLib/.gitattributes b/software/firmware/source/libraries/RadioLib/.gitattributes new file mode 100644 index 000000000..bbf0d9807 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/.gitattributes @@ -0,0 +1,3 @@ +# exclude binary patch files from language detection +src/modules/SX126x/patches/*.h linguist-detectable=false +src/modules/LR11x0/firmware/*.h linguist-detectable=false diff --git a/software/firmware/source/libraries/RadioLib/.gitignore b/software/firmware/source/libraries/RadioLib/.gitignore new file mode 100644 index 000000000..b65463c98 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/.gitignore @@ -0,0 +1,28 @@ +# Arduino Library Development file +.development + +# Atom +*.tags +*.tags1 + +# VS Code +.vscode + +# Jetbrain IDEs +.idea + +# Debug decoder +extras/decoder/log.txt +extras/decoder/out.txt + +# Spectrum scan +extras/SX126x_Spectrum_Scan/out/* + +# PlatformIO +.pio* + +# cmake +build/ + +# Compote build output +dist diff --git a/software/firmware/source/libraries/RadioLib/CMakeLists.txt b/software/firmware/source/libraries/RadioLib/CMakeLists.txt new file mode 100644 index 000000000..229105091 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/CMakeLists.txt @@ -0,0 +1,51 @@ +cmake_minimum_required(VERSION 3.13) + +if(ESP_PLATFORM) + # Build RadioLib as an ESP-IDF component + # required because ESP-IDF runs cmake in script mode + # and needs idf_component_register() + file(GLOB_RECURSE RADIOLIB_ESP_SOURCES + "src/*.*" + ) + + idf_component_register( + SRCS ${RADIOLIB_ESP_SOURCES} + INCLUDE_DIRS . src + ) + + return() +endif() + +if(CMAKE_SCRIPT_MODE_FILE) + message(FATAL_ERROR "Attempted to build RadioLib in script mode") +endif() + +project(radiolib) + +file(GLOB_RECURSE RADIOLIB_SOURCES + "src/*.cpp" +) + +add_library(RadioLib ${RADIOLIB_SOURCES}) + +target_include_directories(RadioLib + PUBLIC $ + $) + +# use c++20 standard +set_property(TARGET RadioLib PROPERTY CXX_STANDARD 20) + +# enable most warnings +target_compile_options(RadioLib PRIVATE -Wall -Wextra) + +include(GNUInstallDirs) + +install(TARGETS RadioLib + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src/ + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/RadioLib + FILES_MATCHING PATTERN "*.h" +) diff --git a/software/firmware/source/libraries/RadioLib/CODE_OF_CONDUCT.md b/software/firmware/source/libraries/RadioLib/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..1a32eaccb --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/CODE_OF_CONDUCT.md @@ -0,0 +1,3 @@ +# Code of Conduct + +Don't be an a*shole. diff --git a/software/firmware/source/libraries/RadioLib/CONTRIBUTING.md b/software/firmware/source/libraries/RadioLib/CONTRIBUTING.md new file mode 100644 index 000000000..4bf96194b --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/CONTRIBUTING.md @@ -0,0 +1,110 @@ +# Contributing to RadioLib + +First of all, thank you very much for taking the time to contribute! All feedback and ideas are greatly appreciated. +To keep this library organized, please follow these rules. + +## Issues + +The following rules guide submission of new issues. These rules are in place mainly so that the issue author can get help as quickly as possible. + +1. **Questions are welcome, spam is not.** +Any issues without description will be considered spam and as such will be **CLOSED** and **LOCKED** immediately! +2. **This repository has issue templates.** +To report bugs or suggest new features, use the provided issue templates. Use the default issue only if the templates do not fit your issue type. +3. **Be as clear as possible when creating issues.** +Issues with generic titles (e.g. "not working", "lora", etc.) will be **CLOSED** until the title is fixed, since the title is supposed to categorize the issue. The same applies for issues with very little information and extensive grammatical or formatting errors that make it difficult to find out what is the actual issue. +4. **Issues deserve some attention too.** +Issues that are left for 2 weeks without response by the original author when asked for further information will be closed due to inactivity. This is to keep track of important issues, the author is encouraged to reopen the issue at a later date. + +## Code style guidelines + +I like pretty code! Or at least, I like *consistent* code style. When creating pull requests, please follow these style guidelines, they're in place to keep high code readability. + +1. **Bracket style** +This library uses the following style of bracket indentation (1TBS, or "javascript" style): + +```c++ +if (foo) { + bar(); +} else { + baz(); +} +``` + +2. **Tabs** +Use 2 space characters for tabs. + +3. **Single-line comments** +Comments can be very useful - and they can become the bane of readability. Every single-line comment should start at new line, have one space between comment delimiter `//` and the start of the comment itself. The comment should also start with a lower-case letter. + +```c++ +// this function does something +foo("bar"); + +// here it does something else +foo(12345); +``` + +4. **Split code into blocks** +It is very easy to write code that machine can read. It is much harder to write one that humans can read. That's why it's a great idea to split code into blocks - even if the block is just a single line! + +```c++ +// build a temporary buffer (first block) +uint8_t* data = new uint8_t[len + 1]; +if(!data) { + return(RADIOLIB_ERR_MEMORY_ALLOCATION_FAILED); +} + +// read the received data (second block) +state = readData(data, len); + +// add null terminator (third block) +data[len] = 0; +``` + +5. **Doxygen** +If you're adding a new method, make sure to add appropriate Doxygen comments, so that the documentation is always complete. + +6. **Keywords** +This is an Arduino library, so it needs to comply with the Arduino library specification. To add a new keyword to the Arduino IDE syntax highlighting, add it to the keywords.txt file. **Use true tabs in keywords.txt! No spaces there!** + +7. **Dynamic memory** +Sometimes, RadioLib might be used in critical applications where dynamic memory allocation using `new` or `malloc` might cause issues. For such cases, RadioLib provides the option to compile using only static arrays. This means that every dynamically allocated array must have a sufficiently large static counterpart. Naturally, all dynamically allocated memory must be properly de-allocated using `delete` or `free`. + +```c++ +// build a temporary buffer +#if defined(RADIOLIB_STATIC_ONLY) + uint8_t data[RADIOLIB_STATIC_ARRAY_SIZE + 1]; +#else + uint8_t* data = new uint8_t[length + 1]; + if(!data) { + return(RADIOLIB_ERR_MEMORY_ALLOCATION_FAILED); + } +#endif + +// read the received data +readData(data, length); + +// deallocate temporary buffer +#if !defined(RADIOLIB_STATIC_ONLY) + delete[] data; +#endif +``` + +8. **God Mode** +During development, it can be useful to have access to the low level drivers, such as the SPI commands. These are incredibly powerful, since they will basically let user do anything he wants with the module, outside of the normal level of sanity checks. As such, they are normally protected using C++ access modifiers `private` or `protected`. God mode disables this protection, and so any newly implemented `class` must contain the appropriate macro check: + +```c++ +class Module { + void publicMethod(); + +#if defined(RADIOLIB_GODMODE) + private: +#endif + + void privateMethod(); +}; +``` + +9. **No Arduino Strings** +Arduino `String` class should never be used internally in the library. The only allowed occurence of Arduino `String` is in public API methods, and only at the top-most layer. diff --git a/software/firmware/source/libraries/RadioLib/README.md b/software/firmware/source/libraries/RadioLib/README.md new file mode 100644 index 000000000..c608e2d6f --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/README.md @@ -0,0 +1,92 @@ +# RadioLib ![Build Status](https://github.com/jgromes/RadioLib/workflows/CI/badge.svg) [![PlatformIO Registry](https://badges.registry.platformio.org/packages/jgromes/library/RadioLib.svg)](https://registry.platformio.org/libraries/jgromes/RadioLib) [![Component Registry](https://components.espressif.com/components/jgromes/radiolib/badge.svg)](https://components.espressif.com/components/jgromes/radiolib) + +### _One radio library to rule them all!_ + +## Universal wireless communication library for embedded devices + +## See the [Wiki](https://github.com/jgromes/RadioLib/wiki) and [FAQ](https://github.com/jgromes/RadioLib/wiki/Frequently-Asked-Questions) for further information. See the [GitHub Pages](https://jgromes.github.io/RadioLib) for detailed and up-to-date API reference. + +RadioLib allows its users to integrate all sorts of different wireless communication modules, protocols and even digital modes into a single consistent system. +Want to add a Bluetooth interface to your LoRa network? Sure thing! Do you just want to go really old-school and play around with radio teletype, slow-scan TV, or even Hellschreiber using nothing but a cheap radio module? Why not! + +RadioLib natively supports Arduino, but can run in non-Arduino environments as well! See [this Wiki page](https://github.com/jgromes/RadioLib/wiki/Porting-to-non-Arduino-Platforms) and [examples/NonArduino](https://github.com/jgromes/RadioLib/tree/master/examples/NonArduino). + +RadioLib was originally created as a driver for [__RadioShield__](https://github.com/jgromes/RadioShield), but it can be used to control as many different wireless modules as you like - or at least as many as your microcontroller can handle! + +### Supported modules: +* __CC1101__ FSK radio module +* __LLCC68__ LoRa module +* __LR11x0__ series LoRa/GFSK modules (LR1110, LR1120, LR1121) +* __nRF24L01__ 2.4 GHz module +* __RF69__ FSK/OOK radio module +* __RFM2x__ series FSK modules (RFM22, RM23) +* __RFM9x__ series LoRa modules (RFM95, RM96, RFM97, RFM98) +* __Si443x__ series FSK modules (Si4430, Si4431, Si4432) +* __STM32WL__ integrated microcontroller/LoRa module +* __SX126x__ series LoRa modules (SX1261, SX1262, SX1268) +* __SX127x__ series LoRa modules (SX1272, SX1273, SX1276, SX1277, SX1278, SX1279) +* __SX128x__ series LoRa/GFSK/BLE/FLRC modules (SX1280, SX1281, SX1282) +* __SX123x__ FSK/OOK radio modules (SX1231, SX1233) + +### Supported protocols and digital modes: +* [__AX.25__](https://www.sigidwiki.com/wiki/PACKET) using 2-FSK or AFSK for modules: +SX127x, RFM9x, SX126x, RF69, SX1231, CC1101, RFM2x, Si443x, LR11x0 and SX128x +* [__RTTY__](https://www.sigidwiki.com/wiki/RTTY) using 2-FSK or AFSK for modules: +SX127x, RFM9x, SX126x, RF69, SX1231, CC1101, nRF24L01, RFM2x, Si443x, LR11x0 and SX128x +* [__Morse Code__](https://www.sigidwiki.com/wiki/Morse_Code_(CW)) using 2-FSK or AFSK for modules: +SX127x, RFM9x, SX126x, RF69, SX1231, CC1101, nRF24L01, RFM2x, Si443x, LR11x0 and SX128x +* [__SSTV__](https://www.sigidwiki.com/wiki/SSTV) using 2-FSK or AFSK for modules: +SX127x, RFM9x, SX126x, RF69, SX1231, CC1101, RFM2x and Si443x +* [__Hellschreiber__](https://www.sigidwiki.com/wiki/Hellschreiber) using 2-FSK or AFSK for modules: +SX127x, RFM9x, SX126x, RF69, SX1231, CC1101, nRF24L01, RFM2x, Si443x, LR11x0 and SX128x +* [__APRS__](https://www.sigidwiki.com/wiki/APRS) using AFSK for modules: +SX127x, RFM9x, SX126x, RF69, SX1231, CC1101, nRF24L01, RFM2x, Si443x and SX128x +* [__POCSAG__](https://www.sigidwiki.com/wiki/POCSAG) using 2-FSK for modules: +SX127x, RFM9x, RF69, SX1231, CC1101, nRF24L01, RFM2x and Si443x +* [__LoRaWAN__](https://lora-alliance.org/) using LoRa for modules: +SX127x, RFM9x, SX126x, LR11x0 and SX128x + +### Supported Arduino platforms: +* __Arduino__ + * [__AVR__](https://github.com/arduino/ArduinoCore-avr) - Arduino Uno, Mega, Leonardo, Pro Mini, Nano etc. + * NOTE: Arduino boards based on ATmega328 (Uno, Pro Mini, Nano etc.) and smaller are NOT recommended. This is because the ATmega328 MCU is very constrained in terms of program and memory size, so the library will end up taking most of the space available. + * [__mbed__](https://github.com/arduino/ArduinoCore-mbed) - Arduino Nano 33 BLE and Arduino Portenta H7 + * [__megaAVR__](https://github.com/arduino/ArduinoCore-megaavr) - Arduino Uno WiFi Rev.2 and Nano Every + * [__SAM__](https://github.com/arduino/ArduinoCore-sam) - Arduino Due + * [__SAMD__](https://github.com/arduino/ArduinoCore-samd) - Arduino Zero, MKR boards, M0 Pro etc. + * [__Renesas__](https://github.com/arduino/ArduinoCore-renesas) - Arduino Uno R4 + +* __Adafruit__ + * [__SAMD__](https://github.com/adafruit/ArduinoCore-samd) - Adafruit Feather M0 and M4 boards (Feather, Metro, Gemma, Trinket etc.) + * [__nRF52__](https://github.com/adafruit/Adafruit_nRF52_Arduino) - Adafruit Feather nRF528x, Bluefruit and CLUE + +* __Espressif__ + * [__ESP32__](https://github.com/espressif/arduino-esp32) - ESP32-based boards + * [__ESP8266__](https://github.com/esp8266/Arduino) - ESP8266-based boards + +* __Intel__ + * [__Curie__](https://github.com/arduino/ArduinoCore-arc32) - Arduino 101 + +* __SparkFun__ + * [__Apollo3__](https://github.com/sparkfun/Arduino_Apollo3) - Sparkfun Artemis Redboard + +* __ST Microelectronics__ + * [__STM32__ (official core)](https://github.com/stm32duino/Arduino_Core_STM32) - STM32 Nucleo, Discovery, Maple, BluePill, BlackPill etc. + * [__STM32__ (unofficial core)](https://github.com/rogerclarkmelbourne/Arduino_STM32) - STM32F1 and STM32F4-based boards + +* __MCUdude__ + * [__MegaCoreX__](https://github.com/MCUdude/MegaCoreX) - megaAVR-0 series (ATmega4809, ATmega3209 etc.) + * [__MegaCore__](https://github.com/MCUdude/MegaCore) - AVR (ATmega1281, ATmega640 etc.) + +* __Raspberry Pi__ + * [__RP2040__ (official core)](https://github.com/arduino/ArduinoCore-mbed) - Raspberry Pi Pico and Arduino Nano RP2040 Connect + * [__RP2040__ (unofficial core)](https://github.com/earlephilhower/arduino-pico) - Raspberry Pi Pico/RP2040-based boards + * [__Raspberry Pi__](https://github.com/me-no-dev/RasPiArduino) - Arduino framework for RaspberryPI + +* __Heltec__ + * [__CubeCell__](https://github.com/HelTecAutomation/CubeCell-Arduino) - ASR650X series (CubeCell-Board, CubeCell-Capsule, CubeCell-Module etc.) + +* __PJRC__ + * [__Teensy__](https://github.com/PaulStoffregen/cores) - Teensy 2.x, 3.x and 4.x boards + +The list above is by no means exhaustive - RadioLib code is independent of the used platform! Compilation of all examples is tested for all platforms officially supported prior to releasing new version. In addition, RadioLib includes an internal hardware abstraction layer, which allows it to be easily ported even to non-Arduino environments. diff --git a/software/firmware/source/libraries/RadioLib/SECURITY.md b/software/firmware/source/libraries/RadioLib/SECURITY.md new file mode 100644 index 000000000..b43ea5c1d --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/SECURITY.md @@ -0,0 +1,5 @@ +# Security Policy + +## Reporting a Vulnerability + +RadioLib is provided as-is without any warranty, and is not intended to be used in security-critical applications. However, if you discover a vulnerability within the library code, please report it to gromes.jan@gmail.com. diff --git a/software/firmware/source/libraries/RadioLib/idf_component.yml b/software/firmware/source/libraries/RadioLib/idf_component.yml new file mode 100644 index 000000000..9f175c3bc --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/idf_component.yml @@ -0,0 +1,43 @@ +version: "7.1.0" +description: "Universal wireless communication library. User-friendly library for sub-GHz radio modules (SX1278, RF69, CC1101, SX1268, and many others), as well as ham radio digital modes (RTTY, SSTV, AX.25 etc.) and other protocols (Pagers, LoRaWAN)." +tags: + - radio + - communication + - morse + - cc1101 + - aprs + - sx1276 + - sx1278 + - sx1272 + - rtty + - ax25 + - afsk + - nrf24 + - rf69 + - sx1231 + - rfm96 + - rfm98 + - sstv + - sx1280 + - sx1281 + - sx1282 + - sx1261 + - sx1262 + - sx1268 + - si4432 + - rfm22 + - llcc68 + - pager + - pocsag + - lorawan + - lr1110 + - lr1120 + - lr1121 +url: "https://github.com/jgromes/RadioLib" +repository: "https://github.com/jgromes/RadioLib.git" +license: "MIT" +dependencies: + # Required IDF version + idf: ">=4.1" +maintainers: + - Jan Gromeš diff --git a/software/firmware/source/libraries/RadioLib/keywords.txt b/software/firmware/source/libraries/RadioLib/keywords.txt new file mode 100644 index 000000000..179b4e293 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/keywords.txt @@ -0,0 +1,517 @@ +####################################### +# Syntax Coloring Map For RadioLib +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +RadioLib KEYWORD1 +Module KEYWORD1 +RadioLibHal KEYWORD1 +ArduinoHal KEYWORD1 + +# modules +CC1101 KEYWORD1 +LLCC68 KEYWORD1 +LR1110 KEYWORD1 +LR1120 KEYWORD1 +LR1121 KEYWORD1 +nRF24 KEYWORD1 +RF69 KEYWORD1 +RFM22 KEYWORD1 +RFM23 KEYWORD1 +RFM95 KEYWORD1 +RFM96 KEYWORD1 +RFM97 KEYWORD1 +RFM98 KEYWORD1 +Si4430 KEYWORD1 +Si4431 KEYWORD1 +Si4432 KEYWORD1 +SIM800 KEYWORD1 +SX1231 KEYWORD1 +SX1233 KEYWORD1 +SX1261 KEYWORD1 +SX1262 KEYWORD1 +SX1268 KEYWORD1 +SX1272 KEYWORD1 +SX1273 KEYWORD1 +SX1276 KEYWORD1 +SX1277 KEYWORD1 +SX1278 KEYWORD1 +SX1279 KEYWORD1 +SX1280 KEYWORD1 +SX1281 KEYWORD1 +SX1282 KEYWORD1 +STM32WLx KEYWORD1 +STM32WLx_Module KEYWORD1 + +# protocols +RTTYClient KEYWORD1 +MorseClient KEYWORD1 +AX25Client KEYWORD1 +AX25Frame KEYWORD1 +SSTVClient KEYWORD1 +HellClient KEYWORD1 +AFSKClient KEYWORD1 +FSK4Client KEYWORD1 +APRSClient KEYWORD1 +PagerClient KEYWORD1 +ExternalRadio KEYWORD1 +BellClient KEYWORD1 +LoRaWANNode KEYWORD1 +LoRaWANBand_t KEYWORD1 +LoRaWANEvent_t KEYWORD1 + +# SSTV modes +Scottie1 KEYWORD1 +Scottie2 KEYWORD1 +ScottieDX KEYWORD1 +Martin1 KEYWORD1 +Martin2 KEYWORD1 +Wrasse KEYWORD1 +PasokonP3 KEYWORD1 +PasokonP5 KEYWORD1 +PasokonP7 KEYWORD1 +Robot36 KEYWORD1 +Robot72 KEYWORD1 + +# Bell Modems +Bell101 KEYWORD1 +Bell103 KEYWORD1 +Bell202 KEYWORD1 + +# LoRaWAN bands +EU868 KEYWORD1 +US915 KEYWORD1 +EU433 KEYWORD1 +AU915 KEYWORD1 +CN500 KEYWORD1 +AS923 KEYWORD1 +AS923_2 KEYWORD1 +AS923_3 KEYWORD1 +AS923_4 KEYWORD1 +KR920 KEYWORD1 +IN865 KEYWORD1 + +# LR11x0 structures +LR11x0WifiResult_t KEYWORD1 +LR11x0WifiResultFull_t KEYWORD1 +LR11x0WifiResultExtended_t KEYWORD1 +LR11x0VersionInfo_t KEYWORD1 +LR11x0GnssResult_t KEYWORD1 +LR11x0GnssPosition_t KEYWORD1 +LR11x0GnssSatellite_t KEYWORD1 +LR11x0GnssAlmanacStatusPart_t KEYWORD1 +LR11x0GnssAlmanacStatus_t KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +# RadioLib +ModuleA KEYWORD2 +ModuleB KEYWORD2 +setRfSwitchTable KEYWORD2 + +# SX127x/RFM9x + RF69 + CC1101 +begin KEYWORD2 +beginFSK KEYWORD2 +transmit KEYWORD2 +receive KEYWORD2 +scanChannel KEYWORD2 +sleep KEYWORD2 +standby KEYWORD2 +transmitDirect KEYWORD2 +receiveDirect KEYWORD2 +packetMode KEYWORD2 +setDio0Action KEYWORD2 +setDio1Action KEYWORD2 +clearDio0Action KEYWORD2 +clearDio1Action KEYWORD2 +startTransmit KEYWORD2 +finishTransmit KEYWORD2 +startReceive KEYWORD2 +readData KEYWORD2 +startChannelScan KEYWORD2 +getChannelScanResult KEYWORD2 +setBandwidth KEYWORD2 +setSpreadingFactor KEYWORD2 +setCodingRate KEYWORD2 +setFrequency KEYWORD2 +setSyncWord KEYWORD2 +setOutputPower KEYWORD2 +checkOutputPower KEYWORD2 +setCurrentLimit KEYWORD2 +setPreambleLength KEYWORD2 +setGain KEYWORD2 +getFrequencyError KEYWORD2 +getRSSI KEYWORD2 +getAFCError KEYWORD2 +getSNR KEYWORD2 +getDataRate KEYWORD2 +setBitRate KEYWORD2 +setRxBandwidth KEYWORD2 +autoSetRxBandwidth KEYWORD2 +setAFCBandwidth KEYWORD2 +setAFC KEYWORD2 +setAFCAGCTrigger KEYWORD2 +setFrequencyDeviation KEYWORD2 +setNodeAddress KEYWORD2 +setBroadcastAddress KEYWORD2 +disableAddressFiltering KEYWORD2 +setDataShaping KEYWORD2 +setOOK KEYWORD2 +setDataShapingOOK KEYWORD2 +setCRC KEYWORD2 +variablePacketLengthMode KEYWORD2 +fixedPacketLengthMode KEYWORD2 +setCrcFiltering KEYWORD2 +enableSyncWordFiltering KEYWORD2 +disableSyncWordFiltering KEYWORD2 +setPromiscuous KEYWORD2 +setRSSIConfig KEYWORD2 +setEncoding KEYWORD2 +getIRQFlags KEYWORD2 +getModemStatus KEYWORD2 +getTempRaw KEYWORD2 +setRfSwitchPins KEYWORD2 +forceLDRO KEYWORD2 +autoLDRO KEYWORD2 +getChipVersion KEYWORD2 +invertIQ KEYWORD2 +setOokThresholdType KEYWORD2 +setOokPeakThresholdDecrement KEYWORD2 +setOokFixedOrFloorThreshold KEYWORD2 +setOokPeakThresholdStep KEYWORD2 +setDirectSyncWord KEYWORD2 +setDirectAction KEYWORD2 +readBit KEYWORD2 +enableBitSync KEYWORD2 +disableBitSync KEYWORD2 +setFHSSHoppingPeriod KEYWORD2 +getFHSSHoppingPeriod KEYWORD2 +getFHSSChannel KEYWORD2 +clearFHSSInt KEYWORD2 +randomByte KEYWORD2 +getPacketLength KEYWORD2 +setFifoEmptyAction KEYWORD2 +clearFifoEmptyAction KEYWORD2 +setFifoFullAction KEYWORD2 +clearFifoFullAction KEYWORD2 +fifoAdd KEYWORD2 +fifoGet KEYWORD2 +setLowBatteryThreshold KEYWORD2 + +# RF69-specific +setAESKey KEYWORD2 +enableAES KEYWORD2 +disableAES KEYWORD2 +getTemperature KEYWORD2 +setAmbientTemperature KEYWORD2 +setLnaTestBoost KEYWORD2 +setOokFixedThreshold KEYWORD2 +enableContinuousModeBitSync KEYWORD2 +disableContinuousModeBitSync KEYWORD2 + +# CC1101-specific +getLQI KEYWORD2 +setGdo0Action KEYWORD2 +setGdo2Action KEYWORD2 +clearGdo0Action KEYWORD2 +clearGdo2Action KEYWORD2 +setCrcFiltering KEYWORD2 + +# SX126x-specific +setTCXO KEYWORD2 +setDio2AsRfSwitch KEYWORD2 +getTimeOnAir KEYWORD2 +implicitHeader KEYWORD2 +explicitHeader KEYWORD2 +setSyncBits KEYWORD2 +setWhitening KEYWORD2 +startReceiveDutyCycle KEYWORD2 +startReceiveDutyCycleAuto KEYWORD2 +setRegulatorLDO KEYWORD2 +setRegulatorDCDC KEYWORD2 +getCurrentLimit KEYWORD2 +getIrqStatus KEYWORD2 +getLastError KEYWORD2 +setRxBoostedGainMode KEYWORD2 +uploadPatch KEYWORD2 +spectralScanStart KEYWORD2 +spectralScanAbort KEYWORD2 +spectralScanGetStatus KEYWORD2 +spectralScanGetResult KEYWORD2 +setPaRampTime KEYWORD2 + +# nRF24 +setIrqAction KEYWORD2 +setAddressWidth KEYWORD2 +setTransmitPipe KEYWORD2 +setReceivePipe KEYWORD2 +disablePipe KEYWORD2 +getStatus KEYWORD2 +setAutoAck KEYWORD2 + +# LR11x0 +beginLRFHSS KEYWORD2 +setLrFhssConfig KEYWORD2 +startWifiScan KEYWORD2 +getWifiScanResultsCount KEYWORD2 +getWifiScanResult KEYWORD2 +wifiScan KEYWORD2 +setWiFiScanAction KEYWORD2 +clearWiFiScanAction KEYWORD2 +getVersionInfo KEYWORD2 +updateFirmware KEYWORD2 +beginGNSS KEYWORD2 +isGnssScanCapable KEYWORD2 +gnssScan KEYWORD2 +getGnssAlmanacStatus KEYWORD2 +gnssDelayUntilSubframe KEYWORD2 +updateGnssAlmanac KEYWORD2 +getGnssPosition KEYWORD2 +getGnssSatellites KEYWORD2 + +# RTTY +idle KEYWORD2 +byteArr KEYWORD2 + +# Morse +startSignal KEYWORD2 + +# AX.25 +setRepeaters KEYWORD2 +setRecvSequence KEYWORD2 +setSendSequence KEYWORD2 +sendFrame KEYWORD2 +setCorrection KEYWORD2 + +# SSTV +sendHeader KEYWORD2 +sendLine KEYWORD2 +getPictureHeight KEYWORD2 + +# SX128x +beginGFSK KEYWORD2 +beginFLRC KEYWORD2 +beginBLE KEYWORD2 +setAccessAddress KEYWORD2 +range KEYWORD2 +startRanging KEYWORD2 +getRangingResult KEYWORD2 + +# Hellschreiber +printGlyph KEYWORD2 +setInversion KEYWORD2 + +# AFSK +tone KEYWORD2 +noTone KEYWORD2 + +# APRS +sendPosition KEYWORD2 +sendMicE KEYWORD2 +useRepeaters KEYWORD2 +dropRepeaters KEYWORD2 + +# Pager +sendTone KEYWORD2 + +# PhysicalLayer +RadioLibIrqType_t KEYWORD1 +LoRaRate_t KEYWORD1 +FSKRate_t KEYWORD1 +LrFhssRate_t KEYWORD1 +DataRate_t KEYWORD1 +CADScanConfig_t KEYWORD1 +RSSIScanConfig_t KEYWORD1 +ChannelScanConfig_t KEYWORD1 +ModemType_t KEYWORD1 +dropSync KEYWORD2 +setTimerFlag KEYWORD2 +setInterruptSetup KEYWORD2 +setPacketReceivedAction KEYWORD2 +clearPacketReceivedAction KEYWORD2 +setPacketSentAction KEYWORD2 +clearPacketSentAction KEYWORD2 +setDataRate KEYWORD2 +checkDataRate KEYWORD2 +setModem KEYWORD2 +getModem KEYWORD2 + +# LoRaWAN +getBufferNonces KEYWORD2 +setBufferNonces KEYWORD2 +clearSession KEYWORD2 +getBufferSession KEYWORD2 +setBufferSession KEYWORD2 +beginOTAA KEYWORD2 +beginABP KEYWORD2 +activateOTAA KEYWORD2 +activateABP KEYWORD2 +isActivated KEYWORD2 +sendReceive KEYWORD2 +sendMacCommandReq KEYWORD2 +getMacLinkCheckAns KEYWORD2 +getMacDeviceTimeAns KEYWORD2 +setDatarate KEYWORD2 +setTxPower KEYWORD2 +setRx2Dr KEYWORD2 +setADR KEYWORD2 +setDutyCycle KEYWORD2 +setDwellTime KEYWORD2 +setCSMA KEYWORD2 +setDeviceStatus KEYWORD2 +scheduleTransmission KEYWORD2 +getFCntUp KEYWORD2 +getNFCntDown KEYWORD2 +getAFCntDown KEYWORD2 +resetFCntDown KEYWORD2 +getDevAddr KEYWORD2 +getLastToA KEYWORD2 +dutyCycleInterval KEYWORD2 +timeUntilUplink KEYWORD2 +getMaxPayloadLen KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### + +RADIOLIB_NC LITERAL1 +RADIOLIB_VERSION LITERAL1 +RADIOLIB_PIN_TYPE LITERAL1 + +RADIOLIB_SHAPING_NONE LITERAL1 +RADIOLIB_SHAPING_0_3 LITERAL1 +RADIOLIB_SHAPING_0_5 LITERAL1 +RADIOLIB_SHAPING_0_7 LITERAL1 +RADIOLIB_SHAPING_1_0 LITERAL1 + +RADIOLIB_ENCODING_NRZ LITERAL1 +RADIOLIB_ENCODING_MANCHESTER LITERAL1 +RADIOLIB_ENCODING_WHITENING LITERAL1 + +RADIOLIB_BUILTIN_MODULE LITERAL1 + +RADIOLIB_MORSE_INTER_SYMBOL LITERAL1 +RADIOLIB_MORSE_CHAR_COMPLETE LITERAL1 +RADIOLIB_MORSE_WORD_COMPLETE LITERAL1 + +RADIOLIB_ERR_NONE LITERAL1 +RADIOLIB_ERR_UNKNOWN LITERAL1 + +RADIOLIB_ERR_CHIP_NOT_FOUND LITERAL1 +RADIOLIB_ERR_MEMORY_ALLOCATION_FAILED LITERAL1 +RADIOLIB_ERR_PACKET_TOO_LONG LITERAL1 +RADIOLIB_ERR_TX_TIMEOUT LITERAL1 +RADIOLIB_ERR_RX_TIMEOUT LITERAL1 +RADIOLIB_ERR_CRC_MISMATCH LITERAL1 +RADIOLIB_ERR_INVALID_BANDWIDTH LITERAL1 +RADIOLIB_ERR_INVALID_SPREADING_FACTOR LITERAL1 +RADIOLIB_ERR_INVALID_CODING_RATE LITERAL1 +RADIOLIB_ERR_INVALID_BIT_RANGE LITERAL1 +RADIOLIB_ERR_INVALID_FREQUENCY LITERAL1 +RADIOLIB_ERR_INVALID_OUTPUT_POWER LITERAL1 +RADIOLIB_PREAMBLE_DETECTED LITERAL1 +RADIOLIB_CHANNEL_FREE LITERAL1 +RADIOLIB_ERR_SPI_WRITE_FAILED LITERAL1 +RADIOLIB_ERR_INVALID_CURRENT_LIMIT LITERAL1 +RADIOLIB_ERR_INVALID_PREAMBLE_LENGTH LITERAL1 +RADIOLIB_ERR_INVALID_GAIN LITERAL1 +RADIOLIB_ERR_WRONG_MODEM LITERAL1 +RADIOLIB_ERR_INVALID_NUM_SAMPLES LITERAL1 +RADIOLIB_ERR_INVALID_RSSI_OFFSET LITERAL1 +RADIOLIB_ERR_INVALID_ENCODING LITERAL1 + +RADIOLIB_ERR_INVALID_BIT_RATE LITERAL1 +RADIOLIB_ERR_INVALID_FREQUENCY_DEVIATION LITERAL1 +RADIOLIB_ERR_INVALID_BIT_RATE_BW_RATIO LITERAL1 +RADIOLIB_ERR_INVALID_RX_BANDWIDTH LITERAL1 +RADIOLIB_ERR_INVALID_SYNC_WORD LITERAL1 +RADIOLIB_ERR_INVALID_DATA_SHAPING LITERAL1 +RADIOLIB_ERR_INVALID_MODULATION LITERAL1 +RADIOLIB_ERR_INVALID_OOK_RSSI_PEAK_TYPE LITERAL1 + +RADIOLIB_ERR_INVALID_SYMBOL LITERAL1 +RADIOLIB_ERR_INVALID_MIC_E_TELEMETRY LITERAL1 +RADIOLIB_ERR_INVALID_MIC_E_TELEMETRY_LENGTH LITERAL1 +RADIOLIB_ERR_MIC_E_TELEMETRY_STATUS LITERAL1 + +RADIOLIB_ASCII LITERAL1 +RADIOLIB_ASCII_EXTENDED LITERAL1 +RADIOLIB_ITA2 LITERAL1 +RADIOLIB_ERR_INVALID_RTTY_SHIFT LITERAL1 +RADIOLIB_ERR_UNSUPPORTED_ENCODING LITERAL1 + +RADIOLIB_ERR_INVALID_DATA_RATE LITERAL1 +RADIOLIB_ERR_INVALID_ADDRESS_WIDTH LITERAL1 +RADIOLIB_ERR_INVALID_PIPE_NUMBER LITERAL1 +RADIOLIB_ERR_ACK_NOT_RECEIVED LITERAL1 + +RADIOLIB_ERR_INVALID_NUM_BROAD_ADDRS LITERAL1 + +RADIOLIB_ERR_INVALID_CRC_CONFIGURATION LITERAL1 +RADIOLIB_LORA_DETECTED LITERAL1 +RADIOLIB_ERR_INVALID_TCXO_VOLTAGE LITERAL1 +RADIOLIB_ERR_INVALID_MODULATION_PARAMETERS LITERAL1 +RADIOLIB_ERR_SPI_CMD_TIMEOUT LITERAL1 +RADIOLIB_ERR_SPI_CMD_INVALID LITERAL1 +RADIOLIB_ERR_SPI_CMD_FAILED LITERAL1 +RADIOLIB_ERR_INVALID_SLEEP_PERIOD LITERAL1 +RADIOLIB_ERR_INVALID_RX_PERIOD LITERAL1 + +RADIOLIB_ERR_INVALID_CALLSIGN LITERAL1 +RADIOLIB_ERR_INVALID_NUM_REPEATERS LITERAL1 +RADIOLIB_ERR_INVALID_REPEATER_CALLSIGN LITERAL1 + +RADIOLIB_ERR_RANGING_TIMEOUT LITERAL1 + +RADIOLIB_ERR_INVALID_PAYLOAD LITERAL1 +RADIOLIB_ERR_ADDRESS_NOT_FOUND LITERAL1 +RADIOLIB_ERR_INVALID_FUNCTION LITERAL1 + +RADIOLIB_ERR_NETWORK_NOT_JOINED LITERAL1 +RADIOLIB_ERR_DOWNLINK_MALFORMED LITERAL1 +RADIOLIB_ERR_INVALID_REVISION LITERAL1 +RADIOLIB_ERR_INVALID_PORT LITERAL1 +RADIOLIB_ERR_NO_RX_WINDOW LITERAL1 +RADIOLIB_ERR_NO_CHANNEL_AVAILABLE LITERAL1 +RADIOLIB_ERR_INVALID_CID LITERAL1 +RADIOLIB_ERR_UPLINK_UNAVAILABLE LITERAL1 +RADIOLIB_ERR_COMMAND_QUEUE_FULL LITERAL1 +RADIOLIB_ERR_COMMAND_QUEUE_ITEM_NOT_FOUND LITERAL1 +RADIOLIB_ERR_JOIN_NONCE_INVALID LITERAL1 +RADIOLIB_ERR_N_FCNT_DOWN_INVALID LITERAL1 +RADIOLIB_ERR_A_FCNT_DOWN_INVALID LITERAL1 +RADIOLIB_ERR_DWELL_TIME_EXCEEDED LITERAL1 +RADIOLIB_ERR_CHECKSUM_MISMATCH LITERAL1 +RADIOLIB_ERR_NO_JOIN_ACCEPT LITERAL1 +RADIOLIB_LORAWAN_SESSION_RESTORED LITERAL1 +RADIOLIB_LORAWAN_NEW_SESSION LITERAL1 +RADIOLIB_ERR_NONCES_DISCARDED LITERAL1 +RADIOLIB_ERR_SESSION_DISCARDED LITERAL1 +RADIOLIB_ERR_INVALID_MODE LITERAL1 + +RADIOLIB_ERR_INVALID_WIFI_TYPE LITERAL1 +RADIOLIB_ERR_GNSS_SUBFRAME_NOT_AVAILABLE LITERAL1 +RADIOLIB_GET_GNSS_DEMOD_ERROR LITERAL1 +RADIOLIB_GET_GNSS_SOLVER_ERROR LITERAL1 +RADIOLIB_LR11X0_GNSS_CONSTELLATION_GPS LITERAL1 +RADIOLIB_LR11X0_GNSS_CONSTELLATION_BEIDOU LITERAL1 +RADIOLIB_LR11X0_GNSS_ALMANAC_STATUS_UP_TO_DATE LITERAL1 + +RADIOLIB_LR1110_FIRMWARE_IN_RAM LITERAL1 +RADIOLIB_LR11X0_FIRMWARE_IMAGE_SIZE LITERAL1 +RADIOLIB_LR1110_FIRMWARE_0303 LITERAL1 +RADIOLIB_LR1110_FIRMWARE_0304 LITERAL1 +RADIOLIB_LR1110_FIRMWARE_0305 LITERAL1 +RADIOLIB_LR1110_FIRMWARE_0306 LITERAL1 +RADIOLIB_LR1110_FIRMWARE_0307 LITERAL1 +RADIOLIB_LR1110_FIRMWARE_0401 LITERAL1 +RADIOLIB_LR1120_FIRMWARE_0101 LITERAL1 +RADIOLIB_LR1120_FIRMWARE_0102 LITERAL1 +RADIOLIB_LR1120_FIRMWARE_0201 LITERAL1 +RADIOLIB_LR1121_FIRMWARE_0102 LITERAL1 +RADIOLIB_LR1121_FIRMWARE_0103 LITERAL1 diff --git a/software/firmware/source/libraries/RadioLib/library.json b/software/firmware/source/libraries/RadioLib/library.json new file mode 100644 index 000000000..9c27984d1 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/library.json @@ -0,0 +1,26 @@ +{ + "name": "RadioLib", + "version": "7.1.0", + "description": "Universal wireless communication library. User-friendly library for sub-GHz radio modules (SX1278, RF69, CC1101, SX1268, and many others), as well as ham radio digital modes (RTTY, SSTV, AX.25 etc.) and other protocols (Pagers, LoRaWAN).", + "keywords": "radio, communication, morse, cc1101, aprs, sx1276, sx1278, sx1272, rtty, ax25, afsk, nrf24, rf69, sx1231, rfm96, rfm98, sstv, sx1280, sx1281, sx1282, sx1261, sx1262, sx1268, si4432, rfm22, llcc68, pager, pocsag, lorawan, lr1110, lr1120, lr1121", + "homepage": "https://github.com/jgromes/RadioLib", + "repository": + { + "type": "git", + "url": "https://github.com/jgromes/RadioLib.git" + }, + "authors": + { + "name": "Jan Gromeš", + "email": "gromes.jan@gmail.com", + "maintainer": true + }, + "license": "MIT", + "frameworks": "*", + "platforms": "*", + "headers": "RadioLib.h", + "build": + { + "libLDFMode": "chain+" + } +} \ No newline at end of file diff --git a/software/firmware/source/libraries/RadioLib/library.properties b/software/firmware/source/libraries/RadioLib/library.properties new file mode 100644 index 000000000..26e9ffef4 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/library.properties @@ -0,0 +1,10 @@ +name=RadioLib +version=7.1.0 +author=Jan Gromes +maintainer=Jan Gromes +sentence=Universal wireless communication library +paragraph=User-friendly library for sub-GHz radio modules (SX1278, RF69, CC1101, SX1268, LR1110 and many others), as well as ham radio digital modes (RTTY, SSTV, AX.25 etc.) and other protocols (Pagers, LoRaWAN). +category=Communication +url=https://github.com/jgromes/RadioLib +architectures=* +includes=RadioLib.h diff --git a/software/firmware/source/libraries/RadioLib/license.txt b/software/firmware/source/libraries/RadioLib/license.txt new file mode 100644 index 000000000..201215f25 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/license.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Jan Gromeš + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/software/firmware/source/libraries/RadioLib/src/BuildOpt.h b/software/firmware/source/libraries/RadioLib/src/BuildOpt.h new file mode 100644 index 000000000..e2c26adac --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/BuildOpt.h @@ -0,0 +1,587 @@ +#if !defined(_RADIOLIB_BUILD_OPTIONS_H) +#define _RADIOLIB_BUILD_OPTIONS_H + +#include "TypeDef.h" + +/* RadioLib build configuration options */ + +/* + * Debug output enable. + * Warning: Debug output will slow down the whole system significantly. + * Also, it will result in larger compiled binary. + * Levels: basic - only main info + * protocol - mainly LoRaWAN stuff, but other protocols as well + * SPI - full transcript of all SPI communication + */ +#if !defined(RADIOLIB_DEBUG_BASIC) + #define RADIOLIB_DEBUG_BASIC (0) +#endif +#if !defined(RADIOLIB_DEBUG_PROTOCOL) + #define RADIOLIB_DEBUG_PROTOCOL (0) +#endif +#if !defined(RADIOLIB_DEBUG_SPI) + #define RADIOLIB_DEBUG_SPI (0) +#endif +#if !defined(RADIOLIB_VERBOSE_ASSERT) + #define RADIOLIB_VERBOSE_ASSERT (0) +#endif + +// set which output port should be used for debug output +// may be Serial port (on Arduino) or file like stdout or stderr (on generic platforms) +#if !defined(RADIOLIB_DEBUG_PORT) + #define RADIOLIB_DEBUG_PORT Serial +#endif + +/* + * Comment to disable "paranoid" SPI mode, or set RADIOLIB_SPI_PARANOID to 0 + * Every write to an SPI register using SPI set function will be verified by a subsequent read operation. + * This improves reliability, but slightly slows down communication. + * Note: Enabled by default. + */ +#if !defined(RADIOLIB_SPI_PARANOID) + #define RADIOLIB_SPI_PARANOID (1) +#endif + +/* + * Comment to disable parameter range checking + * RadioLib will check provided parameters (such as frequency) against limits determined by the device manufacturer. + * It is highly advised to keep this macro defined, removing it will allow invalid values to be set, + * possibly leading to bricked module and/or program crashing. + * Note: Enabled by default. + */ +#if !defined(RADIOLIB_CHECK_PARAMS) + #define RADIOLIB_CHECK_PARAMS (1) +#endif + +/* + * SX127x errata fix enable + * Warning: SX127x errata fix has been reported to cause issues with LoRa bandwidths lower than 62.5 kHz. + * It should only be enabled if you really are observing some errata-related issue. + * Note: Disabled by default. + */ +#if !defined(RADIOLIB_FIX_ERRATA_SX127X) + #define RADIOLIB_FIX_ERRATA_SX127X (0) +#endif + +/* + * God mode enable - all methods and member variables in all classes will be made public, thus making them accessible from Arduino code. + * Warning: Come on, it's called GOD mode - obviously only use this if you know what you're doing. + * Failure to heed the above warning may result in bricked module. + */ +#if !defined(RADIOLIB_GODMODE) + #define RADIOLIB_GODMODE (0) +#endif + +/* + * Low-level hardware access enable + * This will make some hardware methods like SPI get/set accessible from the user sketch - think of it as "god mode lite" + * Warning: RadioLib won't stop you from writing invalid stuff into your device, so it's quite easy to brick your module with this. + */ +#if !defined(RADIOLIB_LOW_LEVEL) + #define RADIOLIB_LOW_LEVEL (0) +#endif + +/* + * Enable interrupt-based timing control + * For details, see https://github.com/jgromes/RadioLib/wiki/Interrupt-Based-Timing + */ +#if !defined(RADIOLIB_INTERRUPT_TIMING) + #define RADIOLIB_INTERRUPT_TIMING (0) +#endif + +/* + * Enable static-only memory management: no dynamic allocation will be performed. + * Warning: Large static arrays will be created in some methods. It is not advised to send large packets in this mode. + */ +#if !defined(RADIOLIB_STATIC_ONLY) + #define RADIOLIB_STATIC_ONLY (0) +#endif + +// set the size of static arrays to use +#if !defined(RADIOLIB_STATIC_ARRAY_SIZE) + #define RADIOLIB_STATIC_ARRAY_SIZE (256) +#endif + +/* + * Uncomment on boards whose clock runs too slow or too fast + * Set the value according to the following scheme: + * Enable timestamps on your terminal + * Print something to terminal, wait 1000 milliseconds, print something again + * If the difference is e.g. 1014 milliseconds between the prints, set this value to 14 + * Or, for more accuracy, wait for 100,000 milliseconds and divide the total drift by 100 + */ +#if !defined(RADIOLIB_CLOCK_DRIFT_MS) + //#define RADIOLIB_CLOCK_DRIFT_MS (0) +#endif + +#if ARDUINO >= 100 + // Arduino build + #include "Arduino.h" + #define RADIOLIB_BUILD_ARDUINO +#else + // generic build + #include + #define RADIOLIB_BUILD_GENERIC +#endif + +#if defined(RADIOLIB_BUILD_ARDUINO) +/* + * Platform-specific configuration. + * + * RADIOLIB_PLATFORM - platform name, used in debugging to quickly check the correct platform is detected. + * RADIOLIB_NC - alias for unused pin, usually the largest possible value of uint32_t. + * RADIOLIB_DEFAULT_SPI - default SPIClass instance to use. + * RADIOLIB_NONVOLATILE - macro to place variable into program storage (usually Flash). + * RADIOLIB_NONVOLATILE_READ_BYTE - function/macro to read variables saved in program storage (usually Flash). + * RADIOLIB_TYPE_ALIAS - construct to create an alias for a type, usually vai the `using` keyword. + * RADIOLIB_TONE_UNSUPPORTED - some platforms do not have tone()/noTone(), which is required for AFSK. + * + * In addition, some platforms may require RadioLib to disable specific drivers (such as ESP8266). + * + * Users may also specify their own configuration by uncommenting the RADIOLIB_CUSTOM_ARDUINO, + * and then specifying all platform parameters in the section below. This will override automatic + * platform detection. + */ + + // uncomment to enable custom platform definition + //#define RADIOLIB_CUSTOM_ARDUINO + +#if defined(RADIOLIB_CUSTOM_ARDUINO) + // name for your platform + #define RADIOLIB_PLATFORM "Custom" + + // the following must be defined if the Arduino core does not support tone or yield function + //#define RADIOLIB_TONE_UNSUPPORTED + //#define RADIOLIB_YIELD_UNSUPPORTED + + // in addition, the following macros may be defined if the Arduino core differs from the defaults + #define RADIOLIB_NC (0xFFFFFFFF) + #define RADIOLIB_DEFAULT_SPI SPI + #define RADIOLIB_DEFAULT_SPI_SETTINGS SPISettings(2000000, MSBFIRST, SPI_MODE0) + #define RADIOLIB_NONVOLATILE PROGMEM + #define RADIOLIB_NONVOLATILE_READ_BYTE(addr) pgm_read_byte(addr) + #define RADIOLIB_NONVOLATILE_READ_DWORD(addr) pgm_read_dword(addr) + #define RADIOLIB_TYPE_ALIAS(type, alias) using alias = type; + + // you might also have to define these if the Arduino core has some uncommon pin mode/status types + #define RADIOLIB_ARDUINOHAL_PIN_MODE_CAST + #define RADIOLIB_ARDUINOHAL_PIN_STATUS_CAST + #define RADIOLIB_ARDUINOHAL_INTERRUPT_MODE_CAST + + // some of RadioLib drivers may be excluded, to prevent collisions with platforms (or to speed up build process) + // the following is a complete list of all possible exclusion macros, uncomment any of them to disable that driver + // NOTE: Some of the exclusion macros are dependent on each other. For example, it is not possible to exclude RF69 + // while keeping SX1231 (because RF69 is the base class for SX1231). The dependency is always uni-directional, + // so excluding SX1231 and keeping RF69 is valid. + //#define RADIOLIB_EXCLUDE_CC1101 (1) + //#define RADIOLIB_EXCLUDE_NRF24 (1) + //#define RADIOLIB_EXCLUDE_RF69 (1) + //#define RADIOLIB_EXCLUDE_SX1231 (1) // dependent on RADIOLIB_EXCLUDE_RF69 + //#define RADIOLIB_EXCLUDE_SI443X (1) + //#define RADIOLIB_EXCLUDE_RFM2X (1) // dependent on RADIOLIB_EXCLUDE_SI443X + //#define RADIOLIB_EXCLUDE_SX127X (1) + //#define RADIOLIB_EXCLUDE_SX126X (1) + //#define RADIOLIB_EXCLUDE_STM32WLX (1) // dependent on RADIOLIB_EXCLUDE_SX126X + //#define RADIOLIB_EXCLUDE_SX128X (1) + //#define RADIOLIB_EXCLUDE_AFSK (1) + //#define RADIOLIB_EXCLUDE_AX25 (1) + //#define RADIOLIB_EXCLUDE_HELLSCHREIBER (1) + //#define RADIOLIB_EXCLUDE_MORSE (1) + //#define RADIOLIB_EXCLUDE_RTTY (1) + //#define RADIOLIB_EXCLUDE_SSTV (1) + //#define RADIOLIB_EXCLUDE_DIRECT_RECEIVE (1) + +#elif defined(__AVR__) && !(defined(ARDUINO_AVR_UNO_WIFI_REV2) || defined(ARDUINO_AVR_NANO_EVERY) || defined(ARDUINO_ARCH_MEGAAVR)) + // Arduino AVR boards (except for megaAVR) - Uno, Mega etc. + #define RADIOLIB_PLATFORM "Arduino AVR" + + #if !(defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(__AVR_ATmega1284__) || defined(__AVR_ATmega1284P__)) + #define RADIOLIB_LOWEND_PLATFORM + #endif + +#elif defined(ESP8266) + // ESP8266 boards + #define RADIOLIB_PLATFORM "ESP8266" + +#elif defined(ESP32) || defined(ARDUINO_ARCH_ESP32) + #define RADIOLIB_ESP32 + + // ESP32 boards + #define RADIOLIB_PLATFORM "ESP32" + + // ESP32 doesn't support tone(), but it can be emulated via LED control peripheral + #define RADIOLIB_TONE_UNSUPPORTED + #define RADIOLIB_TONE_ESP32_CHANNEL (1) + +#elif defined(ARDUINO_ARCH_STM32) + // official STM32 Arduino core (https://github.com/stm32duino/Arduino_Core_STM32) + #define RADIOLIB_PLATFORM "Arduino STM32 (official)" + +#elif defined(SAMD_SERIES) + // Adafruit SAMD boards (M0 and M4) + #define RADIOLIB_PLATFORM "Adafruit SAMD" + +#elif defined(ARDUINO_ARCH_SAMD) + // Arduino SAMD (Zero, MKR, etc.) + #define RADIOLIB_PLATFORM "Arduino SAMD" + #define RADIOLIB_ARDUINOHAL_PIN_MODE_CAST (PinMode) + #define RADIOLIB_ARDUINOHAL_PIN_STATUS_CAST (PinStatus) + #define RADIOLIB_ARDUINOHAL_INTERRUPT_MODE_CAST (PinStatus) + +#elif defined(__SAM3X8E__) + // Arduino Due + #define RADIOLIB_PLATFORM "Arduino Due" + #define RADIOLIB_TONE_UNSUPPORTED + +#elif (defined(NRF52832_XXAA) || defined(NRF52840_XXAA)) && !defined(ARDUINO_ARDUINO_NANO33BLE) + // Adafruit nRF52 boards + #define RADIOLIB_PLATFORM "Adafruit nRF52" + +#elif defined(ARDUINO_ARC32_TOOLS) + // Intel Curie + #define RADIOLIB_PLATFORM "Intel Curie" + +#elif defined(ARDUINO_AVR_UNO_WIFI_REV2) || defined(ARDUINO_AVR_NANO_EVERY) || defined(PORTDUINO) + // Arduino megaAVR boards - Uno Wifi Rev.2, Nano Every + #define RADIOLIB_PLATFORM "Arduino megaAVR" + #define RADIOLIB_ARDUINOHAL_PIN_MODE_CAST (PinMode) + #define RADIOLIB_ARDUINOHAL_PIN_STATUS_CAST (PinStatus) + #define RADIOLIB_ARDUINOHAL_INTERRUPT_MODE_CAST (PinStatus) + +#elif defined(ARDUINO_ARCH_APOLLO3) + // Sparkfun Apollo3 boards + #define RADIOLIB_PLATFORM "Sparkfun Apollo3" + #define RADIOLIB_ARDUINOHAL_PIN_MODE_CAST (Arduino_PinMode) + #define RADIOLIB_ARDUINOHAL_PIN_STATUS_CAST (PinStatus) + #define RADIOLIB_ARDUINOHAL_INTERRUPT_MODE_CAST (PinStatus) + +#elif defined(ARDUINO_ARDUINO_NANO33BLE) + // Arduino Nano 33 BLE + #define RADIOLIB_PLATFORM "Arduino Nano 33 BLE" + #define RADIOLIB_ARDUINOHAL_PIN_MODE_CAST (PinMode) + #define RADIOLIB_ARDUINOHAL_PIN_STATUS_CAST (PinStatus) + #define RADIOLIB_ARDUINOHAL_INTERRUPT_MODE_CAST (PinStatus) + + // Arduino mbed OS boards have a really bad tone implementation which will crash after a couple seconds + #define RADIOLIB_TONE_UNSUPPORTED + #define RADIOLIB_MBED_TONE_OVERRIDE + +#elif defined(ARDUINO_PORTENTA_H7_M7) || defined(ARDUINO_PORTENTA_H7_M4) + // Arduino Portenta H7 + #define RADIOLIB_PLATFORM "Portenta H7" + #define RADIOLIB_ARDUINOHAL_PIN_MODE_CAST (PinMode) + #define RADIOLIB_ARDUINOHAL_PIN_STATUS_CAST (PinStatus) + #define RADIOLIB_ARDUINOHAL_INTERRUPT_MODE_CAST (PinStatus) + + // Arduino mbed OS boards have a really bad tone implementation which will crash after a couple seconds + #define RADIOLIB_TONE_UNSUPPORTED + #define RADIOLIB_MBED_TONE_OVERRIDE + +#elif defined(__STM32F4__) || defined(__STM32F1__) + // Arduino STM32 core by Roger Clark (https://github.com/rogerclarkmelbourne/Arduino_STM32) + #define RADIOLIB_PLATFORM "STM32duino (unofficial)" + #define RADIOLIB_ARDUINOHAL_PIN_MODE_CAST (WiringPinMode) + #define RADIOLIB_ARDUINOHAL_INTERRUPT_MODE_CAST (ExtIntTriggerMode) + +#elif defined(ARDUINO_ARCH_MEGAAVR) + // MegaCoreX by MCUdude (https://github.com/MCUdude/MegaCoreX) + #define RADIOLIB_PLATFORM "MegaCoreX" + +#elif defined(ARDUINO_ARCH_MBED_RP2040) + // Raspberry Pi Pico (official mbed core) + #define RADIOLIB_PLATFORM "Raspberry Pi Pico" + #define RADIOLIB_ARDUINOHAL_PIN_MODE_CAST (PinMode) + #define RADIOLIB_ARDUINOHAL_PIN_STATUS_CAST (PinStatus) + #define RADIOLIB_ARDUINOHAL_INTERRUPT_MODE_CAST (PinStatus) + + // Arduino mbed OS boards have a really bad tone implementation which will crash after a couple seconds + #define RADIOLIB_TONE_UNSUPPORTED + #define RADIOLIB_MBED_TONE_OVERRIDE + +#elif defined(ARDUINO_ARCH_RP2040) + // Raspberry Pi Pico (unofficial core) + #define RADIOLIB_PLATFORM "Raspberry Pi Pico (unofficial)" + #define RADIOLIB_ARDUINOHAL_PIN_MODE_CAST (PinMode) + #define RADIOLIB_ARDUINOHAL_PIN_STATUS_CAST (PinStatus) + #define RADIOLIB_ARDUINOHAL_INTERRUPT_MODE_CAST (PinStatus) + +#elif defined(__ASR6501__) || defined(ARDUINO_ARCH_ASR650X) || defined(DARDUINO_ARCH_ASR6601) + // CubeCell + #define RADIOLIB_PLATFORM "CubeCell" + #define RADIOLIB_DEFAULT_SPI_SETTINGS SPISettings(1000000, MSBFIRST, SPI_MODE0) // see issue #709 + #define RADIOLIB_ARDUINOHAL_PIN_MODE_CAST (PINMODE) + #define RADIOLIB_ARDUINOHAL_INTERRUPT_MODE_CAST (IrqModes) + + // CubeCell doesn't seem to define nullptr, let's do something like that now + #define nullptr NULL + + // ... and also defines pinMode() as a macro, which is by far the stupidest thing I have seen on Arduino + #undef pinMode + + // ... and uses an outdated GCC which does not support type aliases + #define RADIOLIB_TYPE_ALIAS(type, alias) typedef class type alias; + + // ... and it also has no tone(). This platform was designed by an idiot. + #define RADIOLIB_TONE_UNSUPPORTED + + // ... AND as the (hopefully) final nail in the coffin, IT F*CKING DEFINES YIELD() AS A MACRO THAT DOES NOTHING!!! + #define RADIOLIB_YIELD_UNSUPPORTED + #if defined(yield) + #undef yield + inline void yield() { }; + #endif + +#elif defined(RASPI) + // RaspiDuino framework (https://github.com/me-no-dev/RasPiArduino) + #define RADIOLIB_PLATFORM "RasPiArduino" + + // let's start off easy - no tone on this platform, that can happen + #define RADIOLIB_TONE_UNSUPPORTED + + // hmm, no yield either - weird on something like Raspberry PI, but sure, we can handle it + #define RADIOLIB_YIELD_UNSUPPORTED + + // aight, getting to the juicy stuff - PGM_P seems missing, that's the first time + #define PGM_P const char * + + // ... and for the grand finale, we have millis() and micros() DEFINED AS MACROS! + #if defined(millis) + #undef millis + inline RadioLibTime_t millis() { return((RadioLibTime_t)(STCV / 1000)); }; + #endif + + #if defined(micros) + #undef micros + inline RadioLibTime_t micros() { return((RadioLibTime_t)(STCV)); }; + #endif + +#elif defined(TEENSYDUINO) + // Teensy + #define RADIOLIB_PLATFORM "Teensy" + +#elif defined(ARDUINO_ARCH_RENESAS) + // Arduino Renesas (UNO R4) + #define RADIOLIB_PLATFORM "Arduino Renesas (UNO R4)" + #define RADIOLIB_ARDUINOHAL_PIN_MODE_CAST (PinMode) + #define RADIOLIB_ARDUINOHAL_PIN_STATUS_CAST (PinStatus) + #define RADIOLIB_ARDUINOHAL_INTERRUPT_MODE_CAST (PinStatus) + +#else + // other Arduino platforms not covered by the above list - this may or may not work + #define RADIOLIB_PLATFORM "Unknown Arduino" + #define RADIOLIB_UNKNOWN_PLATFORM + +#endif + + // set the default values for all macros + // these will be applied if they were not defined above + #if !defined(RADIOLIB_NC) + #define RADIOLIB_NC (0xFFFFFFFF) + #endif + + #if !defined(RADIOLIB_DEFAULT_SPI) + #define RADIOLIB_DEFAULT_SPI SPI + #endif + + #if !defined(RADIOLIB_DEFAULT_SPI_SETTINGS) + #define RADIOLIB_DEFAULT_SPI_SETTINGS SPISettings(2000000, MSBFIRST, SPI_MODE0) + #endif + + #if !defined(RADIOLIB_NONVOLATILE) + #define RADIOLIB_NONVOLATILE PROGMEM + #endif + + #if !defined(RADIOLIB_NONVOLATILE_PTR) + #define RADIOLIB_NONVOLATILE_PTR PGM_P + #endif + + #if !defined(RADIOLIB_NONVOLATILE_READ_BYTE) + #define RADIOLIB_NONVOLATILE_READ_BYTE(addr) pgm_read_byte(addr) + #endif + + #if !defined(RADIOLIB_NONVOLATILE_READ_DWORD) + #define RADIOLIB_NONVOLATILE_READ_DWORD(addr) pgm_read_dword(addr) + #endif + + #if !defined(RADIOLIB_TYPE_ALIAS) + #define RADIOLIB_TYPE_ALIAS(type, alias) using alias = type; + #endif + + #if !defined(RADIOLIB_ARDUINOHAL_PIN_MODE_CAST) + #define RADIOLIB_ARDUINOHAL_PIN_MODE_CAST + #endif + + #if !defined(RADIOLIB_ARDUINOHAL_PIN_STATUS_CAST) + #define RADIOLIB_ARDUINOHAL_PIN_STATUS_CAST + #endif + + #if !defined(RADIOLIB_ARDUINOHAL_INTERRUPT_MODE_CAST) + #define RADIOLIB_ARDUINOHAL_INTERRUPT_MODE_CAST + #endif + +#else + // generic non-Arduino platform + #define RADIOLIB_PLATFORM "Generic" + + #define RADIOLIB_NC (0xFFFFFFFF) + #define RADIOLIB_NONVOLATILE + #define RADIOLIB_NONVOLATILE_READ_BYTE(addr) (*((uint8_t *)(void *)(addr))) + #define RADIOLIB_NONVOLATILE_READ_DWORD(addr) (*((uint32_t *)(void *)(addr))) + #define RADIOLIB_TYPE_ALIAS(type, alias) using alias = type; + + #if !defined(RADIOLIB_DEBUG_PORT) + #define RADIOLIB_DEBUG_PORT stdout + #endif + + #define DEC 10 + #define HEX 16 + #define OCT 8 + #define BIN 2 + + #include + +#endif + +// This only compiles on STM32 boards with SUBGHZ module, but also +// include when generating docs +#if (!defined(ARDUINO_ARCH_STM32) || !defined(SUBGHZSPI_BASE)) && !defined(DOXYGEN) + #define RADIOLIB_EXCLUDE_STM32WLX (1) +#endif + +// if verbose assert is enabled, enable basic debug too +#if RADIOLIB_VERBOSE_ASSERT + #define RADIOLIB_DEBUG (1) +#endif + +// set the global debug mode flag +#if RADIOLIB_DEBUG_BASIC || RADIOLIB_DEBUG_PROTOCOL || RADIOLIB_DEBUG_SPI + #define RADIOLIB_DEBUG (1) +#else + #define RADIOLIB_DEBUG (0) +#endif + +#if RADIOLIB_DEBUG + #if defined(RADIOLIB_BUILD_ARDUINO) + #define RADIOLIB_DEBUG_PRINT(...) rlb_printf(__VA_ARGS__) + #define RADIOLIB_DEBUG_PRINTLN(M, ...) rlb_printf(M "\n", ##__VA_ARGS__) + #define RADIOLIB_DEBUG_PRINT_LVL(LEVEL, M, ...) rlb_printf(LEVEL "" M, ##__VA_ARGS__) + #define RADIOLIB_DEBUG_PRINTLN_LVL(LEVEL, M, ...) rlb_printf(LEVEL "" M "\n", ##__VA_ARGS__) + + // some platforms do not support printf("%f"), so it has to be done this way + #define RADIOLIB_DEBUG_PRINT_FLOAT(LEVEL, VAL, DECIMALS) RADIOLIB_DEBUG_PRINT(LEVEL); RADIOLIB_DEBUG_PORT.print(VAL, DECIMALS) + #else + #if !defined(RADIOLIB_DEBUG_PRINT) + #define RADIOLIB_DEBUG_PRINT(...) fprintf(RADIOLIB_DEBUG_PORT, __VA_ARGS__) + #define RADIOLIB_DEBUG_PRINT_LVL(LEVEL, M, ...) fprintf(RADIOLIB_DEBUG_PORT, LEVEL "" M, ##__VA_ARGS__) + #endif + #if !defined(RADIOLIB_DEBUG_PRINTLN) + #define RADIOLIB_DEBUG_PRINTLN(M, ...) fprintf(RADIOLIB_DEBUG_PORT, M "\n", ##__VA_ARGS__) + #define RADIOLIB_DEBUG_PRINTLN_LVL(LEVEL, M, ...) fprintf(RADIOLIB_DEBUG_PORT, LEVEL "" M "\n", ##__VA_ARGS__) + #endif + #define RADIOLIB_DEBUG_PRINT_FLOAT(LEVEL, VAL, DECIMALS) RADIOLIB_DEBUG_PRINT(LEVEL "%.3f", VAL) + #endif + + #define RADIOLIB_DEBUG_HEXDUMP(LEVEL, ...) rlb_hexdump(LEVEL, __VA_ARGS__) +#else + #define RADIOLIB_DEBUG_PRINT(...) {} + #define RADIOLIB_DEBUG_PRINTLN(...) {} + #define RADIOLIB_DEBUG_PRINT_FLOAT(VAL, DECIMALS) {} + #define RADIOLIB_DEBUG_HEXDUMP(...) {} +#endif + +#if RADIOLIB_DEBUG_BASIC + #define RADIOLIB_DEBUG_BASIC_PRINT(...) RADIOLIB_DEBUG_PRINT_LVL("RLB_DBG: ", __VA_ARGS__) + #define RADIOLIB_DEBUG_BASIC_PRINT_NOTAG(...) RADIOLIB_DEBUG_PRINT_LVL("", __VA_ARGS__) + #define RADIOLIB_DEBUG_BASIC_PRINTLN(...) RADIOLIB_DEBUG_PRINTLN_LVL("RLB_DBG: ", __VA_ARGS__) + #define RADIOLIB_DEBUG_BASIC_PRINT_FLOAT(...) RADIOLIB_DEBUG_PRINT_FLOAT("RLB_DBG: ", __VA_ARGS__); + #define RADIOLIB_DEBUG_BASIC_HEXDUMP(...) RADIOLIB_DEBUG_HEXDUMP("RLB_DBG: ", __VA_ARGS__); +#else + #define RADIOLIB_DEBUG_BASIC_PRINT(...) {} + #define RADIOLIB_DEBUG_BASIC_PRINT_NOTAG(...) {} + #define RADIOLIB_DEBUG_BASIC_PRINTLN(...) {} + #define RADIOLIB_DEBUG_BASIC_PRINT_FLOAT(...) {} + #define RADIOLIB_DEBUG_BASIC_HEXDUMP(...) {} +#endif + +#if RADIOLIB_DEBUG_PROTOCOL + #define RADIOLIB_DEBUG_PROTOCOL_PRINT(...) RADIOLIB_DEBUG_PRINT_LVL("RLB_PRO: ", __VA_ARGS__) + #define RADIOLIB_DEBUG_PROTOCOL_PRINTLN(...) RADIOLIB_DEBUG_PRINTLN_LVL("RLB_PRO: ", __VA_ARGS__) + #define RADIOLIB_DEBUG_PROTOCOL_PRINT_FLOAT(...) RADIOLIB_DEBUG_PRINT_FLOAT("RLB_PRO: ", __VA_ARGS__); + #define RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(...) RADIOLIB_DEBUG_HEXDUMP("RLB_PRO: ", __VA_ARGS__); +#else + #define RADIOLIB_DEBUG_PROTOCOL_PRINT(...) {} + #define RADIOLIB_DEBUG_PROTOCOL_PRINTLN(...) {} + #define RADIOLIB_DEBUG_PROTOCOL_PRINT_FLOAT(...) {} + #define RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(...) {} +#endif + +#if RADIOLIB_DEBUG_SPI + #define RADIOLIB_DEBUG_SPI_PRINT(...) RADIOLIB_DEBUG_PRINT_LVL("RLB_SPI: ", __VA_ARGS__) + #define RADIOLIB_DEBUG_SPI_PRINT_NOTAG(...) RADIOLIB_DEBUG_PRINT_LVL("", __VA_ARGS__) + #define RADIOLIB_DEBUG_SPI_PRINTLN(...) RADIOLIB_DEBUG_PRINTLN_LVL("RLB_SPI: ", __VA_ARGS__) + #define RADIOLIB_DEBUG_SPI_PRINTLN_NOTAG(...) RADIOLIB_DEBUG_PRINTLN_LVL("", __VA_ARGS__) + #define RADIOLIB_DEBUG_SPI_PRINT_FLOAT(...) RADIOLIB_DEBUG_PRINT_FLOAT("RLB_SPI: ", __VA_ARGS__); + #define RADIOLIB_DEBUG_SPI_HEXDUMP(...) RADIOLIB_DEBUG_HEXDUMP("RLB_SPI: ", __VA_ARGS__); +#else + #define RADIOLIB_DEBUG_SPI_PRINT(...) {} + #define RADIOLIB_DEBUG_SPI_PRINT_NOTAG(...) {} + #define RADIOLIB_DEBUG_SPI_PRINTLN(...) {} + #define RADIOLIB_DEBUG_SPI_PRINTLN_NOTAG(...) {} + #define RADIOLIB_DEBUG_SPI_PRINT_FLOAT(...) {} + #define RADIOLIB_DEBUG_SPI_HEXDUMP(...) {} +#endif + +// debug info strings +#define RADIOLIB_VALUE_TO_STRING(x) #x +#define RADIOLIB_VALUE(x) RADIOLIB_VALUE_TO_STRING(x) + +#define RADIOLIB_INFO "\nRadioLib Info\nVersion: \"" \ + RADIOLIB_VALUE(RADIOLIB_VERSION_MAJOR) "." \ + RADIOLIB_VALUE(RADIOLIB_VERSION_MINOR) "." \ + RADIOLIB_VALUE(RADIOLIB_VERSION_PATCH) "." \ + RADIOLIB_VALUE(RADIOLIB_VERSION_EXTRA) "\"\n" \ + "Platform: " RADIOLIB_VALUE(RADIOLIB_PLATFORM) "\n" \ + "Compiled: " RADIOLIB_VALUE(__DATE__) " " RADIOLIB_VALUE(__TIME__) + +/*! + \brief A simple assert macro, will return on error. + If RADIOLIB_VERBOSE_ASSERT is enabled, the macro will also print out file and line number trace, + at a significant program storage cost. +*/ +#if RADIOLIB_VERBOSE_ASSERT +#define RADIOLIB_ASSERT(STATEVAR) { if((STATEVAR) != RADIOLIB_ERR_NONE) { RADIOLIB_DEBUG_BASIC_PRINTLN("%d at %s:%d", STATEVAR, __FILE__, __LINE__); return(STATEVAR); } } +#define RADIOLIB_ASSERT_PTR(PTR) { if((PTR) == NULL) { RADIOLIB_DEBUG_BASIC_PRINTLN("NULL at %s:%d", __FILE__, __LINE__); return(RADIOLIB_ERR_MEMORY_ALLOCATION_FAILED); } } +#else +#define RADIOLIB_ASSERT(STATEVAR) { if((STATEVAR) != RADIOLIB_ERR_NONE) { return(STATEVAR); } } +#define RADIOLIB_ASSERT_PTR(PTR) { if((PTR) == NULL) { return(RADIOLIB_ERR_MEMORY_ALLOCATION_FAILED); } } +#endif + +/*! + \brief Macro to check variable is within constraints - this is commonly used to check parameter ranges. Requires RADIOLIB_CHECK_RANGE to be enabled +*/ +#if RADIOLIB_CHECK_PARAMS + #define RADIOLIB_CHECK_RANGE(VAR, MIN, MAX, ERR) { if(!(((VAR) >= (MIN)) && ((VAR) <= (MAX)))) { return(ERR); } } +#else + #define RADIOLIB_CHECK_RANGE(VAR, MIN, MAX, ERR) {} +#endif + +#if RADIOLIB_FIX_ERRATA_SX127X + #define RADIOLIB_ERRATA_SX127X(...) { errataFix(__VA_ARGS__); } +#else + #define RADIOLIB_ERRATA_SX127X(...) {} +#endif + +// these macros are usually defined by Arduino, but some platforms undef them, so its safer to use our own +#define RADIOLIB_MIN(a,b) ((a)<(b)?(a):(b)) +#define RADIOLIB_MAX(a,b) ((a)>(b)?(a):(b)) +#define RADIOLIB_ABS(x) ((x)>0?(x):-(x)) + +// version definitions +#define RADIOLIB_VERSION_MAJOR 7 +#define RADIOLIB_VERSION_MINOR 1 +#define RADIOLIB_VERSION_PATCH 0 +#define RADIOLIB_VERSION_EXTRA 0 + +#define RADIOLIB_VERSION (((RADIOLIB_VERSION_MAJOR) << 24) | ((RADIOLIB_VERSION_MINOR) << 16) | ((RADIOLIB_VERSION_PATCH) << 8) | (RADIOLIB_VERSION_EXTRA)) + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/BuildOptUser.h b/software/firmware/source/libraries/RadioLib/src/BuildOptUser.h new file mode 100644 index 000000000..31c672904 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/BuildOptUser.h @@ -0,0 +1,15 @@ +#if !defined(_RADIOLIB_USER_BUILD_OPTIONS_H) +#define _RADIOLIB_USER_BUILD_OPTIONS_H + +// this file can be used to define any user build options +// most commonly, RADIOLIB_EXCLUDE_* macros +// or enabling debug output + +//#define RADIOLIB_DEBUG_BASIC (1) // basic debugging (e.g. reporting GPIO timeouts or module not being found) +//#define RADIOLIB_DEBUG_PROTOCOL (1) // protocol information (e.g. LoRaWAN internal information) +//#define RADIOLIB_DEBUG_SPI (1) // verbose transcription of all SPI communication - produces large debug logs! +//#define RADIOLIB_VERBOSE_ASSERT (1) // verbose assertions - will print out file and line number on failure + +#define RADIOLIB_LOW_LEVEL (1) // Low-level hardware access enable + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/Hal.cpp b/software/firmware/source/libraries/RadioLib/src/Hal.cpp new file mode 100644 index 000000000..acc43bfe7 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/Hal.cpp @@ -0,0 +1,35 @@ +#include "Hal.h" + +RadioLibHal::RadioLibHal(const uint32_t input, const uint32_t output, const uint32_t low, const uint32_t high, const uint32_t rising, const uint32_t falling) + : GpioModeInput(input), + GpioModeOutput(output), + GpioLevelLow(low), + GpioLevelHigh(high), + GpioInterruptRising(rising), + GpioInterruptFalling(falling) {} + +void RadioLibHal::init() { + +} + +void RadioLibHal::term() { + +} + +void RadioLibHal::tone(uint32_t pin, unsigned int frequency, RadioLibTime_t duration) { + (void)pin; + (void)frequency; + (void)duration; +} + +void RadioLibHal::noTone(uint32_t pin) { + (void)pin; +} + +void RadioLibHal::yield() { + +} + +uint32_t RadioLibHal::pinToInterrupt(uint32_t pin) { + return(pin); +} diff --git a/software/firmware/source/libraries/RadioLib/src/Hal.h b/software/firmware/source/libraries/RadioLib/src/Hal.h new file mode 100644 index 000000000..291b34735 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/Hal.h @@ -0,0 +1,212 @@ +#if !defined(_RADIOLIB_HAL_H) +#define _RADIOLIB_HAL_H + +#include +#include + +#include "BuildOpt.h" + +/*! + \class RadioLibHal + \brief Hardware abstraction library base interface. +*/ +class RadioLibHal { + public: + + // values for pin modes, levels and change directions + // these tell RadioLib how are different logic states represented on a given platform + + /*! + \brief Value to be used as the "input" GPIO direction. + */ + const uint32_t GpioModeInput; + + /*! + \brief Value to be used as the "output" GPIO direction. + */ + const uint32_t GpioModeOutput; + + /*! + \brief Value to be used as the "low" GPIO level. + */ + const uint32_t GpioLevelLow; + + /*! + \brief Value to be used as the "high" GPIO level. + */ + const uint32_t GpioLevelHigh; + + /*! + \brief Value to be used as the "rising" GPIO level change direction. + */ + const uint32_t GpioInterruptRising; + + /*! + \brief Value to be used as the "falling" GPIO level change direction. + */ + const uint32_t GpioInterruptFalling; + + /*! + \brief Default constructor. + \param input Value to be used as the "input" GPIO direction. + \param output Value to be used as the "output" GPIO direction. + \param low Value to be used as the "low" GPIO level. + \param high Value to be used as the "high" GPIO level. + \param rising Value to be used as the "rising" GPIO level change direction. + \param falling Value to be used as the "falling" GPIO level change direction. + */ + RadioLibHal(const uint32_t input, const uint32_t output, const uint32_t low, const uint32_t high, const uint32_t rising, const uint32_t falling); + + // pure virtual methods - these must be implemented by the hardware abstraction for RadioLib to function + + /*! + \brief GPIO pin mode (input/output/...) configuration method. + Must be implemented by the platform-specific hardware abstraction! + \param pin Pin to be changed (platform-specific). + \param mode Mode to be set (platform-specific). + */ + virtual void pinMode(uint32_t pin, uint32_t mode) = 0; + + /*! + \brief Digital write method. + Must be implemented by the platform-specific hardware abstraction! + \param pin Pin to be changed (platform-specific). + \param value Value to set (platform-specific). + */ + virtual void digitalWrite(uint32_t pin, uint32_t value) = 0; + + /*! + \brief Digital read method. + Must be implemented by the platform-specific hardware abstraction! + \param pin Pin to be changed (platform-specific). + \returns Value read on the pin (platform-specific). + */ + virtual uint32_t digitalRead(uint32_t pin) = 0; + + /*! + \brief Method to attach function to an external interrupt. + Must be implemented by the platform-specific hardware abstraction! + \param interruptNum Interrupt number to attach to (platform-specific). + \param interruptCb Interrupt service routine to execute. + \param mode Rising/falling mode (platform-specific). + */ + virtual void attachInterrupt(uint32_t interruptNum, void (*interruptCb)(void), uint32_t mode) = 0; + + /*! + \brief Method to detach function from an external interrupt. + Must be implemented by the platform-specific hardware abstraction! + \param interruptNum Interrupt number to detach from (platform-specific). + */ + virtual void detachInterrupt(uint32_t interruptNum) = 0; + + /*! + \brief Blocking wait function. + Must be implemented by the platform-specific hardware abstraction! + \param ms Number of milliseconds to wait. + */ + virtual void delay(RadioLibTime_t ms) = 0; + + /*! + \brief Blocking microsecond wait function. + Must be implemented by the platform-specific hardware abstraction! + \param us Number of microseconds to wait. + */ + virtual void delayMicroseconds(RadioLibTime_t us) = 0; + + /*! + \brief Get number of milliseconds since start. + Must be implemented by the platform-specific hardware abstraction! + \returns Number of milliseconds since start. + */ + virtual RadioLibTime_t millis() = 0; + + /*! + \brief Get number of microseconds since start. + Must be implemented by the platform-specific hardware abstraction! + \returns Number of microseconds since start. + */ + virtual RadioLibTime_t micros() = 0; + + /*! + \brief Measure the length of incoming digital pulse in microseconds. + Must be implemented by the platform-specific hardware abstraction! + \param pin Pin to measure on (platform-specific). + \param state Pin level to monitor (platform-specific). + \param timeout Timeout in microseconds. + \returns Pulse length in microseconds, or 0 if the pulse did not start before timeout. + */ + virtual long pulseIn(uint32_t pin, uint32_t state, RadioLibTime_t timeout) = 0; + + /*! + \brief SPI initialization method. + */ + virtual void spiBegin() = 0; + + /*! + \brief Method to start SPI transaction. + */ + virtual void spiBeginTransaction() = 0; + + /*! + \brief Method to transfer buffer over SPI. + \param out Buffer to send. + \param len Number of data to send or receive. + \param in Buffer to save received data into. + */ + virtual void spiTransfer(uint8_t* out, size_t len, uint8_t* in) = 0; + + /*! + \brief Method to end SPI transaction. + */ + virtual void spiEndTransaction() = 0; + + /*! + \brief SPI termination method. + */ + virtual void spiEnd() = 0; + + // virtual methods - these may or may not exists on a given platform + // they exist in this implementation, but do nothing + + /*! + \brief Module initialization method. + This will be called by all radio modules at the beginning of startup. + Can be used to e.g., initialize SPI interface. + */ + virtual void init(); + + /*! + \brief Module termination method. + This will be called by all radio modules when the destructor is called. + Can be used to e.g., stop SPI interface. + */ + virtual void term(); + + /*! + \brief Method to produce a square-wave with 50% duty cycle ("tone") of a given frequency at some pin. + \param pin Pin to be used as the output. + \param frequency Frequency of the square wave. + \param duration Duration of the tone in ms. When set to 0, the tone will be infinite. + */ + virtual void tone(uint32_t pin, unsigned int frequency, RadioLibTime_t duration = 0); + + /*! + \brief Method to stop producing a tone. + \param pin Pin which is currently producing the tone. + */ + virtual void noTone(uint32_t pin); + + /*! + \brief Yield method, called from long loops in multi-threaded environment (to prevent blocking other threads). + */ + virtual void yield(); + + /*! + \brief Function to convert from pin number to interrupt number. + \param pin Pin to convert from. + \returns The interrupt number of a given pin. + */ + virtual uint32_t pinToInterrupt(uint32_t pin); +}; + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/Module.cpp b/software/firmware/source/libraries/RadioLib/src/Module.cpp new file mode 100644 index 000000000..7b3c59aef --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/Module.cpp @@ -0,0 +1,524 @@ +#include "Module.h" + +// the following is probably only needed on non-Arduino builds +#include +#include + +#if defined(RADIOLIB_BUILD_ARDUINO) +#include "hal/Arduino/ArduinoHal.h" + +Module::Module(uint32_t cs, uint32_t irq, uint32_t rst, uint32_t gpio) : csPin(cs), irqPin(irq), rstPin(rst), gpioPin(gpio) { + this->hal = new ArduinoHal(); +} + +Module::Module(uint32_t cs, uint32_t irq, uint32_t rst, uint32_t gpio, SPIClass& spi, SPISettings spiSettings) : csPin(cs), irqPin(irq), rstPin(rst), gpioPin(gpio) { + this->hal = new ArduinoHal(spi, spiSettings); +} +#endif + +Module::Module(RadioLibHal *hal, uint32_t cs, uint32_t irq, uint32_t rst, uint32_t gpio) : csPin(cs), irqPin(irq), rstPin(rst), gpioPin(gpio) { + this->hal = hal; +} + +Module::Module(const Module& mod) { + *this = mod; +} + +Module& Module::operator=(const Module& mod) { + memcpy((void*)&mod.spiConfig, &this->spiConfig, sizeof(SPIConfig_t)); + this->csPin = mod.csPin; + this->irqPin = mod.irqPin; + this->rstPin = mod.rstPin; + this->gpioPin = mod.gpioPin; + return(*this); +} + +static volatile const char info[] = RADIOLIB_INFO; +void Module::init() { + this->hal->init(); + this->hal->pinMode(csPin, this->hal->GpioModeOutput); + this->hal->digitalWrite(csPin, this->hal->GpioLevelHigh); + RADIOLIB_DEBUG_BASIC_PRINTLN(RADIOLIB_INFO); +} + +void Module::term() { + // stop hardware interfaces (if they were initialized by the library) + this->hal->term(); +} + +int16_t Module::SPIgetRegValue(uint32_t reg, uint8_t msb, uint8_t lsb) { + if((msb > 7) || (lsb > 7) || (lsb > msb)) { + return(RADIOLIB_ERR_INVALID_BIT_RANGE); + } + + uint8_t rawValue = SPIreadRegister(reg); + uint8_t maskedValue = rawValue & ((0b11111111 << lsb) & (0b11111111 >> (7 - msb))); + return(maskedValue); +} + +int16_t Module::SPIsetRegValue(uint32_t reg, uint8_t value, uint8_t msb, uint8_t lsb, uint8_t checkInterval, uint8_t checkMask) { + if((msb > 7) || (lsb > 7) || (lsb > msb)) { + return(RADIOLIB_ERR_INVALID_BIT_RANGE); + } + + uint8_t currentValue = SPIreadRegister(reg); + uint8_t mask = ~((0b11111111 << (msb + 1)) | (0b11111111 >> (8 - lsb))); + uint8_t newValue = (currentValue & ~mask) | (value & mask); + SPIwriteRegister(reg, newValue); + + #if RADIOLIB_SPI_PARANOID + // check register value each millisecond until check interval is reached + // some registers need a bit of time to process the change (e.g. SX127X_REG_OP_MODE) + RadioLibTime_t start = this->hal->micros(); + #if RADIOLIB_DEBUG_SPI + uint8_t readValue = 0x00; + #endif + while(this->hal->micros() - start < (checkInterval * 1000)) { + uint8_t val = SPIreadRegister(reg); + if((val & checkMask) == (newValue & checkMask)) { + // check passed, we can stop the loop + return(RADIOLIB_ERR_NONE); + } + #if RADIOLIB_DEBUG_SPI + readValue = val; + #endif + } + + // check failed, print debug info + RADIOLIB_DEBUG_SPI_PRINTLN(); + RADIOLIB_DEBUG_SPI_PRINTLN("address:\t0x%X", reg); + RADIOLIB_DEBUG_SPI_PRINTLN("bits:\t\t%d %d", msb, lsb); + RADIOLIB_DEBUG_SPI_PRINTLN("value:\t\t0x%X", value); + RADIOLIB_DEBUG_SPI_PRINTLN("current:\t0x%X", currentValue); + RADIOLIB_DEBUG_SPI_PRINTLN("mask:\t\t0x%X", mask); + RADIOLIB_DEBUG_SPI_PRINTLN("new:\t\t0x%X", newValue); + RADIOLIB_DEBUG_SPI_PRINTLN("read:\t\t0x%X", readValue); + + return(RADIOLIB_ERR_SPI_WRITE_FAILED); + #else + return(RADIOLIB_ERR_NONE); + #endif +} + +void Module::SPIreadRegisterBurst(uint32_t reg, size_t numBytes, uint8_t* inBytes) { + if(!this->spiConfig.stream) { + SPItransfer(this->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_READ], reg, NULL, inBytes, numBytes); + } else { + uint8_t cmd[6]; + uint8_t* cmdPtr = cmd; + for(int8_t i = (int8_t)this->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_CMD]/8 - 1; i >= 0; i--) { + *(cmdPtr++) = (this->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_READ] >> 8*i) & 0xFF; + } + for(int8_t i = (int8_t)((this->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_ADDR]/8) - 1); i >= 0; i--) { + *(cmdPtr++) = (reg >> 8*i) & 0xFF; + } + SPItransferStream(cmd, this->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_CMD]/8 + this->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_ADDR]/8, false, NULL, inBytes, numBytes, true); + } +} + +uint8_t Module::SPIreadRegister(uint32_t reg) { + uint8_t resp = 0; + if(!spiConfig.stream) { + SPItransfer(this->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_READ], reg, NULL, &resp, 1); + } else { + uint8_t cmd[6]; + uint8_t* cmdPtr = cmd; + for(int8_t i = (int8_t)this->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_CMD]/8 - 1; i >= 0; i--) { + *(cmdPtr++) = (this->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_READ] >> 8*i) & 0xFF; + } + for(int8_t i = (int8_t)((this->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_ADDR]/8) - 1); i >= 0; i--) { + *(cmdPtr++) = (reg >> 8*i) & 0xFF; + } + SPItransferStream(cmd, this->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_CMD]/8 + this->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_ADDR]/8, false, NULL, &resp, 1, true); + } + return(resp); +} + +void Module::SPIwriteRegisterBurst(uint32_t reg, uint8_t* data, size_t numBytes) { + if(!spiConfig.stream) { + SPItransfer(spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_WRITE], reg, data, NULL, numBytes); + } else { + uint8_t cmd[6]; + uint8_t* cmdPtr = cmd; + for(int8_t i = (int8_t)this->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_CMD]/8 - 1; i >= 0; i--) { + *(cmdPtr++) = (this->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_WRITE] >> 8*i) & 0xFF; + } + for(int8_t i = (int8_t)((this->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_ADDR]/8) - 1); i >= 0; i--) { + *(cmdPtr++) = (reg >> 8*i) & 0xFF; + } + SPItransferStream(cmd, this->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_CMD]/8 + this->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_ADDR]/8, true, data, NULL, numBytes, true); + } +} + +void Module::SPIwriteRegister(uint32_t reg, uint8_t data) { + if(!spiConfig.stream) { + SPItransfer(spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_WRITE], reg, &data, NULL, 1); + } else { + uint8_t cmd[6]; + uint8_t* cmdPtr = cmd; + for(int8_t i = (int8_t)this->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_CMD]/8 - 1; i >= 0; i--) { + *(cmdPtr++) = (this->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_WRITE] >> 8*i) & 0xFF; + } + for(int8_t i = (int8_t)((this->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_ADDR]/8) - 1); i >= 0; i--) { + *(cmdPtr++) = (reg >> 8*i) & 0xFF; + } + SPItransferStream(cmd, this->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_CMD]/8 + this->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_ADDR]/8, true, &data, NULL, 1, true); + } +} + +void Module::SPItransfer(uint16_t cmd, uint32_t reg, uint8_t* dataOut, uint8_t* dataIn, size_t numBytes) { + // prepare the buffers + size_t buffLen = this->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_CMD]/8 + this->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_ADDR]/8 + numBytes; + #if RADIOLIB_STATIC_ONLY + uint8_t buffOut[RADIOLIB_STATIC_ARRAY_SIZE]; + uint8_t buffIn[RADIOLIB_STATIC_ARRAY_SIZE]; + #else + uint8_t* buffOut = new uint8_t[buffLen]; + uint8_t* buffIn = new uint8_t[buffLen]; + #endif + uint8_t* buffOutPtr = buffOut; + + // copy the command + // TODO properly handle variable commands and addresses + if(this->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_ADDR] <= 8) { + *(buffOutPtr++) = reg | cmd; + } else { + *(buffOutPtr++) = (reg >> 8) | cmd; + *(buffOutPtr++) = reg & 0xFF; + } + + // copy the data + if(cmd == spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_WRITE]) { + memcpy(buffOutPtr, dataOut, numBytes); + } else { + memset(buffOutPtr, this->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_NOP], numBytes); + } + + // do the transfer + this->hal->spiBeginTransaction(); + this->hal->digitalWrite(this->csPin, this->hal->GpioLevelLow); + this->hal->spiTransfer(buffOut, buffLen, buffIn); + this->hal->digitalWrite(this->csPin, this->hal->GpioLevelHigh); + this->hal->spiEndTransaction(); + + // copy the data + if(cmd == spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_READ]) { + memcpy(dataIn, &buffIn[this->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_ADDR]/8], numBytes); + } + + // print debug information + #if RADIOLIB_DEBUG_SPI + uint8_t* debugBuffPtr = NULL; + if(cmd == spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_WRITE]) { + RADIOLIB_DEBUG_SPI_PRINT("W\t%X\t", reg); + debugBuffPtr = &buffOut[this->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_ADDR]/8]; + } else if(cmd == spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_READ]) { + RADIOLIB_DEBUG_SPI_PRINT("R\t%X\t", reg); + debugBuffPtr = &buffIn[this->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_ADDR]/8]; + } + for(size_t n = 0; n < numBytes; n++) { + RADIOLIB_DEBUG_SPI_PRINT_NOTAG("%X\t", debugBuffPtr[n]); + } + RADIOLIB_DEBUG_SPI_PRINTLN_NOTAG(); + #endif + + #if !RADIOLIB_STATIC_ONLY + delete[] buffOut; + delete[] buffIn; + #endif +} + +int16_t Module::SPIreadStream(uint16_t cmd, uint8_t* data, size_t numBytes, bool waitForGpio, bool verify) { + uint8_t cmdBuf[2]; + uint8_t* cmdPtr = cmdBuf; + for(int8_t i = (int8_t)this->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_CMD]/8 - 1; i >= 0; i--) { + *(cmdPtr++) = (cmd >> 8*i) & 0xFF; + } + return(this->SPIreadStream(cmdBuf, this->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_CMD]/8, data, numBytes, waitForGpio, verify)); +} + +int16_t Module::SPIreadStream(uint8_t* cmd, uint8_t cmdLen, uint8_t* data, size_t numBytes, bool waitForGpio, bool verify) { + // send the command + int16_t state = this->SPItransferStream(cmd, cmdLen, false, NULL, data, numBytes, waitForGpio); + RADIOLIB_ASSERT(state); + + #if !RADIOLIB_SPI_PARANOID + (void)verify; + return(RADIOLIB_ERR_NONE); + #else + + // check the status + if(verify && (this->spiConfig.checkStatusCb != nullptr)) { + state = this->spiConfig.checkStatusCb(this); + } + + return(state); + #endif +} + +int16_t Module::SPIwriteStream(uint16_t cmd, uint8_t* data, size_t numBytes, bool waitForGpio, bool verify) { + uint8_t cmdBuf[2]; + uint8_t* cmdPtr = cmdBuf; + for(int8_t i = (int8_t)this->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_CMD]/8 - 1; i >= 0; i--) { + *(cmdPtr++) = (cmd >> 8*i) & 0xFF; + } + return(this->SPIwriteStream(cmdBuf, this->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_CMD]/8, data, numBytes, waitForGpio, verify)); +} + +int16_t Module::SPIwriteStream(uint8_t* cmd, uint8_t cmdLen, uint8_t* data, size_t numBytes, bool waitForGpio, bool verify) { + // send the command + int16_t state = this->SPItransferStream(cmd, cmdLen, true, data, NULL, numBytes, waitForGpio); + RADIOLIB_ASSERT(state); + + #if !RADIOLIB_SPI_PARANOID + (void)verify; + return(RADIOLIB_ERR_NONE); + #else + + // check the status + if(verify && (this->spiConfig.checkStatusCb != nullptr)) { + state = this->spiConfig.checkStatusCb(this); + } + + return(state); + #endif +} + +int16_t Module::SPIcheckStream() { + int16_t state = RADIOLIB_ERR_NONE; + + #if RADIOLIB_SPI_PARANOID + // get the status + uint8_t spiStatus = 0; + uint8_t cmdBuf[2]; + uint8_t* cmdPtr = cmdBuf; + for(int8_t i = (int8_t)this->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_CMD]/8 - 1; i >= 0; i--) { + *(cmdPtr++) = ( this->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_STATUS] >> 8*i) & 0xFF; + } + state = this->SPItransferStream(cmdBuf, this->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_CMD]/8, false, NULL, &spiStatus, 1, true); + RADIOLIB_ASSERT(state); + + // translate to RadioLib status code + if(this->spiConfig.parseStatusCb != nullptr) { + this->spiConfig.err = this->spiConfig.parseStatusCb(spiStatus); + } + #endif + + return(state); +} + +int16_t Module::SPItransferStream(const uint8_t* cmd, uint8_t cmdLen, bool write, uint8_t* dataOut, uint8_t* dataIn, size_t numBytes, bool waitForGpio) { + // prepare the output buffer + size_t buffLen = cmdLen + numBytes; + if(!write) { + buffLen += (this->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_STATUS] / 8); + } + #if RADIOLIB_STATIC_ONLY + uint8_t buffOut[RADIOLIB_STATIC_ARRAY_SIZE]; + #else + uint8_t* buffOut = new uint8_t[buffLen]; + #endif + uint8_t* buffOutPtr = buffOut; + + // copy the command + for(uint8_t n = 0; n < cmdLen; n++) { + *(buffOutPtr++) = cmd[n]; + } + + // copy the data + if(write) { + memcpy(buffOutPtr, dataOut, numBytes); + } else { + memset(buffOutPtr, this->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_NOP], numBytes + (this->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_STATUS] / 8)); + } + + // ensure GPIO is low + if(waitForGpio) { + if(this->gpioPin == RADIOLIB_NC) { + this->hal->delay(50); + } else { + RadioLibTime_t start = this->hal->millis(); + while(this->hal->digitalRead(this->gpioPin)) { + this->hal->yield(); + if(this->hal->millis() - start >= this->spiConfig.timeout) { + RADIOLIB_DEBUG_BASIC_PRINTLN("GPIO pre-transfer timeout, is it connected?"); + #if !RADIOLIB_STATIC_ONLY + delete[] buffOut; + #endif + return(RADIOLIB_ERR_SPI_CMD_TIMEOUT); + } + } + } + } + + // prepare the input buffer + #if RADIOLIB_STATIC_ONLY + uint8_t buffIn[RADIOLIB_STATIC_ARRAY_SIZE]; + #else + uint8_t* buffIn = new uint8_t[buffLen]; + #endif + + // do the transfer + this->hal->spiBeginTransaction(); + this->hal->digitalWrite(this->csPin, this->hal->GpioLevelLow); + this->hal->spiTransfer(buffOut, buffLen, buffIn); + this->hal->digitalWrite(this->csPin, this->hal->GpioLevelHigh); + this->hal->spiEndTransaction(); + + // wait for GPIO to go high and then low + if(waitForGpio) { + if(this->gpioPin == RADIOLIB_NC) { + this->hal->delay(1); + } else { + this->hal->delayMicroseconds(1); + RadioLibTime_t start = this->hal->millis(); + while(this->hal->digitalRead(this->gpioPin)) { + this->hal->yield(); + if(this->hal->millis() - start >= this->spiConfig.timeout) { + RADIOLIB_DEBUG_BASIC_PRINTLN("GPIO post-transfer timeout, is it connected?"); + #if !RADIOLIB_STATIC_ONLY + delete[] buffOut; + delete[] buffIn; + #endif + return(RADIOLIB_ERR_SPI_CMD_TIMEOUT); + } + } + } + } + + // parse status + int16_t state = RADIOLIB_ERR_NONE; + if((this->spiConfig.parseStatusCb != nullptr) && (numBytes > 0)) { + state = this->spiConfig.parseStatusCb(buffIn[this->spiConfig.statusPos]); + } + + // copy the data + if(!write) { + // skip the status bytes if present + memcpy(dataIn, &buffIn[cmdLen + (this->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_STATUS] / 8)], numBytes); + } + + // print debug information + #if RADIOLIB_DEBUG_SPI + // print command byte(s) + RADIOLIB_DEBUG_SPI_PRINT("CMD"); + if(write) { + RADIOLIB_DEBUG_SPI_PRINT_NOTAG("W\t"); + } else { + RADIOLIB_DEBUG_SPI_PRINT_NOTAG("R\t"); + } + size_t n = 0; + for(; n < cmdLen; n++) { + RADIOLIB_DEBUG_SPI_PRINT_NOTAG("%X\t", cmd[n]); + } + RADIOLIB_DEBUG_SPI_PRINTLN_NOTAG(); + + // print data bytes + RADIOLIB_DEBUG_SPI_PRINT("SI\t"); + for(n = 0; n < cmdLen; n++) { + RADIOLIB_DEBUG_SPI_PRINT_NOTAG("\t"); + } + for(; n < buffLen; n++) { + RADIOLIB_DEBUG_SPI_PRINT_NOTAG("%X\t", buffOut[n]); + } + RADIOLIB_DEBUG_SPI_PRINTLN_NOTAG(); + RADIOLIB_DEBUG_SPI_PRINT("SO\t"); + for(n = 0; n < buffLen; n++) { + RADIOLIB_DEBUG_SPI_PRINT_NOTAG("%X\t", buffIn[n]); + } + RADIOLIB_DEBUG_SPI_PRINTLN_NOTAG(); + #endif + + #if !RADIOLIB_STATIC_ONLY + delete[] buffOut; + delete[] buffIn; + #endif + + return(state); +} + +void Module::waitForMicroseconds(RadioLibTime_t start, RadioLibTime_t len) { + #if RADIOLIB_INTERRUPT_TIMING + (void)start; + if((this->TimerSetupCb != nullptr) && (len != this->prevTimingLen)) { + prevTimingLen = len; + this->TimerSetupCb(len); + } + this->TimerFlag = false; + while(!this->TimerFlag) { + this->hal->yield(); + } + #else + while(this->hal->micros() - start < len) { + this->hal->yield(); + } + #endif +} + +#if RADIOLIB_DEBUG +void Module::regdump(const char* level, uint16_t start, size_t len) { + #if RADIOLIB_STATIC_ONLY + uint8_t buff[RADIOLIB_STATIC_ARRAY_SIZE]; + #else + uint8_t* buff = new uint8_t[len]; + #endif + SPIreadRegisterBurst(start, len, buff); + rlb_hexdump(level, buff, len, start); + #if !RADIOLIB_STATIC_ONLY + delete[] buff; + #endif +} +#endif + +void Module::setRfSwitchPins(uint32_t rxEn, uint32_t txEn) { + // This can be on the stack, setRfSwitchTable copies the contents + const uint32_t pins[] = { + rxEn, txEn, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC, + }; + + // This must be static, since setRfSwitchTable stores a reference. + static const RfSwitchMode_t table[] = { + { MODE_IDLE, {this->hal->GpioLevelLow, this->hal->GpioLevelLow} }, + { MODE_RX, {this->hal->GpioLevelHigh, this->hal->GpioLevelLow} }, + { MODE_TX, {this->hal->GpioLevelLow, this->hal->GpioLevelHigh} }, + END_OF_MODE_TABLE, + }; + setRfSwitchTable(pins, table); +} + +void Module::setRfSwitchTable(const uint32_t (&pins)[RFSWITCH_MAX_PINS], const RfSwitchMode_t table[]) { + memcpy(this->rfSwitchPins, pins, sizeof(this->rfSwitchPins)); + this->rfSwitchTable = table; + for(size_t i = 0; i < RFSWITCH_MAX_PINS; i++) { + this->hal->pinMode(pins[i], this->hal->GpioModeOutput); + } +} + +const Module::RfSwitchMode_t *Module::findRfSwitchMode(uint8_t mode) const { + const RfSwitchMode_t *row = this->rfSwitchTable; + while(row && row->mode != MODE_END_OF_TABLE) { + if(row->mode == mode) { + return row; + } + ++row; + } + return nullptr; +} + +void Module::setRfSwitchState(uint8_t mode) { + const RfSwitchMode_t *row = findRfSwitchMode(mode); + if(!row) { + // RF switch control is disabled or does not have this mode + return; + } + + // set pins + const uint32_t *value = &row->values[0]; + for(size_t i = 0; i < RFSWITCH_MAX_PINS; i++) { + uint32_t pin = this->rfSwitchPins[i]; + if(!(pin & RFSWITCH_PIN_FLAG)) { + this->hal->digitalWrite(pin, *value); + } + ++value; + } +} diff --git a/software/firmware/source/libraries/RadioLib/src/Module.h b/software/firmware/source/libraries/RadioLib/src/Module.h new file mode 100644 index 000000000..946c42e4d --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/Module.h @@ -0,0 +1,543 @@ +#if !defined(_RADIOLIB_MODULE_H) +#define _RADIOLIB_MODULE_H + +#include "TypeDef.h" +#include "Hal.h" +#include "utils/Utils.h" + +#if defined(RADIOLIB_BUILD_ARDUINO) + #include +#endif + +#if defined(STM32WLxx) + #include +#endif + +/*! + \def END_OF_MODE_TABLE Value to use as the last element in a mode table to indicate the + end of the table. See \ref setRfSwitchTable for details. +*/ +#define END_OF_MODE_TABLE { Module::MODE_END_OF_TABLE, {} } + +/*! + \def RFSWITCH_PIN_FLAG Bit flag used to mark unused pins in RF switch pin map. This can be either + unconnected pin marked with RADIOLIB_NC, or a pin controlled by the radio (e.g. DIOx pins on LR11x0), + as opposed to an MCU-controlled GPIO pin. +*/ +#define RFSWITCH_PIN_FLAG (0x01UL << 31) + +/*! + \defgroup module_spi_command_pos Position of commands in Module::spiConfig command array. + \{ +*/ + +/*! \def RADIOLIB_MODULE_SPI_COMMAND_READ Position of the read command. */ +#define RADIOLIB_MODULE_SPI_COMMAND_READ (0) + +/*! \def RADIOLIB_MODULE_SPI_COMMAND_WRITE Position of the write command. */ +#define RADIOLIB_MODULE_SPI_COMMAND_WRITE (1) + +/*! \def RADIOLIB_MODULE_SPI_COMMAND_NOP Position of the no-operation command. */ +#define RADIOLIB_MODULE_SPI_COMMAND_NOP (2) + +/*! \def RADIOLIB_MODULE_SPI_COMMAND_STATUS Position of the status command. */ +#define RADIOLIB_MODULE_SPI_COMMAND_STATUS (3) + +/*! + \} +*/ + +/*! + \defgroup module_spi_width_pos Position of bit field widths in Module::spiConfig width array. + \{ +*/ + +/*! \def RADIOLIB_MODULE_SPI_WIDTH_ADDR Position of the address width. */ +#define RADIOLIB_MODULE_SPI_WIDTH_ADDR (0) + +/*! \def RADIOLIB_MODULE_SPI_WIDTH_CMD Position of the command width. */ +#define RADIOLIB_MODULE_SPI_WIDTH_CMD (1) + +/*! \def RADIOLIB_MODULE_SPI_WIDTH_STATUS Position of the status width. */ +#define RADIOLIB_MODULE_SPI_WIDTH_STATUS (2) + +/*! + \} +*/ + +/*! + \class Module + \brief Implements all common low-level methods to control the wireless module. + Every module class contains one private instance of this class. +*/ +class Module { + public: + /*! + \brief The maximum number of pins supported by the RF switch code. + Note: It is not recommended to use this constant in your sketch + when defining a rfswitch pins array, to prevent issues when this + value is ever increased and such an array gets extra zero + elements (that will be interpreted as pin 0). + */ + static const size_t RFSWITCH_MAX_PINS = 5; + + /*! + \struct RfSwitchMode_t + \brief Description of RF switch pin states for a single mode. + See \ref setRfSwitchTable for details. + */ + struct RfSwitchMode_t { + /*! \brief RF switching mode, one of \ref OpMode_t or a custom radio-defined value. */ + uint8_t mode; + + /*! \brief Output pin values */ + uint32_t values[RFSWITCH_MAX_PINS]; + }; + + /*! + \enum OpMode_t + \brief Constants to use in a mode table set be setRfSwitchTable. These + constants work for most radios, but some radios define their own + constants to be used instead. + + See \ref setRfSwitchTable for details. + */ + enum OpMode_t { + /*! + \brief End of table marker, use \ref END_OF_MODE_TABLE constant instead. + Value is zero to ensure zero-initialized mode ends the table. + */ + MODE_END_OF_TABLE = 0, + + /*! \brief Idle mode */ + MODE_IDLE, + + /*! \brief Receive mode */ + MODE_RX, + + /*! \brief Transmission mode */ + MODE_TX, + }; + + #if defined(RADIOLIB_BUILD_ARDUINO) + /*! + \brief Arduino Module constructor. Will use the default SPI interface and automatically initialize it. + \param cs Arduino pin to be used as chip select. + \param irq Arduino pin to be used as interrupt/GPIO. + \param rst Arduino pin to be used as hardware reset for the module. + \param gpio Arduino pin to be used as additional interrupt/GPIO. + */ + Module(uint32_t cs, uint32_t irq, uint32_t rst, uint32_t gpio = RADIOLIB_NC); + + /*! + \brief Arduino Module constructor. Will not attempt SPI interface initialization. + \param cs Arduino pin to be used as chip select. + \param irq Arduino pin to be used as interrupt/GPIO. + \param rst Arduino pin to be used as hardware reset for the module. + \param gpio Arduino pin to be used as additional interrupt/GPIO. + \param spi SPI interface to be used, can also use software SPI implementations. + \param spiSettings SPI interface settings. + */ + Module(uint32_t cs, uint32_t irq, uint32_t rst, uint32_t gpio, SPIClass& spi, SPISettings spiSettings = RADIOLIB_DEFAULT_SPI_SETTINGS); + #endif + + /*! + \brief Module constructor. + \param hal A Hardware abstraction layer instance. An ArduinoHal instance for example. + \param cs Pin to be used as chip select. + \param irq Pin to be used as interrupt/GPIO. + \param rst Pin to be used as hardware reset for the module. + \param gpio Pin to be used as additional interrupt/GPIO. + */ + Module(RadioLibHal *hal, uint32_t cs, uint32_t irq, uint32_t rst, uint32_t gpio = RADIOLIB_NC); + + /*! + \brief Copy constructor. + \param mod Module instance to copy. + */ + Module(const Module& mod); + + /*! + \brief Overload for assignment operator. + \param mod rvalue Module. + */ + Module& operator=(const Module& mod); + + // public member variables + /*! \brief Hardware abstraction layer to be used. */ + RadioLibHal* hal = NULL; + + /*! \brief Callback for parsing SPI status. */ + typedef int16_t (*SPIparseStatusCb_t)(uint8_t in); + + /*! \brief Callback for validation SPI status. */ + typedef int16_t (*SPIcheckStatusCb_t)(Module* mod); + + enum BitWidth_t { + BITS_0 = 0, + BITS_8 = 8, + BITS_16 = 16, + BITS_32 = 32, + }; + + /*! + \struct SPIConfig_t + \brief SPI configuration structure. + */ + struct SPIConfig_t { + /*! \brief Whether the SPI module is stream-type (SX126x/8x) or registrer access type (SX127x, CC1101 etc). */ + bool stream; + + /*! \brief Last recorded SPI error - only updated for modules that return status during SPI transfers. */ + int16_t err; + + /*! \brief SPI commands */ + uint16_t cmds[4]; + + /*! \brief Bit widths of SPI addresses, commands and status bytes */ + BitWidth_t widths[3]; + + /*! \brief Byte position of status command in SPI stream */ + uint8_t statusPos; + + /*! \brief Callback for parsing SPI status. */ + SPIparseStatusCb_t parseStatusCb; + + /*! \brief Callback for validation SPI status. */ + SPIcheckStatusCb_t checkStatusCb; + + /*! \brief Timeout in ms when waiting for GPIO signals. */ + RadioLibTime_t timeout; + }; + + /*! \brief SPI configuration structure. The default configuration corresponds to register-access modules, such as SX127x. */ + SPIConfig_t spiConfig = { + .stream = false, + .err = RADIOLIB_ERR_UNKNOWN, + .cmds = { 0x00, 0x80, 0x00, 0x00 }, + .widths = { Module::BITS_8, Module::BITS_0, Module::BITS_8 }, + .statusPos = 0, + .parseStatusCb = nullptr, + .checkStatusCb = nullptr, + .timeout = 1000, + }; + + #if RADIOLIB_INTERRUPT_TIMING + + /*! + \brief Timer interrupt setup callback typedef. + */ + typedef void (*TimerSetupCb_t)(uint32_t len); + + /*! + \brief Callback to timer interrupt setup function when running in interrupt timing control mode. + */ + TimerSetupCb_t TimerSetupCb = nullptr; + + /*! + \brief Timer flag variable to be controlled by a platform-dependent interrupt. + */ + volatile bool TimerFlag = false; + + #endif + + // basic methods + + /*! + \brief Initialize low-level module control. + */ + void init(); + + /*! + \brief Terminate low-level module control. + */ + void term(); + + // SPI methods + + /*! + \brief SPI read method that automatically masks unused bits. This method is the preferred SPI read mechanism. + \param reg Address of SPI register to read. + \param msb Most significant bit of the register variable. Bits above this one will be masked out. + \param lsb Least significant bit of the register variable. Bits below this one will be masked out. + \returns Masked register value or status code. + */ + int16_t SPIgetRegValue(uint32_t reg, uint8_t msb = 7, uint8_t lsb = 0); + + /*! + \brief Overwrite-safe SPI write method with verification. This method is the preferred SPI write mechanism. + \param reg Address of SPI register to write. + \param value Single byte value that will be written to the SPI register. + \param msb Most significant bit of the register variable. Bits above this one will not be affected by the write operation. + \param lsb Least significant bit of the register variable. Bits below this one will not be affected by the write operation. + \param checkInterval Number of milliseconds between register writing and verification reading. Some registers need up to 10ms to process the change. + \param checkMask Mask of bits to check, only bits set to 1 will be verified. + \returns \ref status_codes + */ + int16_t SPIsetRegValue(uint32_t reg, uint8_t value, uint8_t msb = 7, uint8_t lsb = 0, uint8_t checkInterval = 2, uint8_t checkMask = 0xFF); + + /*! + \brief SPI burst read method. + \param reg Address of SPI register to read. + \param numBytes Number of bytes that will be read. + \param inBytes Pointer to array that will hold the read data. + */ + void SPIreadRegisterBurst(uint32_t reg, size_t numBytes, uint8_t* inBytes); + + /*! + \brief SPI basic read method. Use of this method is reserved for special cases, SPIgetRegValue should be used instead. + \param reg Address of SPI register to read. + \returns Value that was read from register. + */ + uint8_t SPIreadRegister(uint32_t reg); + + /*! + \brief SPI burst write method. + \param reg Address of SPI register to write. + \param data Pointer to array that holds the data that will be written. + \param numBytes Number of bytes that will be written. + */ + void SPIwriteRegisterBurst(uint32_t reg, uint8_t* data, size_t numBytes); + + /*! + \brief SPI basic write method. Use of this method is reserved for special cases, SPIsetRegValue should be used instead. + \param reg Address of SPI register to write. + \param data Value that will be written to the register. + */ + void SPIwriteRegister(uint32_t reg, uint8_t data); + + /*! + \brief SPI single transfer method. + \param cmd SPI access command (read/write/burst/...). + \param reg Address of SPI register to transfer to/from. + \param dataOut Data that will be transferred from master to slave. + \param dataIn Data that was transferred from slave to master. + \param numBytes Number of bytes to transfer. + */ + void SPItransfer(uint16_t cmd, uint32_t reg, uint8_t* dataOut, uint8_t* dataIn, size_t numBytes); + + /*! + \brief Method to check the result of last SPI stream transfer. + \returns \ref status_codes + */ + int16_t SPIcheckStream(); + + /*! + \brief Method to perform a read transaction with SPI stream. + \param cmd SPI operation command. + \param data Data that will be transferred from slave to master. + \param numBytes Number of bytes to transfer. + \param waitForGpio Whether to wait for some GPIO at the end of transfer (e.g. BUSY line on SX126x/SX128x). + \param verify Whether to verify the result of the transaction after it is finished. + \returns \ref status_codes + */ + int16_t SPIreadStream(uint16_t cmd, uint8_t* data, size_t numBytes, bool waitForGpio = true, bool verify = true); + + /*! + \brief Method to perform a read transaction with SPI stream. + \param cmd SPI operation command. + \param cmdLen SPI command length in bytes. + \param data Data that will be transferred from slave to master. + \param numBytes Number of bytes to transfer. + \param waitForGpio Whether to wait for some GPIO at the end of transfer (e.g. BUSY line on SX126x/SX128x). + \param verify Whether to verify the result of the transaction after it is finished. + \returns \ref status_codes + */ + int16_t SPIreadStream(uint8_t* cmd, uint8_t cmdLen, uint8_t* data, size_t numBytes, bool waitForGpio = true, bool verify = true); + + /*! + \brief Method to perform a write transaction with SPI stream. + \param cmd SPI operation command. + \param data Data that will be transferred from master to slave. + \param numBytes Number of bytes to transfer. + \param waitForGpio Whether to wait for some GPIO at the end of transfer (e.g. BUSY line on SX126x/SX128x). + \param verify Whether to verify the result of the transaction after it is finished. + \returns \ref status_codes + */ + int16_t SPIwriteStream(uint16_t cmd, uint8_t* data, size_t numBytes, bool waitForGpio = true, bool verify = true); + + /*! + \brief Method to perform a write transaction with SPI stream. + \param cmd SPI operation command. + \param cmdLen SPI command length in bytes. + \param data Data that will be transferred from master to slave. + \param numBytes Number of bytes to transfer. + \param waitForGpio Whether to wait for some GPIO at the end of transfer (e.g. BUSY line on SX126x/SX128x). + \param verify Whether to verify the result of the transaction after it is finished. + \returns \ref status_codes + */ + int16_t SPIwriteStream(uint8_t* cmd, uint8_t cmdLen, uint8_t* data, size_t numBytes, bool waitForGpio = true, bool verify = true); + + /*! + \brief SPI single transfer method for modules with stream-type SPI interface (SX126x, SX128x etc.). + \param cmd SPI operation command. + \param cmdLen SPI command length in bytes. + \param write Set to true for write commands, false for read commands. + \param dataOut Data that will be transferred from master to slave. + \param dataIn Data that was transferred from slave to master. + \param numBytes Number of bytes to transfer. + \param waitForGpio Whether to wait for some GPIO at the end of transfer (e.g. BUSY line on SX126x/SX128x). + \returns \ref status_codes + */ + int16_t SPItransferStream(const uint8_t* cmd, uint8_t cmdLen, bool write, uint8_t* dataOut, uint8_t* dataIn, size_t numBytes, bool waitForGpio); + + // pin number access methods + + /*! + \brief Access method to get the pin number of SPI chip select. + \returns Pin number of SPI chip select configured in the constructor. + */ + uint32_t getCs() const { return(csPin); } + + /*! + \brief Access method to get the pin number of interrupt/GPIO. + \returns Pin number of interrupt/GPIO configured in the constructor. + */ + uint32_t getIrq() const { return(irqPin); } + + /*! + \brief Access method to get the pin number of hardware reset pin. + \returns Pin number of hardware reset pin configured in the constructor. + */ + uint32_t getRst() const { return(rstPin); } + + /*! + \brief Access method to get the pin number of second interrupt/GPIO. + \returns Pin number of second interrupt/GPIO configured in the constructor. + */ + uint32_t getGpio() const { return(gpioPin); } + + /*! + \brief Some modules contain external RF switch controlled by pins. + This function gives RadioLib control over those pins to + automatically switch between various modes: When idle both pins + will be LOW, during TX the `txEn` pin will be HIGH, during RX the + `rxPin` will be HIGH. + + Radiolib will automatically set the pin mode and value of these + pins, so do not control them from the sketch. + + When more than two pins or more control over the output values are + needed, use the setRfSwitchTable() function. + + \param rxEn RX enable pin. + \param txEn TX enable pin. + */ + void setRfSwitchPins(uint32_t rxEn, uint32_t txEn); + + /*! + \brief Some modules contain external RF switch controlled by pins. + This function gives RadioLib control over those pins to + automatically switch between various modes. + + Radiolib will automatically set the pin mode and value of these + pins, so do not control them from the sketch. + + + \param pins A reference to an array of pins to control. This + should always be an array of 3 elements. If you need less pins, + use RADIOLIB_NC for the unused elements. + + \param table A reference to an array of pin values to use for each + supported mode. Each element is an RfSwitchMode_T struct that + lists the mode for which it applies and the values for each of the + pins passed in the pins argument respectively. + + The `pins` array will be copied into the Module object, so the + original array can be deallocated after this call. However, + a reference to the `table` array will be stored, so that array + must remain valid as long RadioLib is being used. + + The `mode` field in each table row should normally use any of the + `MODE_*` constants from the Module::OpMode_t enum. However, some + radios support additional modes and will define their own OpMode_t + enum. + + The length of the table is variable (to support radios that add + additional modes), so the table must always be terminated with the + special END_OF_MODE_TABLE value. + + Normally all modes should be listed in the table, but for some + radios, modes can be omitted to indicate they are not supported + (e.g. when a radio has a high power and low power TX mode but + external circuitry only supports low power). If applicable, this + is documented in the radio class itself. + + #### Example + For example, on a board that has an RF switch with an enable pin + connected to PA0 and a TX/RX select pin connected to PA1: + + \code + // In global scope, define the pin array and mode table + static const uint32_t rfswitch_pins[] = + {PA0, PA1, RADIOLIB_NC}; + static const Module::RfSwitchMode_t rfswitch_table[] = { + {Module::MODE_IDLE, {LOW, LOW}}, + {Module::MODE_RX, {HIGH, LOW}}, + {Module::MODE_TX, {HIGH, HIGH}}, + Module::END_OF_MODE_TABLE, + }; + + void setup() { + ... + // Then somewhere in setup, pass them to radiolib + radio.setRfSwitchTable(rfswitch_pins, rfswitch_table); + ... + } + \endcode + */ + + void setRfSwitchTable(const uint32_t (&pins)[RFSWITCH_MAX_PINS], const RfSwitchMode_t table[]); + + /*! + \brief Find a mode in the RfSwitchTable. + \param mode The mode to find. + \returns A pointer to the RfSwitchMode_t struct in the table that + matches the passed mode. Returns nullptr if no rfswitch pins are + configured, or the passed mode is not listed in the table. + */ + const RfSwitchMode_t *findRfSwitchMode(uint8_t mode) const; + + /*! + \brief Set RF switch state. + \param mode The mode to set. This must be one of the MODE_ constants, or a radio-specific constant. + */ + void setRfSwitchState(uint8_t mode); + + /*! + \brief Wait for time to elapse, either using the microsecond timer, or the TimerFlag. + Note that in interrupt timing mode, it is up to the user to set up the timing interrupt! + + \param start Waiting start timestamp, in microseconds. + \param len Waiting duration, in microseconds; + */ + void waitForMicroseconds(RadioLibTime_t start, RadioLibTime_t len); + + #if RADIOLIB_DEBUG + /*! + \brief Function to dump device registers as hex into the debug port. + \param level RadioLib debug level, set to NULL to not print. + \param start First address to dump. + \param len Number of bytes to dump. + */ + void regdump(const char* level, uint16_t start, size_t len); + #endif + +#if !RADIOLIB_GODMODE + private: +#endif + uint32_t csPin = RADIOLIB_NC; + uint32_t irqPin = RADIOLIB_NC; + uint32_t rstPin = RADIOLIB_NC; + uint32_t gpioPin = RADIOLIB_NC; + + // RF switch pins and table + uint32_t rfSwitchPins[RFSWITCH_MAX_PINS] = { RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC }; + const RfSwitchMode_t *rfSwitchTable = nullptr; + + #if RADIOLIB_INTERRUPT_TIMING + uint32_t prevTimingLen = 0; + #endif +}; + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/RadioLib.h b/software/firmware/source/libraries/RadioLib/src/RadioLib.h new file mode 100644 index 000000000..bd6c9598d --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/RadioLib.h @@ -0,0 +1,117 @@ +#if !defined(_RADIOLIB_H) +#define _RADIOLIB_H + +/*! + \mainpage RadioLib Documentation + + Universal wireless communication library for Arduino. + + \par Currently Supported Wireless Modules and Protocols + - CC1101 FSK module + - RF69 FSK module + - Si443x FSK module + - SX126x LoRa/FSK module + - SX127x LoRa/FSK module + - SX128x LoRa/GFSK/BLE/FLRC module + - SX1231 FSK module + - PhysicalLayer protocols + - RTTY (RTTYClient) + - Morse Code (MorseClient) + - AX.25 (AX25Client) + - SSTV (SSTVClient) + - Hellschreiber (HellClient) + - 4-FSK (FSK4Client) + - APRS (APRSClient) + + \par Quick Links + Documentation for most common methods can be found in its reference page (see the list above).\n + Some methods (mainly configuration) are also overridden in derived classes, such as SX1272, SX1278, RFM96 etc. for SX127x.\n + \ref status_codes have their own page.\n + Some modules implement methods of one or more compatibility layers, loosely based on the ISO/OSI model: + - PhysicalLayer - FSK and LoRa radio modules + + \see https://github.com/jgromes/RadioLib + + \copyright Copyright (c) 2019 Jan Gromes +*/ + +#include "TypeDef.h" +#include "Module.h" + +#include "Hal.h" +#if defined(RADIOLIB_BUILD_ARDUINO) +#include "hal/Arduino/ArduinoHal.h" +#endif + + +// warnings are printed in this file since BuildOpt.h is compiled in multiple places + +// check God mode +#if RADIOLIB_GODMODE + #warning "God mode active, I hope it was intentional. Buckle up, lads." +#endif + +// print debug info +#if RADIOLIB_DEBUG + #pragma message(RADIOLIB_INFO) +#endif + +// check unknown/unsupported platform +#if defined(RADIOLIB_UNKNOWN_PLATFORM) + #warning "RadioLib might not be compatible with this Arduino board - check supported platforms at https://github.com/jgromes/RadioLib!" +#endif + +// print warning for low-end platforms +#if defined(RADIOLIB_LOWEND_PLATFORM) + #warning "Low-end platform detected, stability issues are likely!" +#endif + +#include "modules/CC1101/CC1101.h" +#include "modules/LLCC68/LLCC68.h" +#include "modules/LR11x0/LR1110.h" +#include "modules/LR11x0/LR1120.h" +#include "modules/LR11x0/LR1121.h" +#include "modules/nRF24/nRF24.h" +#include "modules/RF69/RF69.h" +#include "modules/RFM2x/RFM22.h" +#include "modules/RFM2x/RFM23.h" +#include "modules/Si443x/Si4430.h" +#include "modules/Si443x/Si4431.h" +#include "modules/Si443x/Si4432.h" +#include "modules/SX123x/SX1231.h" +#include "modules/SX123x/SX1233.h" +#include "modules/SX126x/SX1261.h" +#include "modules/SX126x/SX1262.h" +#include "modules/SX126x/SX1268.h" +#include "modules/SX126x/STM32WLx.h" +#include "modules/SX127x/SX1272.h" +#include "modules/SX127x/SX1273.h" +#include "modules/SX127x/SX1276.h" +#include "modules/SX127x/SX1277.h" +#include "modules/SX127x/SX1278.h" +#include "modules/SX127x/SX1279.h" +#include "modules/SX128x/SX1280.h" +#include "modules/SX128x/SX1281.h" +#include "modules/SX128x/SX1282.h" + +// physical layer protocols +#include "protocols/PhysicalLayer/PhysicalLayer.h" +#include "protocols/AFSK/AFSK.h" +#include "protocols/AX25/AX25.h" +#include "protocols/Hellschreiber/Hellschreiber.h" +#include "protocols/Morse/Morse.h" +#include "protocols/Pager/Pager.h" +#include "protocols/RTTY/RTTY.h" +#include "protocols/SSTV/SSTV.h" +#include "protocols/FSK4/FSK4.h" +#include "protocols/APRS/APRS.h" +#include "protocols/ExternalRadio/ExternalRadio.h" +#include "protocols/Print/Print.h" +#include "protocols/BellModem/BellModem.h" +#include "protocols/LoRaWAN/LoRaWAN.h" + +// utilities +#include "utils/CRC.h" +#include "utils/Cryptography.h" + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/TypeDef.h b/software/firmware/source/libraries/RadioLib/src/TypeDef.h new file mode 100644 index 000000000..a25256856 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/TypeDef.h @@ -0,0 +1,654 @@ +#if !defined(_RADIOLIB_TYPES_H) +#define _RADIOLIB_TYPES_H + +// user build options may override the default +#include "BuildOptUser.h" +#include "BuildOpt.h" + +/*! + \defgroup config_shaping Data shaping filter values aliases. + + \{ +*/ + +/*! + \brief No shaping. +*/ +#define RADIOLIB_SHAPING_NONE (0x00) + +/*! + \brief Gaussian shaping filter, BT = 0.3 +*/ +#define RADIOLIB_SHAPING_0_3 (0x01) + +/*! + \brief Gaussian shaping filter, BT = 0.5 +*/ +#define RADIOLIB_SHAPING_0_5 (0x02) + +/*! + \brief Gaussian shaping filter, BT = 0.7 +*/ +#define RADIOLIB_SHAPING_0_7 (0x03) + +/*! + \brief Gaussian shaping filter, BT = 1.0 +*/ +#define RADIOLIB_SHAPING_1_0 (0x04) + +/*! + \} +*/ + +/*! + \defgroup config_encoding Encoding type aliases. + + \{ +*/ + +/*! + \brief Non-return to zero - no encoding. +*/ +#define RADIOLIB_ENCODING_NRZ (0x00) + +/*! + \brief Manchester encoding. +*/ +#define RADIOLIB_ENCODING_MANCHESTER (0x01) + +/*! + \brief Whitening. +*/ +#define RADIOLIB_ENCODING_WHITENING (0x02) + +/*! + \} +*/ + +/*! + \defgroup config_standby Standby mode type aliases. + + \{ +*/ + +/*! + \brief Default standby used by the module +*/ +#define RADIOLIB_STANDBY_DEFAULT (0x00) + +/*! + \brief Warm standby (e.g. crystal left running). +*/ +#define RADIOLIB_STANDBY_WARM (0x01) + +/*! + \brief Cold standby (e.g. only internal RC oscillator running). +*/ +#define RADIOLIB_STANDBY_COLD (0x02) + +/*! + \} +*/ + +/*! + \defgroup status_codes Status Codes + + \{ +*/ + +// common status codes + +/*! + \brief No error, method executed successfully. +*/ +#define RADIOLIB_ERR_NONE (0) + +/*! + \brief There was an unexpected, unknown error. If you see this, something went incredibly wrong. + Your Arduino may be possessed, contact your local exorcist to resolve this error. +*/ +#define RADIOLIB_ERR_UNKNOWN (-1) + +// SX127x/RFM9x status codes + +/*! + \brief Radio chip was not found during initialization. This can be caused by specifying wrong chip type in the constructor + (i.e. calling SX1272 constructor for SX1278 chip) or by a fault in your wiring (incorrect slave select pin). +*/ +#define RADIOLIB_ERR_CHIP_NOT_FOUND (-2) + +/*! + \brief Failed to allocate memory for temporary buffer. This can be cause by not enough RAM or by passing invalid pointer. +*/ +#define RADIOLIB_ERR_MEMORY_ALLOCATION_FAILED (-3) + +/*! + \brief Packet supplied to transmission method was longer than limit. +*/ +#define RADIOLIB_ERR_PACKET_TOO_LONG (-4) + +/*! + \brief Timed out waiting for transmission finish. +*/ +#define RADIOLIB_ERR_TX_TIMEOUT (-5) + +/*! + \brief Timed out waiting for incoming transmission. +*/ +#define RADIOLIB_ERR_RX_TIMEOUT (-6) + +/*! + \brief The calculated and expected CRCs of received packet do not match. + This means that the packet was damaged during transmission and should be sent again. +*/ +#define RADIOLIB_ERR_CRC_MISMATCH (-7) + +/*! + \brief The supplied bandwidth value is invalid for this module. +*/ +#define RADIOLIB_ERR_INVALID_BANDWIDTH (-8) + +/*! + \brief The supplied spreading factor value is invalid for this module. +*/ +#define RADIOLIB_ERR_INVALID_SPREADING_FACTOR (-9) + +/*! + \brief The supplied coding rate value is invalid for this module. +*/ +#define RADIOLIB_ERR_INVALID_CODING_RATE (-10) + +/*! + \brief Internal only. +*/ +#define RADIOLIB_ERR_INVALID_BIT_RANGE (-11) + +/*! + \brief The supplied frequency value is invalid for this module. +*/ +#define RADIOLIB_ERR_INVALID_FREQUENCY (-12) + +/*! + \brief The supplied output power value is invalid for this module. +*/ +#define RADIOLIB_ERR_INVALID_OUTPUT_POWER (-13) + +/*! + \brief LoRa preamble was detected during channel activity detection. + This means that there is some LoRa device currently transmitting in your channel. +*/ +#define RADIOLIB_PREAMBLE_DETECTED (-14) + +/*! + \brief No LoRa preambles were detected during channel activity detection. Your channel is free. +*/ +#define RADIOLIB_CHANNEL_FREE (-15) + +/*! + \brief Real value in SPI register does not match the expected one. This can be caused by faulty SPI wiring. +*/ +#define RADIOLIB_ERR_SPI_WRITE_FAILED (-16) + +/*! + \brief The supplied current limit value is invalid. +*/ +#define RADIOLIB_ERR_INVALID_CURRENT_LIMIT (-17) + +/*! + \brief The supplied preamble length is invalid. +*/ +#define RADIOLIB_ERR_INVALID_PREAMBLE_LENGTH (-18) + +/*! + \brief The supplied gain value is invalid. +*/ +#define RADIOLIB_ERR_INVALID_GAIN (-19) + +/*! + \brief User tried to execute modem-exclusive method on a wrong modem. + For example, this can happen when you try to change LoRa configuration when FSK modem is active. +*/ +#define RADIOLIB_ERR_WRONG_MODEM (-20) + +/*! + \brief The supplied number of RSSI samples is invalid. +*/ +#define RADIOLIB_ERR_INVALID_NUM_SAMPLES (-21) + +/*! + \brief The supplied RSSI offset is invalid. +*/ +#define RADIOLIB_ERR_INVALID_RSSI_OFFSET (-22) + +/*! + \brief The supplied encoding is invalid. +*/ +#define RADIOLIB_ERR_INVALID_ENCODING (-23) + +/*! + \brief LoRa packet header has been damaged. +*/ +#define RADIOLIB_ERR_LORA_HEADER_DAMAGED (-24) + +/*! + \brief The requested functionality is not supported for this device +*/ +#define RADIOLIB_ERR_UNSUPPORTED (-25) + +/*! + \brief The specified DIO pin does not exist on this device +*/ +#define RADIOLIB_ERR_INVALID_DIO_PIN (-26) + +/*! + \brief The supplied RSSI threshold is invalid. +*/ +#define RADIOLIB_ERR_INVALID_RSSI_THRESHOLD (-27) + +/*! + \brief A `NULL` pointer has been encountered. If you see this, there may be a potential security vulnerability. +*/ +#define RADIOLIB_ERR_NULL_POINTER (-28) + +/*! + \brief The requested IRQ configuration is not valid for this module. +*/ +#define RADIOLIB_ERR_INVALID_IRQ (-29) + +// RF69-specific status codes + +/*! + \brief The supplied bit rate value is invalid. +*/ +#define RADIOLIB_ERR_INVALID_BIT_RATE (-101) + +/*! + \brief The supplied frequency deviation value is invalid. +*/ +#define RADIOLIB_ERR_INVALID_FREQUENCY_DEVIATION (-102) + +/*! + \brief The supplied bit rate to bandwidth ratio is invalid. See the module datasheet for more information. +*/ +#define RADIOLIB_ERR_INVALID_BIT_RATE_BW_RATIO (-103) + +/*! + \brief The supplied receiver bandwidth value is invalid. +*/ +#define RADIOLIB_ERR_INVALID_RX_BANDWIDTH (-104) + +/*! + \brief The supplied FSK sync word is invalid. +*/ +#define RADIOLIB_ERR_INVALID_SYNC_WORD (-105) + +/*! + \brief The supplied FSK data shaping option is invalid. +*/ +#define RADIOLIB_ERR_INVALID_DATA_SHAPING (-106) + +/*! + \brief The current modulation is invalid for the requested operation. +*/ +#define RADIOLIB_ERR_INVALID_MODULATION (-107) + +/*! + \brief Supplied Peak type is invalid. +*/ +#define RADIOLIB_ERR_INVALID_OOK_RSSI_PEAK_TYPE (-108) + +/*! + \brief Supplied Bitrate tolerance value is out of Range. +*/ +#define RADIOLIB_ERR_INVALID_BIT_RATE_TOLERANCE_VALUE (-109) + +// APRS status codes + +/*! + \brief Supplied APRS symbol is invalid. +*/ +#define RADIOLIB_ERR_INVALID_SYMBOL (-201) + +/*! + \brief Mic-E Telemetry is invalid. +*/ +#define RADIOLIB_ERR_INVALID_MIC_E_TELEMETRY (-202) + +/*! + \brief Mic-E Telemetry length is invalid (only 0, 2 or 5 is allowed). +*/ +#define RADIOLIB_ERR_INVALID_MIC_E_TELEMETRY_LENGTH (-203) + +/*! + \brief Mic-E message cannot contain both telemetry and status text. +*/ +#define RADIOLIB_ERR_MIC_E_TELEMETRY_STATUS (-204) + +// SSDV status codes + +/*! + \brief SSDV mode is invalid. +*/ +#define RADIOLIB_ERR_INVALID_SSDV_MODE (-301) + +/*! + \brief Image size is invalid. +*/ +#define RADIOLIB_ERR_INVALID_IMAGE_SIZE (-302) + +/*! + \brief Image quality is invalid. +*/ +#define RADIOLIB_ERR_INVALID_IMAGE_QUALITY (-303) + +/*! + \brief Image subsampling is invalid. +*/ +#define RADIOLIB_ERR_INVALID_SUBSAMPLING (-304) + +// RTTY status codes + +/*! + \brief Supplied RTTY frequency shift is invalid for this module. +*/ +#define RADIOLIB_ERR_INVALID_RTTY_SHIFT (-401) + +/*! + \brief Supplied RTTY encoding is invalid. +*/ +#define RADIOLIB_ERR_UNSUPPORTED_ENCODING (-402) + +// nRF24-specific status codes + +/*! + \brief Supplied data rate is invalid. +*/ +#define RADIOLIB_ERR_INVALID_DATA_RATE (-501) + +/*! + \brief Supplied address width is invalid. +*/ +#define RADIOLIB_ERR_INVALID_ADDRESS_WIDTH (-502) + +/*! + \brief Supplied data pipe number is invalid. +*/ +#define RADIOLIB_ERR_INVALID_PIPE_NUMBER (-503) + +/*! + \brief ACK packet from destination module was not received within 15 retries. +*/ +#define RADIOLIB_ERR_ACK_NOT_RECEIVED (-504) + +// CC1101-specific status codes + +/*! + \brief Supplied number of broadcast addresses is invalid. +*/ +#define RADIOLIB_ERR_INVALID_NUM_BROAD_ADDRS (-601) + +// SX126x-specific status codes + +/*! + \brief Supplied CRC configuration is invalid. +*/ +#define RADIOLIB_ERR_INVALID_CRC_CONFIGURATION (-701) + +/*! + \brief Detected LoRa transmission while scanning channel. +*/ +#define RADIOLIB_LORA_DETECTED (-702) + +/*! + \brief Supplied TCXO reference voltage is invalid. +*/ +#define RADIOLIB_ERR_INVALID_TCXO_VOLTAGE (-703) + +/*! + \brief Bit rate / bandwidth / frequency deviation ratio is invalid. See SX126x datasheet for details. +*/ +#define RADIOLIB_ERR_INVALID_MODULATION_PARAMETERS (-704) + +/*! + \brief SX126x timed out while waiting for complete SPI command. +*/ +#define RADIOLIB_ERR_SPI_CMD_TIMEOUT (-705) + +/*! + \brief SX126x received invalid SPI command. +*/ +#define RADIOLIB_ERR_SPI_CMD_INVALID (-706) + +/*! + \brief SX126x failed to execute SPI command. + Often this means that the module is trying to use TCXO while + XTAL is connected (or vice versa). Make sure your crystal setup + (e.g. TCXO reference voltage) matches your hardware by setting + "tcxoVoltage" to 0 when using XTAL module, or to appropriate value + when using TCXO module. +*/ +#define RADIOLIB_ERR_SPI_CMD_FAILED (-707) + +/*! + \brief The supplied sleep period is invalid. + + The specified sleep period is shorter than the time necessary to sleep and wake the hardware + including TCXO delay, or longer than the maximum possible +*/ +#define RADIOLIB_ERR_INVALID_SLEEP_PERIOD (-708) + +/*! + \brief The supplied Rx period is invalid. + + The specified Rx period is shorter or longer than the hardware can handle. +*/ +#define RADIOLIB_ERR_INVALID_RX_PERIOD (-709) + +// AX.25-specific status codes + +/*! + \brief The provided callsign is invalid. + + The specified callsign is longer than 6 ASCII characters. +*/ +#define RADIOLIB_ERR_INVALID_CALLSIGN (-801) + +/*! + \brief The provided repeater configuration is invalid. + + The specified number of repeaters does not match number of repeater IDs or their callsigns. +*/ +#define RADIOLIB_ERR_INVALID_NUM_REPEATERS (-802) + +/*! + \brief One of the provided repeater callsigns is invalid. + + The specified callsign is longer than 6 ASCII characters. +*/ +#define RADIOLIB_ERR_INVALID_REPEATER_CALLSIGN (-803) + +// SX128x-specific status codes + +/*! + \brief Timed out waiting for ranging exchange finish. +*/ +#define RADIOLIB_ERR_RANGING_TIMEOUT (-901) + +// Pager-specific status codes + +/*! + \brief The provided payload data configuration is invalid. +*/ +#define RADIOLIB_ERR_INVALID_PAYLOAD (-1001) + +/*! + \brief The requested address was not found in the received data. +*/ +#define RADIOLIB_ERR_ADDRESS_NOT_FOUND (-1002) + +/*! + \brief The function code is invalid. 2 Bits only. +*/ +#define RADIOLIB_ERR_INVALID_FUNCTION (-1003) + +// LoRaWAN-specific status codes + +/*! + \brief Unable to restore existing LoRaWAN session because this node did not join any network yet. +*/ +#define RADIOLIB_ERR_NETWORK_NOT_JOINED (-1101) + +/*! + \brief Malformed downlink packet received from network server. +*/ +#define RADIOLIB_ERR_DOWNLINK_MALFORMED (-1102) + +/*! + \brief Network server requested switch to unsupported LoRaWAN revision. +*/ +#define RADIOLIB_ERR_INVALID_REVISION (-1103) + +/*! + \brief Invalid LoRaWAN uplink port requested by user, or downlink received at invalid port. +*/ +#define RADIOLIB_ERR_INVALID_PORT (-1104) + +/*! + \brief User did not enable downlink in time. +*/ +#define RADIOLIB_ERR_NO_RX_WINDOW (-1105) + +/*! + \brief There are no channels available for the requested datarate. +*/ +#define RADIOLIB_ERR_NO_CHANNEL_AVAILABLE (-1106) + +/*! + \brief Invalid LoRaWAN MAC command ID. +*/ +#define RADIOLIB_ERR_INVALID_CID (-1107) + +/*! + \brief User requested to start uplink while still inside RX window or under dutycycle. +*/ +#define RADIOLIB_ERR_UPLINK_UNAVAILABLE (-1108) + +/*! + \brief Unable to push new MAC command because the queue is full. +*/ +#define RADIOLIB_ERR_COMMAND_QUEUE_FULL (-1109) + +/*! + \brief Unable to delete MAC command because it was not found in the queue. +*/ +#define RADIOLIB_ERR_COMMAND_QUEUE_ITEM_NOT_FOUND (-1110) + +/*! + \brief Unable to join network because JoinNonce is not higher than saved value. +*/ +#define RADIOLIB_ERR_JOIN_NONCE_INVALID (-1111) + +/*! + \brief Received downlink Network frame counter is invalid (lower than last heard value). +*/ +#define RADIOLIB_ERR_N_FCNT_DOWN_INVALID (-1112) + +/*! + \brief Received downlink Application frame counter is invalid (lower than last heard value). +*/ +#define RADIOLIB_ERR_A_FCNT_DOWN_INVALID (-1113) + +/*! + \brief Uplink payload length at this datarate exceeds the active dwell time limitations. +*/ +#define RADIOLIB_ERR_DWELL_TIME_EXCEEDED (-1114) + +/*! + \brief The buffer integrity check did not match the supplied checksum value. +*/ +#define RADIOLIB_ERR_CHECKSUM_MISMATCH (-1115) + +/*! + \brief No JoinAccept was received - check your keys, or otherwise likely a range issue! +*/ +#define RADIOLIB_ERR_NO_JOIN_ACCEPT (-1116) + +/*! + \brief The LoRaWAN session was successfully re-activated. +*/ +#define RADIOLIB_LORAWAN_SESSION_RESTORED (-1117) + +/*! + \brief A new LoRaWAN session is started. +*/ +#define RADIOLIB_LORAWAN_NEW_SESSION (-1118) + +/*! + \brief The supplied Nonces buffer is discarded as its activation information is invalid. +*/ +#define RADIOLIB_ERR_NONCES_DISCARDED (-1119) + +/*! + \brief The supplied Session buffer is discarded as it doesn't match the Nonces. +*/ +#define RADIOLIB_ERR_SESSION_DISCARDED (-1120) + +/*! + \brief The requested command is unavailable under the current LoRaWAN mode. +*/ +#define RADIOLIB_ERR_INVALID_MODE (-1121) + +// LR11x0-specific status codes + +/*! + \brief The selected 802.11 WiFi type is invalid. +*/ +#define RADIOLIB_ERR_INVALID_WIFI_TYPE (-1200) + +/*! + \brief GNSS subframe not available in the next 2.3 seconds. +*/ +#define RADIOLIB_ERR_GNSS_SUBFRAME_NOT_AVAILABLE (-1201) + +/*! + \brief Offset of GNSS demodulator errors. + See LR11x0 datasheet for details on the actual demodulator error +*/ +#define RADIOLIB_ERR_GNSS_DEMOD_OFFSET (-1210) +#define RADIOLIB_ERR_GNSS_DEMOD(X) (RADIOLIB_ERR_GNSS_DEMOD_OFFSET + (X)) +#define RADIOLIB_GET_GNSS_DEMOD_ERROR(X) ((X) - RADIOLIB_ERR_GNSS_DEMOD_OFFSET) + +/*! + \brief GNSS solver errors. + See LR11x0 datasheet for details on the actual solver error +*/ +#define RADIOLIB_ERR_GNSS_SOLVER_OFFSET (-1230) +#define RADIOLIB_ERR_GNSS_SOLVER(X) (RADIOLIB_ERR_GNSS_SOLVER_OFFSET - (X)) +#define RADIOLIB_GET_GNSS_SOLVER_ERROR(X) (-((X) - RADIOLIB_ERR_GNSS_SOLVER_OFFSET)) + +/*! + \} +*/ + +/*! + \defgroup typedefs Type aliases used by RadioLib. + + \{ +*/ + +/*! + \brief Type used for durations in RadioLib +*/ +typedef unsigned long RadioLibTime_t; + +/*! + \brief Type used for radio-agnostic IRQ flags. IRQ to enable corresponds to the bit index (RadioLibIrq_t). + For example, if bit 0 is set, the module will enable its RADIOLIB_IRQ_TX_DONE (if it is supported). +*/ +typedef uint32_t RadioLibIrqFlags_t; + +/*! + \} +*/ + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/hal/Arduino/ArduinoHal.cpp b/software/firmware/source/libraries/RadioLib/src/hal/Arduino/ArduinoHal.cpp new file mode 100644 index 000000000..a76175bd4 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/hal/Arduino/ArduinoHal.cpp @@ -0,0 +1,209 @@ +#include "ArduinoHal.h" + +#if defined(RADIOLIB_BUILD_ARDUINO) + +ArduinoHal::ArduinoHal(): RadioLibHal(INPUT, OUTPUT, LOW, HIGH, RISING, FALLING), spi(&RADIOLIB_DEFAULT_SPI), initInterface(true) {} + +ArduinoHal::ArduinoHal(SPIClass& spi, SPISettings spiSettings): RadioLibHal(INPUT, OUTPUT, LOW, HIGH, RISING, FALLING), spi(&spi), spiSettings(spiSettings) {} + +void ArduinoHal::init() { + if(initInterface) { + spiBegin(); + } +} + +void ArduinoHal::term() { + if(initInterface) { + spiEnd(); + } +} + +void inline ArduinoHal::pinMode(uint32_t pin, uint32_t mode) { + if(pin == RADIOLIB_NC) { + return; + } + ::pinMode(pin, RADIOLIB_ARDUINOHAL_PIN_MODE_CAST mode); +} + +void inline ArduinoHal::digitalWrite(uint32_t pin, uint32_t value) { + if(pin == RADIOLIB_NC) { + return; + } + ::digitalWrite(pin, RADIOLIB_ARDUINOHAL_PIN_STATUS_CAST value); +} + +uint32_t inline ArduinoHal::digitalRead(uint32_t pin) { + if(pin == RADIOLIB_NC) { + return 0; + } + return(::digitalRead(pin)); +} + +void inline ArduinoHal::attachInterrupt(uint32_t interruptNum, void (*interruptCb)(void), uint32_t mode) { + if(interruptNum == RADIOLIB_NC) { + return; + } +#if defined(ARDUINO_ARCH_CH32) + ::attachInterrupt(interruptNum, GPIO_Mode_IN_FLOATING, interruptCb, EXTI_Mode_Interrupt, + mode == RISING ? EXTI_Trigger_Rising : + mode == FALLING ? EXTI_Trigger_Falling : + EXTI_Trigger_Rising_Falling /* CHANGE */ ); +#else + ::attachInterrupt(interruptNum, interruptCb, RADIOLIB_ARDUINOHAL_INTERRUPT_MODE_CAST mode); +#endif /* ARDUINO_ARCH_CH32 */ +} + +void inline ArduinoHal::detachInterrupt(uint32_t interruptNum) { + if(interruptNum == RADIOLIB_NC) { + return; + } + ::detachInterrupt(interruptNum); +} + +void inline ArduinoHal::delay(RadioLibTime_t ms) { +#if !defined(RADIOLIB_CLOCK_DRIFT_MS) + ::delay(ms); +#else + ::delay(ms * 1000 / (1000 + RADIOLIB_CLOCK_DRIFT_MS)); +#endif +} + +void inline ArduinoHal::delayMicroseconds(RadioLibTime_t us) { +#if !defined(RADIOLIB_CLOCK_DRIFT_MS) + ::delayMicroseconds(us); +#else + ::delayMicroseconds(us * 1000 / (1000 + RADIOLIB_CLOCK_DRIFT_MS)); +#endif +} + +RadioLibTime_t inline ArduinoHal::millis() { +#if !defined(RADIOLIB_CLOCK_DRIFT_MS) + return(::millis()); +#else + return(::millis() * 1000 / (1000 + RADIOLIB_CLOCK_DRIFT_MS)); +#endif +} + +RadioLibTime_t inline ArduinoHal::micros() { +#if !defined(RADIOLIB_CLOCK_DRIFT_MS) + return(::micros()); +#else + return(::micros() * 1000 / (1000 + RADIOLIB_CLOCK_DRIFT_MS)); +#endif +} + +long inline ArduinoHal::pulseIn(uint32_t pin, uint32_t state, RadioLibTime_t timeout) { + if(pin == RADIOLIB_NC) { + return 0; + } + return(::pulseIn(pin, state, timeout)); +} + +void inline ArduinoHal::spiBegin() { + spi->begin(); +} + +void inline ArduinoHal::spiBeginTransaction() { + spi->beginTransaction(spiSettings); +} + +void ArduinoHal::spiTransfer(uint8_t* out, size_t len, uint8_t* in) { + for(size_t i = 0; i < len; i++) { + in[i] = spi->transfer(out[i]); + } +} + +void inline ArduinoHal::spiEndTransaction() { + spi->endTransaction(); +} + +void inline ArduinoHal::spiEnd() { + spi->end(); +} + +void inline ArduinoHal::tone(uint32_t pin, unsigned int frequency, RadioLibTime_t duration) { + #if !defined(RADIOLIB_TONE_UNSUPPORTED) + if(pin == RADIOLIB_NC) { + return; + } + ::tone(pin, frequency, duration); + #elif defined(RADIOLIB_ESP32) + // ESP32 tone() emulation + (void)duration; + if(prev == -1) { + #if !defined(ESP_IDF_VERSION) || (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5,0,0)) + ledcAttachPin(pin, RADIOLIB_TONE_ESP32_CHANNEL); + #else + ledcAttach(pin, frequency, 14); // 14-bit resolution should be enough + #endif + } + if(prev != frequency) { + #if !defined(ESP_IDF_VERSION) || (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5,0,0)) + ledcWriteTone(RADIOLIB_TONE_ESP32_CHANNEL, frequency); + #else + ledcWriteTone(pin, frequency); + #endif + } + prev = frequency; + #elif defined(RADIOLIB_MBED_TONE_OVERRIDE) + // better tone for mbed OS boards + (void)duration; + if(!pwmPin) { + pwmPin = new mbed::PwmOut(digitalPinToPinName(pin)); + } + pwmPin->period(1.0 / frequency); + pwmPin->write(0.5); + #else + (void)pin; + (void)frequency; + (void)duration; + #endif +} + +void inline ArduinoHal::noTone(uint32_t pin) { + #if !defined(RADIOLIB_TONE_UNSUPPORTED) and defined(ARDUINO_ARCH_STM32) + if(pin == RADIOLIB_NC) { + return; + } + ::noTone(pin, false); + #elif !defined(RADIOLIB_TONE_UNSUPPORTED) + if(pin == RADIOLIB_NC) { + return; + } + ::noTone(pin); + #elif defined(RADIOLIB_ESP32) + if(pin == RADIOLIB_NC) { + return; + } + // ESP32 tone() emulation + #if !defined(ESP_IDF_VERSION) || (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5,0,0)) + ledcDetachPin(pin); + ledcWrite(RADIOLIB_TONE_ESP32_CHANNEL, 0); + #else + ledcDetach(pin); + ledcWrite(pin, 0); + #endif + prev = -1; + #elif defined(RADIOLIB_MBED_TONE_OVERRIDE) + if(pin == RADIOLIB_NC) { + return; + } + // better tone for mbed OS boards + (void)pin; + pwmPin->suspend(); + #else + (void)pin; + #endif +} + +void inline ArduinoHal::yield() { + #if !defined(RADIOLIB_YIELD_UNSUPPORTED) + ::yield(); + #endif +} + +uint32_t inline ArduinoHal::pinToInterrupt(uint32_t pin) { + return(digitalPinToInterrupt(pin)); +} + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/hal/Arduino/ArduinoHal.h b/software/firmware/source/libraries/RadioLib/src/hal/Arduino/ArduinoHal.h new file mode 100644 index 000000000..17efeec1c --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/hal/Arduino/ArduinoHal.h @@ -0,0 +1,80 @@ +// make sure this is always compiled +#include "../../TypeDef.h" + +#if !defined(_RADIOLIB_ARDUINOHAL_H) +#define _RADIOLIB_ARDUINOHAL_H + +// this file only makes sense for Arduino builds +#if defined(RADIOLIB_BUILD_ARDUINO) + +#if defined(RADIOLIB_MBED_TONE_OVERRIDE) +#include "mbed.h" +#endif + +#include "Hal.h" + +#include + +/*! + \class ArduinoHal + \brief Arduino default hardware abstraction library implementation. + This class can be extended to support other Arduino platform or change behaviour of the default implementation. +*/ +class ArduinoHal : public RadioLibHal { + public: + /*! + \brief Arduino Hal constructor. Will use the default SPI interface and automatically initialize it. + */ + ArduinoHal(); + + /*! + \brief Arduino Hal constructor. Will not attempt SPI interface initialization. + \param spi SPI interface to be used, can also use software SPI implementations. + \param spiSettings SPI interface settings. + */ + explicit ArduinoHal(SPIClass& spi, SPISettings spiSettings = RADIOLIB_DEFAULT_SPI_SETTINGS); + + // implementations of pure virtual RadioLibHal methods + void pinMode(uint32_t pin, uint32_t mode) override; + void digitalWrite(uint32_t pin, uint32_t value) override; + uint32_t digitalRead(uint32_t pin) override; + void attachInterrupt(uint32_t interruptNum, void (*interruptCb)(void), uint32_t mode) override; + void detachInterrupt(uint32_t interruptNum) override; + void delay(RadioLibTime_t ms) override; + void delayMicroseconds(RadioLibTime_t us) override; + RadioLibTime_t millis() override; + RadioLibTime_t micros() override; + long pulseIn(uint32_t pin, uint32_t state, RadioLibTime_t timeout) override; + void spiBegin() override; + void spiBeginTransaction() override; + void spiTransfer(uint8_t* out, size_t len, uint8_t* in) override; + void spiEndTransaction() override; + void spiEnd() override; + + // implementations of virtual RadioLibHal methods + void init() override; + void term() override; + void tone(uint32_t pin, unsigned int frequency, RadioLibTime_t duration = 0) override; + void noTone(uint32_t pin) override; + void yield() override; + uint32_t pinToInterrupt(uint32_t pin) override; + +#if !RADIOLIB_GODMODE + protected: +#endif + SPIClass* spi = NULL; + SPISettings spiSettings = RADIOLIB_DEFAULT_SPI_SETTINGS; + bool initInterface = false; + + #if defined(RADIOLIB_MBED_TONE_OVERRIDE) + mbed::PwmOut *pwmPin = NULL; + #endif + + #if defined(RADIOLIB_ESP32) + int32_t prev = -1; + #endif +}; + +#endif + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/hal/ESP-IDF/EspHal.h b/software/firmware/source/libraries/RadioLib/src/hal/ESP-IDF/EspHal.h new file mode 100644 index 000000000..340adbd63 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/hal/ESP-IDF/EspHal.h @@ -0,0 +1,322 @@ +#ifndef ESP_HAL_H +#define ESP_HAL_H + +// include RadioLib +#include + +// this example only works on ESP32 and is unlikely to work on ESP32S2/S3 etc. +// if you need high portability, you should probably use Arduino anyway ... +#if CONFIG_IDF_TARGET_ESP32 == 0 + #error Target is not ESP32! +#endif + +// include all the dependencies +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp32/rom/gpio.h" +#include "soc/rtc.h" +#include "soc/dport_reg.h" +#include "soc/spi_reg.h" +#include "soc/spi_struct.h" +#include "driver/gpio.h" +#include "hal/gpio_hal.h" +#include "esp_timer.h" +#include "esp_log.h" + +// define Arduino-style macros +#define LOW (0x0) +#define HIGH (0x1) +#define INPUT (0x01) +#define OUTPUT (0x03) +#define RISING (0x01) +#define FALLING (0x02) +#define NOP() asm volatile ("nop") + +#define MATRIX_DETACH_OUT_SIG (0x100) +#define MATRIX_DETACH_IN_LOW_PIN (0x30) + +// all of the following is needed to calculate SPI clock divider +#define ClkRegToFreq(reg) (apb_freq / (((reg)->clkdiv_pre + 1) * ((reg)->clkcnt_n + 1))) + +typedef union { + uint32_t value; + struct { + uint32_t clkcnt_l: 6; + uint32_t clkcnt_h: 6; + uint32_t clkcnt_n: 6; + uint32_t clkdiv_pre: 13; + uint32_t clk_equ_sysclk: 1; + }; +} spiClk_t; + +uint32_t getApbFrequency() { + rtc_cpu_freq_config_t conf; + rtc_clk_cpu_freq_get_config(&conf); + + if(conf.freq_mhz >= 80) { + return(80 * MHZ); + } + + return((conf.source_freq_mhz * MHZ) / conf.div); +} + +uint32_t spiFrequencyToClockDiv(uint32_t freq) { + uint32_t apb_freq = getApbFrequency(); + if(freq >= apb_freq) { + return SPI_CLK_EQU_SYSCLK; + } + + const spiClk_t minFreqReg = { 0x7FFFF000 }; + uint32_t minFreq = ClkRegToFreq((spiClk_t*) &minFreqReg); + if(freq < minFreq) { + return minFreqReg.value; + } + + uint8_t calN = 1; + spiClk_t bestReg = { 0 }; + int32_t bestFreq = 0; + while(calN <= 0x3F) { + spiClk_t reg = { 0 }; + int32_t calFreq; + int32_t calPre; + int8_t calPreVari = -2; + + reg.clkcnt_n = calN; + + while(calPreVari++ <= 1) { + calPre = (((apb_freq / (reg.clkcnt_n + 1)) / freq) - 1) + calPreVari; + if(calPre > 0x1FFF) { + reg.clkdiv_pre = 0x1FFF; + } else if(calPre <= 0) { + reg.clkdiv_pre = 0; + } else { + reg.clkdiv_pre = calPre; + } + reg.clkcnt_l = ((reg.clkcnt_n + 1) / 2); + calFreq = ClkRegToFreq(®); + if(calFreq == (int32_t) freq) { + memcpy(&bestReg, ®, sizeof(bestReg)); + break; + } else if(calFreq < (int32_t) freq) { + if(RADIOLIB_ABS(freq - calFreq) < RADIOLIB_ABS(freq - bestFreq)) { + bestFreq = calFreq; + memcpy(&bestReg, ®, sizeof(bestReg)); + } + } + } + if(calFreq == (int32_t) freq) { + break; + } + calN++; + } + return(bestReg.value); +} + +// create a new ESP-IDF hardware abstraction layer +// the HAL must inherit from the base RadioLibHal class +// and implement all of its virtual methods +// this is pretty much just copied from Arduino ESP32 core +class EspHal : public RadioLibHal { + public: + // default constructor - initializes the base HAL and any needed private members + EspHal(int8_t sck, int8_t miso, int8_t mosi) + : RadioLibHal(INPUT, OUTPUT, LOW, HIGH, RISING, FALLING), + spiSCK(sck), spiMISO(miso), spiMOSI(mosi) { + } + + void init() override { + // we only need to init the SPI here + spiBegin(); + } + + void term() override { + // we only need to stop the SPI here + spiEnd(); + } + + // GPIO-related methods (pinMode, digitalWrite etc.) should check + // RADIOLIB_NC as an alias for non-connected pins + void pinMode(uint32_t pin, uint32_t mode) override { + if(pin == RADIOLIB_NC) { + return; + } + + gpio_hal_context_t gpiohal; + gpiohal.dev = GPIO_LL_GET_HW(GPIO_PORT_0); + + gpio_config_t conf = { + .pin_bit_mask = (1ULL<pin[pin].int_type, + }; + gpio_config(&conf); + } + + void digitalWrite(uint32_t pin, uint32_t value) override { + if(pin == RADIOLIB_NC) { + return; + } + + gpio_set_level((gpio_num_t)pin, value); + } + + uint32_t digitalRead(uint32_t pin) override { + if(pin == RADIOLIB_NC) { + return(0); + } + + return(gpio_get_level((gpio_num_t)pin)); + } + + void attachInterrupt(uint32_t interruptNum, void (*interruptCb)(void), uint32_t mode) override { + if(interruptNum == RADIOLIB_NC) { + return; + } + + gpio_install_isr_service((int)ESP_INTR_FLAG_IRAM); + gpio_set_intr_type((gpio_num_t)interruptNum, (gpio_int_type_t)(mode & 0x7)); + + // this uses function typecasting, which is not defined when the functions have different signatures + // untested and might not work + gpio_isr_handler_add((gpio_num_t)interruptNum, (void (*)(void*))interruptCb, NULL); + } + + void detachInterrupt(uint32_t interruptNum) override { + if(interruptNum == RADIOLIB_NC) { + return; + } + + gpio_isr_handler_remove((gpio_num_t)interruptNum); + gpio_wakeup_disable((gpio_num_t)interruptNum); + gpio_set_intr_type((gpio_num_t)interruptNum, GPIO_INTR_DISABLE); + } + + void delay(unsigned long ms) override { + vTaskDelay(ms / portTICK_PERIOD_MS); + } + + void delayMicroseconds(unsigned long us) override { + uint64_t m = (uint64_t)esp_timer_get_time(); + if(us) { + uint64_t e = (m + us); + if(m > e) { // overflow + while((uint64_t)esp_timer_get_time() > e) { + NOP(); + } + } + while((uint64_t)esp_timer_get_time() < e) { + NOP(); + } + } + } + + unsigned long millis() override { + return((unsigned long)(esp_timer_get_time() / 1000ULL)); + } + + unsigned long micros() override { + return((unsigned long)(esp_timer_get_time())); + } + + long pulseIn(uint32_t pin, uint32_t state, unsigned long timeout) override { + if(pin == RADIOLIB_NC) { + return(0); + } + + this->pinMode(pin, INPUT); + uint32_t start = this->micros(); + uint32_t curtick = this->micros(); + + while(this->digitalRead(pin) == state) { + if((this->micros() - curtick) > timeout) { + return(0); + } + } + + return(this->micros() - start); + } + + void spiBegin() { + // enable peripheral + DPORT_SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_SPI2_CLK_EN); + DPORT_CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_SPI2_RST); + + // reset the control struct + this->spi->slave.trans_done = 0; + this->spi->slave.val = 0; + this->spi->pin.val = 0; + this->spi->user.val = 0; + this->spi->user1.val = 0; + this->spi->ctrl.val = 0; + this->spi->ctrl1.val = 0; + this->spi->ctrl2.val = 0; + this->spi->clock.val = 0; + this->spi->user.usr_mosi = 1; + this->spi->user.usr_miso = 1; + this->spi->user.doutdin = 1; + for(uint8_t i = 0; i < 16; i++) { + this->spi->data_buf[i] = 0x00000000; + } + + // set SPI mode 0 + this->spi->pin.ck_idle_edge = 0; + this->spi->user.ck_out_edge = 0; + + // set bit order to MSB first + this->spi->ctrl.wr_bit_order = 0; + this->spi->ctrl.rd_bit_order = 0; + + // set the clock + this->spi->clock.val = spiFrequencyToClockDiv(2000000); + + // initialize pins + this->pinMode(this->spiSCK, OUTPUT); + this->pinMode(this->spiMISO, INPUT); + this->pinMode(this->spiMOSI, OUTPUT); + gpio_matrix_out(this->spiSCK, HSPICLK_OUT_IDX, false, false); + gpio_matrix_in(this->spiMISO, HSPIQ_OUT_IDX, false); + gpio_matrix_out(this->spiMOSI, HSPID_IN_IDX, false, false); + } + + void spiBeginTransaction() { + // not needed - in ESP32 Arduino core, this function + // repeats clock div, mode and bit order configuration + } + + uint8_t spiTransferByte(uint8_t b) { + this->spi->mosi_dlen.usr_mosi_dbitlen = 7; + this->spi->miso_dlen.usr_miso_dbitlen = 7; + this->spi->data_buf[0] = b; + this->spi->cmd.usr = 1; + while(this->spi->cmd.usr); + return(this->spi->data_buf[0] & 0xFF); + } + + void spiTransfer(uint8_t* out, size_t len, uint8_t* in) { + for(size_t i = 0; i < len; i++) { + in[i] = this->spiTransferByte(out[i]); + } + } + + void spiEndTransaction() { + // nothing needs to be done here + } + + void spiEnd() { + // detach pins + gpio_matrix_out(this->spiSCK, MATRIX_DETACH_OUT_SIG, false, false); + gpio_matrix_in(this->spiMISO, MATRIX_DETACH_IN_LOW_PIN, false); + gpio_matrix_out(this->spiMOSI, MATRIX_DETACH_OUT_SIG, false, false); + } + + private: + // the HAL can contain any additional private members + int8_t spiSCK; + int8_t spiMISO; + int8_t spiMOSI; + spi_dev_t * spi = (volatile spi_dev_t *)(DR_REG_SPI2_BASE); +}; + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/hal/RPi/PiHal.h b/software/firmware/source/libraries/RadioLib/src/hal/RPi/PiHal.h new file mode 100644 index 000000000..76a6389b4 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/hal/RPi/PiHal.h @@ -0,0 +1,261 @@ +#ifndef PI_HAL_LGPIO_H +#define PI_HAL_LGPIO_H + +// include RadioLib +#include + +// include the library for Raspberry GPIO pins +#include + +#define PI_RISING (LG_RISING_EDGE) +#define PI_FALLING (LG_FALLING_EDGE) +#define PI_INPUT (0) +#define PI_OUTPUT (1) +#define PI_MAX_USER_GPIO (31) + +// forward declaration of alert handler that will be used to emulate interrupts +static void lgpioAlertHandler(int num_alerts, lgGpioAlert_p alerts, void *userdata); + +// create a new Raspberry Pi hardware abstraction layer +// using the lgpio library +// the HAL must inherit from the base RadioLibHal class +// and implement all of its virtual methods +class PiHal : public RadioLibHal { + public: + // default constructor - initializes the base HAL and any needed private members + PiHal(uint8_t spiChannel, uint32_t spiSpeed = 2000000, uint8_t spiDevice = 0, uint8_t gpioDevice = 0) + : RadioLibHal(PI_INPUT, PI_OUTPUT, LG_LOW, LG_HIGH, PI_RISING, PI_FALLING), + _gpioDevice(gpioDevice), + _spiDevice(spiDevice), + _spiChannel(spiChannel), + _spiSpeed(spiSpeed) { + } + + void init() override { + if(_gpioHandle != -1) { + return; + } + + // first initialise lgpio library + if((_gpioHandle = lgGpiochipOpen(_gpioDevice)) < 0) { + fprintf(stderr, "Could not open GPIO chip: %s\n", lguErrorText(_gpioHandle)); + return; + } + + // now the SPI + spiBegin(); + } + + void term() override { + // stop the SPI + spiEnd(); + + // finally, stop the lgpio library + lgGpiochipClose(_gpioHandle); + _gpioHandle = -1; + } + + // GPIO-related methods (pinMode, digitalWrite etc.) should check + // RADIOLIB_NC as an alias for non-connected pins + void pinMode(uint32_t pin, uint32_t mode) override { + if(pin == RADIOLIB_NC) { + return; + } + + int result; + int flags = 0; + switch(mode) { + case PI_INPUT: + result = lgGpioClaimInput(_gpioHandle, 0, pin); + break; + case PI_OUTPUT: + result = lgGpioClaimOutput(_gpioHandle, flags, pin, LG_HIGH); + break; + default: + fprintf(stderr, "Unknown pinMode mode %" PRIu32 "\n", mode); + return; + } + + if(result < 0) { + fprintf(stderr, "Could not claim pin %" PRIu32 " for mode %" PRIu32 ": %s\n", + pin, mode, lguErrorText(result)); + } + } + + void digitalWrite(uint32_t pin, uint32_t value) override { + if(pin == RADIOLIB_NC) { + return; + } + + int result = lgGpioWrite(_gpioHandle, pin, value); + if(result < 0) { + fprintf(stderr, "Error writing value to pin %" PRIu32 ": %s\n", pin, lguErrorText(result)); + } + } + + uint32_t digitalRead(uint32_t pin) override { + if(pin == RADIOLIB_NC) { + return(0); + } + + int result = lgGpioRead(_gpioHandle, pin); + if(result < 0) { + fprintf(stderr, "Error writing reading from pin %" PRIu32 ": %s\n", pin, lguErrorText(result)); + } + return result; + } + + void attachInterrupt(uint32_t interruptNum, void (*interruptCb)(void), uint32_t mode) override { + if((interruptNum == RADIOLIB_NC) || (interruptNum > PI_MAX_USER_GPIO)) { + return; + } + + // set lgpio alert callback + int result = lgGpioClaimAlert(_gpioHandle, 0, mode, interruptNum, -1); + if(result < 0) { + fprintf(stderr, "Could not claim pin %" PRIu32 " for alert: %s\n", interruptNum, lguErrorText(result)); + return; + } + + // enable emulated interrupt + interruptEnabled[interruptNum] = true; + interruptModes[interruptNum] = mode; + interruptCallbacks[interruptNum] = interruptCb; + + lgGpioSetAlertsFunc(_gpioHandle, interruptNum, lgpioAlertHandler, (void *)this); + } + + void detachInterrupt(uint32_t interruptNum) override { + if((interruptNum == RADIOLIB_NC) || (interruptNum > PI_MAX_USER_GPIO)) { + return; + } + + // clear emulated interrupt + interruptEnabled[interruptNum] = false; + interruptModes[interruptNum] = 0; + interruptCallbacks[interruptNum] = NULL; + + // disable lgpio alert callback + lgGpioFree(_gpioHandle, interruptNum); + lgGpioSetAlertsFunc(_gpioHandle, interruptNum, NULL, NULL); + } + + void delay(unsigned long ms) override { + if(ms == 0) { + sched_yield(); + return; + } + + lguSleep(ms / 1000.0); + } + + void delayMicroseconds(unsigned long us) override { + if(us == 0) { + sched_yield(); + return; + } + + lguSleep(us / 1000000.0); + } + + void yield() override { + sched_yield(); + } + + unsigned long millis() override { + uint32_t time = lguTimestamp() / 1000000UL; + return time; + } + + unsigned long micros() override { + uint32_t time = lguTimestamp() / 1000UL; + return time; + } + + long pulseIn(uint32_t pin, uint32_t state, unsigned long timeout) override { + if(pin == RADIOLIB_NC) { + return(0); + } + + this->pinMode(pin, PI_INPUT); + uint32_t start = this->micros(); + uint32_t curtick = this->micros(); + + while(this->digitalRead(pin) == state) { + if((this->micros() - curtick) > timeout) { + return(0); + } + } + + return(this->micros() - start); + } + + void spiBegin() { + if(_spiHandle < 0) { + if((_spiHandle = lgSpiOpen(_spiDevice, _spiChannel, _spiSpeed, 0)) < 0) { + fprintf(stderr, "Could not open SPI handle on 0: %s\n", lguErrorText(_spiHandle)); + } + } + } + + void spiBeginTransaction() {} + + void spiTransfer(uint8_t* out, size_t len, uint8_t* in) { + int result = lgSpiXfer(_spiHandle, (char *)out, (char*)in, len); + if(result < 0) { + fprintf(stderr, "Could not perform SPI transfer: %s\n", lguErrorText(result)); + } + } + + void spiEndTransaction() {} + + void spiEnd() { + if(_spiHandle >= 0) { + lgSpiClose(_spiHandle); + _spiHandle = -1; + } + } + + void tone(uint32_t pin, unsigned int frequency, unsigned long duration = 0) { + lgTxPwm(_gpioHandle, pin, frequency, 50, 0, duration); + } + + void noTone(uint32_t pin) { + lgTxPwm(_gpioHandle, pin, 0, 0, 0, 0); + } + + // interrupt emulation + bool interruptEnabled[PI_MAX_USER_GPIO + 1]; + uint32_t interruptModes[PI_MAX_USER_GPIO + 1]; + typedef void (*RadioLibISR)(void); + RadioLibISR interruptCallbacks[PI_MAX_USER_GPIO + 1]; + + private: + // the HAL can contain any additional private members + const unsigned int _spiSpeed; + const uint8_t _gpioDevice; + const uint8_t _spiDevice; + const uint8_t _spiChannel; + int _gpioHandle = -1; + int _spiHandle = -1; +}; + +// this handler emulates interrupts +static void lgpioAlertHandler(int num_alerts, lgGpioAlert_p alerts, void *userdata) { + if(!userdata) + return; + + // PiHal instance is passed via the user data + PiHal* hal = (PiHal*)userdata; + + // check the interrupt is enabled, the level matches and a callback exists + for(lgGpioAlert_t *alert = alerts; alert < (alerts + num_alerts); alert++) { + if((hal->interruptEnabled[alert->report.gpio]) && + (hal->interruptModes[alert->report.gpio] == alert->report.level) && + (hal->interruptCallbacks[alert->report.gpio])) { + hal->interruptCallbacks[alert->report.gpio](); + } + } +} + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/hal/RPiPico/PicoHal.h b/software/firmware/source/libraries/RadioLib/src/hal/RPiPico/PicoHal.h new file mode 100644 index 000000000..ec4d12e33 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/hal/RPiPico/PicoHal.h @@ -0,0 +1,198 @@ +#ifndef PICO_HAL_H +#define PICO_HAL_H + +// include RadioLib +#include + +// include the necessary Pico libraries +#include +#include "hardware/spi.h" +#include "hardware/timer.h" +#include "hardware/pwm.h" +#include "hardware/clocks.h" +#include "pico/multicore.h" + +uint32_t toneLoopPin; +unsigned int toneLoopFrequency; +unsigned long toneLoopDuration; + +// pre-calculated pulse-widths for 1200 and 2200Hz +// we do this to save calculation time (see https://github.com/khoih-prog/RP2040_PWM/issues/6) +#define SLEEP_1200 416.666 +#define SLEEP_2200 227.272 + +// === NOTE === +// The tone(...) implementation uses the second core on the RPi Pico. This is to diminish as much +// jitter in the output tones as possible. + +void toneLoop(){ + gpio_set_dir(toneLoopPin, GPIO_OUT); + + uint32_t sleep_dur; + if (toneLoopFrequency == 1200) { + sleep_dur = SLEEP_1200; + } else if (toneLoopFrequency == 2200) { + sleep_dur = SLEEP_2200; + } else { + sleep_dur = 500000 / toneLoopFrequency; + } + + + // tone bitbang + while(1){ + gpio_put(toneLoopPin, 1); + sleep_us(sleep_dur); + gpio_put(toneLoopPin, 0); + sleep_us(sleep_dur); + tight_loop_contents(); + } +} + +// create a new Raspberry Pi Pico hardware abstraction +// layer using the Pico SDK +// the HAL must inherit from the base RadioLibHal class +// and implement all of its virtual methods +class PicoHal : public RadioLibHal { +public: + PicoHal(spi_inst_t *spiChannel, uint32_t misoPin, uint32_t mosiPin, uint32_t sckPin, uint32_t spiSpeed = 500 * 1000) + : RadioLibHal(GPIO_IN, GPIO_OUT, 0, 1, GPIO_IRQ_EDGE_RISE, GPIO_IRQ_EDGE_FALL), + _spiChannel(spiChannel), + _spiSpeed(spiSpeed), + _misoPin(misoPin), + _mosiPin(mosiPin), + _sckPin(sckPin){} + + void init() override { + stdio_init_all(); + spiBegin(); + } + + void term() override { + spiEnd(); + } + + // GPIO-related methods (pinMode, digitalWrite etc.) should check + // RADIOLIB_NC as an alias for non-connected pins + void pinMode(uint32_t pin, uint32_t mode) override { + if (pin == RADIOLIB_NC) { + return; + } + + gpio_init(pin); + gpio_set_dir(pin, mode); + } + + void digitalWrite(uint32_t pin, uint32_t value) override { + if (pin == RADIOLIB_NC) { + return; + } + + gpio_put(pin, (bool)value); + } + + uint32_t digitalRead(uint32_t pin) override { + if (pin == RADIOLIB_NC) { + return 0; + } + + return gpio_get(pin); + } + + void attachInterrupt(uint32_t interruptNum, void (*interruptCb)(void), uint32_t mode) override { + if (interruptNum == RADIOLIB_NC) { + return; + } + + gpio_set_irq_enabled_with_callback(interruptNum, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, true, (gpio_irq_callback_t)interruptCb); + } + + void detachInterrupt(uint32_t interruptNum) override { + if (interruptNum == RADIOLIB_NC) { + return; + } + + gpio_set_irq_enabled_with_callback(interruptNum, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, false, NULL); + } + + void delay(unsigned long ms) override { + sleep_ms(ms); + } + + void delayMicroseconds(unsigned long us) override { + sleep_us(us); + } + + unsigned long millis() override { + return to_ms_since_boot(get_absolute_time()); + } + + unsigned long micros() override { + return to_us_since_boot(get_absolute_time()); + } + + long pulseIn(uint32_t pin, uint32_t state, unsigned long timeout) override { + if (pin == RADIOLIB_NC) { + return 0; + } + + this->pinMode(pin, GPIO_IN); + uint32_t start = this->micros(); + uint32_t curtick = this->micros(); + + while (this->digitalRead(pin) == state) { + if ((this->micros() - curtick) > timeout) { + return 0; + } + } + + return (this->micros() - start); + } + + void tone(uint32_t pin, unsigned int frequency, unsigned long duration = 0) override { + // tones on the Pico are generated using bitbanging. This process is offloaded to the Pico's second core + multicore_reset_core1(); + toneLoopPin = pin; + toneLoopFrequency = frequency; + toneLoopDuration = duration; + multicore_launch_core1(toneLoop); + } + + void noTone(uint32_t pin) override { + multicore_reset_core1(); + } + + void spiBegin() { + spi_init(_spiChannel, _spiSpeed); + spi_set_format(_spiChannel, 8, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST); + + gpio_set_function(_sckPin, GPIO_FUNC_SPI); + gpio_set_function(_mosiPin, GPIO_FUNC_SPI); + gpio_set_function(_misoPin, GPIO_FUNC_SPI); + } + + void spiBeginTransaction() {} + + void spiTransfer(uint8_t *out, size_t len, uint8_t *in) { + spi_write_read_blocking(_spiChannel, out, in, len); + } + + void yield() override { + tight_loop_contents(); + } + + void spiEndTransaction() {} + + void spiEnd() { + spi_deinit(_spiChannel); + } + +private: + // the HAL can contain any additional private members + spi_inst_t *_spiChannel; + uint32_t _spiSpeed; + uint32_t _misoPin; + uint32_t _mosiPin; + uint32_t _sckPin; +}; + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/hal/Tock/libtockHal.h b/software/firmware/source/libraries/RadioLib/src/hal/Tock/libtockHal.h new file mode 100644 index 000000000..7ae8bf2ae --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/hal/Tock/libtockHal.h @@ -0,0 +1,225 @@ +/* + RadioLib Non-Arduino Tock Library helper functions + + Licensed under the MIT License + + Copyright (c) 2023 Alistair Francis + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +#ifndef TOCK_HAL_H +#define TOCK_HAL_H + +// include RadioLib +#include + +// include all the dependencies +#include "libtock/net/lora_phy.h" +#include "libtock/net/syscalls/lora_phy_syscalls.h" +#include "libtock-sync/net/lora_phy.h" +#include "libtock/peripherals/gpio.h" +#include "libtock-sync/services/alarm.h" +#include "libtock/kernel/read_only_state.h" + +#define RADIO_BUSY 1 +#define RADIO_DIO_1 2 +#define RADIO_DIO_3 3 +#define RADIO_RESET 4 +// Skip the chips select as Tock handles this for us +#define RADIO_NSS RADIOLIB_NC + +// define Arduino-style macros +#define PIN_LOW (0x0) +#define PIN_HIGH (0x1) +#define PIN_INPUT (0x01) +#define PIN_OUTPUT (0x03) +#define PIN_RISING (0x01) +#define PIN_FALLING (0x02) + +typedef void (*gpioIrqFn)(void); + +gpioIrqFn gpio_funcs[4] = { NULL, NULL, NULL, NULL}; +uint32_t frequency = 0; + +/* + * Get the the timer frequency in Hz. + */ +int alarm_internal_frequency(uint32_t* frequency) { + syscall_return_t rval = command(0x0, 1, 0, 0); + return tock_command_return_u32_to_returncode(rval, frequency); +} + +int alarm_internal_read(uint32_t* time) { + syscall_return_t rval = command(0x0, 2, 0, 0); + return tock_command_return_u32_to_returncode(rval, time); +} + +static void lora_phy_gpio_Callback (int gpioPin, + __attribute__ ((unused)) int arg2, + __attribute__ ((unused)) int arg3, + void* userdata) +{ + gpioIrqFn fn = gpio_funcs[gpioPin - 1]; + + if (fn != NULL ) { + fn(); + } +} + +class TockHal : public RadioLibHal { + public: + // default constructor - initializes the base HAL and any needed private members + TockHal() + : RadioLibHal(PIN_INPUT, PIN_OUTPUT, PIN_LOW, PIN_HIGH, PIN_RISING, PIN_FALLING) { + } + + void init() override { + } + + void term() override { + } + + // GPIO-related methods (pinMode, digitalWrite etc.) should check + // RADIOLIB_NC as an alias for non-connected pins + void pinMode(uint32_t pin, uint32_t mode) override { + if(pin == RADIOLIB_NC) { + return; + } + + if (mode == PIN_OUTPUT) { + libtock_lora_phy_gpio_enable_output(pin); + } else if (mode == PIN_INPUT) { + libtock_lora_phy_gpio_enable_input(pin, libtock_pull_down); + } + } + + void digitalWrite(uint32_t pin, uint32_t value) override { + if(pin == RADIOLIB_NC) { + return; + } + + if (value) { + libtock_lora_phy_gpio_set(pin); + } else { + libtock_lora_phy_gpio_clear(pin); + } + } + + uint32_t digitalRead(uint32_t pin) override { + int value; + + if(pin == RADIOLIB_NC) { + return 0; + } + + libtock_lora_phy_gpio_read(pin, &value); + + return value; + } + + void attachInterrupt(uint32_t interruptNum, gpioIrqFn interruptCb, uint32_t mode) override { + if(interruptNum == RADIOLIB_NC) { + return; + } + + gpio_funcs[interruptNum - 1] = interruptCb; + libtock_lora_phy_gpio_command_interrupt_callback(lora_phy_gpio_Callback, NULL); + + // set GPIO as input and enable interrupts on it + libtock_lora_phy_gpio_enable_input(interruptNum, libtock_pull_down); + libtock_lora_phy_gpio_enable_interrupt(interruptNum, libtock_change); + } + + void detachInterrupt(uint32_t interruptNum) override { + if(interruptNum == RADIOLIB_NC) { + return; + } + + gpio_funcs[interruptNum - 1] = NULL; + libtock_lora_phy_gpio_disable_interrupt(interruptNum); + } + + void delay(unsigned long ms) override { +#if !defined(RADIOLIB_CLOCK_DRIFT_MS) + libtocksync_alarm_delay_ms(ms); +#else + libtocksync_alarm_delay_ms(ms * 1000 / (1000 + RADIOLIB_CLOCK_DRIFT_MS)); +#endif + } + + void delayMicroseconds(unsigned long us) override { +#if !defined(RADIOLIB_CLOCK_DRIFT_MS) + libtocksync_alarm_delay_ms(us / 1000); +#else + libtocksync_alarm_delay_ms((us * 1000 / (1000 + RADIOLIB_CLOCK_DRIFT_MS)) / 1000); +#endif + } + + unsigned long millis() override { + uint32_t now; + unsigned long ms; + + if (frequency == 0) { + alarm_internal_frequency(&frequency); + } + + alarm_internal_read(&now); + + ms = now / (frequency / 1000); + +#if !defined(RADIOLIB_CLOCK_DRIFT_MS) + return ms; +#else + return ms * 1000 / (1000 + RADIOLIB_CLOCK_DRIFT_MS); +#endif + } + + unsigned long micros() override { + return millis() / 1000; + } + + long pulseIn(uint32_t pin, uint32_t state, unsigned long timeout) override { + return 0; + } + + void spiBegin() { + } + + void spiBeginTransaction() { + } + + void spiTransfer(uint8_t* out, size_t len, uint8_t* in) { + libtocksync_lora_phy_read_write(out, in, len); + } + + void spiEndTransaction() { + } + + void spiEnd() { + } + + void yield() { + ::yield_no_wait(); + } + + private: +}; + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/CC1101/CC1101.cpp b/software/firmware/source/libraries/RadioLib/src/modules/CC1101/CC1101.cpp new file mode 100644 index 000000000..f2ff50afd --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/CC1101/CC1101.cpp @@ -0,0 +1,1174 @@ +#include "CC1101.h" +#include +#if !RADIOLIB_EXCLUDE_CC1101 + +CC1101::CC1101(Module* module) : PhysicalLayer(RADIOLIB_CC1101_FREQUENCY_STEP_SIZE, RADIOLIB_CC1101_MAX_PACKET_LENGTH) { + this->mod = module; +} + +int16_t CC1101::begin(float freq, float br, float freqDev, float rxBw, int8_t pwr, uint8_t preambleLength) { + // set module properties + this->mod->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_READ] = RADIOLIB_CC1101_CMD_READ; + this->mod->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_WRITE] = RADIOLIB_CC1101_CMD_WRITE; + this->mod->init(); + this->mod->hal->pinMode(this->mod->getIrq(), this->mod->hal->GpioModeInput); + + // try to find the CC1101 chip + uint8_t i = 0; + bool flagFound = false; + while((i < 10) && !flagFound) { + int16_t version = getChipVersion(); + if((version == RADIOLIB_CC1101_VERSION_CURRENT) || (version == RADIOLIB_CC1101_VERSION_LEGACY) || (version == RADIOLIB_CC1101_VERSION_CLONE)) { + flagFound = true; + } else { + RADIOLIB_DEBUG_BASIC_PRINTLN("CC1101 not found! (%d of 10 tries) RADIOLIB_CC1101_REG_VERSION == 0x%04X, expected 0x0004/0x0014", i + 1, version); + this->mod->hal->delay(10); + i++; + } + } + + if(!flagFound) { + RADIOLIB_DEBUG_BASIC_PRINTLN("No CC1101 found!"); + this->mod->term(); + return(RADIOLIB_ERR_CHIP_NOT_FOUND); + } else { + RADIOLIB_DEBUG_BASIC_PRINTLN("M\tCC1101"); + } + + // configure settings not accessible by API + int16_t state = config(); + RADIOLIB_ASSERT(state); + + // configure publicly accessible settings + state = setFrequency(freq); + RADIOLIB_ASSERT(state); + + // configure bitrate + state = setBitRate(br); + RADIOLIB_ASSERT(state); + + // configure default RX bandwidth + state = setRxBandwidth(rxBw); + RADIOLIB_ASSERT(state); + + // configure default frequency deviation + state = setFrequencyDeviation(freqDev); + RADIOLIB_ASSERT(state); + + // configure default TX output power + state = setOutputPower(pwr); + RADIOLIB_ASSERT(state); + + // set default packet length mode + state = variablePacketLengthMode(); + RADIOLIB_ASSERT(state); + + // configure default preamble length + state = setPreambleLength(preambleLength, preambleLength - 4); + RADIOLIB_ASSERT(state); + + // set default data shaping + state = setDataShaping(RADIOLIB_SHAPING_NONE); + RADIOLIB_ASSERT(state); + + // set default encoding + state = setEncoding(RADIOLIB_ENCODING_NRZ); + RADIOLIB_ASSERT(state); + + // set default sync word + uint8_t sw[RADIOLIB_CC1101_DEFAULT_SW_LEN] = RADIOLIB_CC1101_DEFAULT_SW; + state = setSyncWord(sw[0], sw[1], 0, false); + RADIOLIB_ASSERT(state); + + // flush FIFOs + SPIsendCommand(RADIOLIB_CC1101_CMD_FLUSH_RX); + SPIsendCommand(RADIOLIB_CC1101_CMD_FLUSH_TX); + + return(state); +} + +void CC1101::reset() { + // this is the manual power-on-reset sequence + this->mod->hal->digitalWrite(this->mod->getCs(), this->mod->hal->GpioLevelLow); + this->mod->hal->delayMicroseconds(5); + this->mod->hal->digitalWrite(this->mod->getCs(), this->mod->hal->GpioLevelHigh); + this->mod->hal->delayMicroseconds(40); + this->mod->hal->digitalWrite(this->mod->getCs(), this->mod->hal->GpioLevelLow); + this->mod->hal->delay(10); + SPIsendCommand(RADIOLIB_CC1101_CMD_RESET); +} + +int16_t CC1101::transmit(const uint8_t* data, size_t len, uint8_t addr) { + // calculate timeout (5ms + 500 % of expected time-on-air) + RadioLibTime_t timeout = 5 + (RadioLibTime_t)((((float)(len * 8)) / this->bitRate) * 5); + + // start transmission + int16_t state = startTransmit(data, len, addr); + RADIOLIB_ASSERT(state); + + // wait for transmission start or timeout + RadioLibTime_t start = this->mod->hal->millis(); + while(!this->mod->hal->digitalRead(this->mod->getGpio())) { + this->mod->hal->yield(); + + if(this->mod->hal->millis() - start > timeout) { + finishTransmit(); + return(RADIOLIB_ERR_TX_TIMEOUT); + } + } + + // wait for transmission end or timeout + start = this->mod->hal->millis(); + while(this->mod->hal->digitalRead(this->mod->getGpio())) { + this->mod->hal->yield(); + + if(this->mod->hal->millis() - start > timeout) { + finishTransmit(); + return(RADIOLIB_ERR_TX_TIMEOUT); + } + } + + return(finishTransmit()); +} + +int16_t CC1101::receive(uint8_t* data, size_t len) { + // calculate timeout (500 ms + 400 full max-length packets at current bit rate) + RadioLibTime_t timeout = 500 + (1.0/(this->bitRate))*(RADIOLIB_CC1101_MAX_PACKET_LENGTH*400.0); + + // start reception + int16_t state = startReceive(); + RADIOLIB_ASSERT(state); + + // wait for packet start or timeout + RadioLibTime_t start = this->mod->hal->millis(); + while(this->mod->hal->digitalRead(this->mod->getIrq())) { + this->mod->hal->yield(); + + if(this->mod->hal->millis() - start > timeout) { + standby(); + SPIsendCommand(RADIOLIB_CC1101_CMD_FLUSH_RX); + return(RADIOLIB_ERR_RX_TIMEOUT); + } + } + + // wait for packet end or timeout + start = this->mod->hal->millis(); + while(!this->mod->hal->digitalRead(this->mod->getIrq())) { + this->mod->hal->yield(); + + if(this->mod->hal->millis() - start > timeout) { + standby(); + SPIsendCommand(RADIOLIB_CC1101_CMD_FLUSH_RX); + return(RADIOLIB_ERR_RX_TIMEOUT); + } + } + + // read packet data + return(readData(data, len)); +} + +int16_t CC1101::standby() { + // set idle mode + SPIsendCommand(RADIOLIB_CC1101_CMD_IDLE); + + // wait until idle is reached + RadioLibTime_t start = this->mod->hal->millis(); + while(SPIgetRegValue(RADIOLIB_CC1101_REG_MARCSTATE, 4, 0) != RADIOLIB_CC1101_MARC_STATE_IDLE) { + mod->hal->yield(); + if(this->mod->hal->millis() - start > 100) { + // timeout, this should really not happen + return(RADIOLIB_ERR_UNKNOWN); + } + }; + + // set RF switch (if present) + this->mod->setRfSwitchState(Module::MODE_IDLE); + return(RADIOLIB_ERR_NONE); +} + +int16_t CC1101::standby(uint8_t mode) { + (void)mode; + return(standby()); +} + +int16_t CC1101::transmitDirect(uint32_t frf) { + return transmitDirect(true, frf); +} + +int16_t CC1101::transmitDirectAsync(uint32_t frf) { + return transmitDirect(false, frf); +} + +int16_t CC1101::transmitDirect(bool sync, uint32_t frf) { + // set RF switch (if present) + this->mod->setRfSwitchState(Module::MODE_TX); + + // user requested to start transmitting immediately (required for RTTY) + if(frf != 0) { + SPIwriteRegister(RADIOLIB_CC1101_REG_FREQ2, (frf & 0xFF0000) >> 16); + SPIwriteRegister(RADIOLIB_CC1101_REG_FREQ1, (frf & 0x00FF00) >> 8); + SPIwriteRegister(RADIOLIB_CC1101_REG_FREQ0, frf & 0x0000FF); + + SPIsendCommand(RADIOLIB_CC1101_CMD_TX); + return(RADIOLIB_ERR_NONE); + } + + // activate direct mode + int16_t state = directMode(sync); + RADIOLIB_ASSERT(state); + + // start transmitting + SPIsendCommand(RADIOLIB_CC1101_CMD_TX); + return(state); +} + +int16_t CC1101::receiveDirect() { + return receiveDirect(true); +} + +int16_t CC1101::receiveDirectAsync() { + return receiveDirect(false); +} + +int16_t CC1101::receiveDirect(bool sync) { + // set RF switch (if present) + this->mod->setRfSwitchState(Module::MODE_RX); + + // enable promiscuous mode - needed for protocols that decode in software (e.g. PagerClient) + int16_t state = setPromiscuousMode(true); + RADIOLIB_ASSERT(state); + + // activate direct mode + state = directMode(sync); + RADIOLIB_ASSERT(state); + + // start receiving + SPIsendCommand(RADIOLIB_CC1101_CMD_RX); + return(RADIOLIB_ERR_NONE); +} + +int16_t CC1101::packetMode() { + int16_t state = SPIsetRegValue(RADIOLIB_CC1101_REG_PKTCTRL1, RADIOLIB_CC1101_CRC_AUTOFLUSH_OFF | RADIOLIB_CC1101_APPEND_STATUS_ON | RADIOLIB_CC1101_ADR_CHK_NONE, 3, 0); + state |= SPIsetRegValue(RADIOLIB_CC1101_REG_PKTCTRL0, RADIOLIB_CC1101_WHITE_DATA_OFF | RADIOLIB_CC1101_PKT_FORMAT_NORMAL, 6, 4); + state |= SPIsetRegValue(RADIOLIB_CC1101_REG_PKTCTRL0, RADIOLIB_CC1101_CRC_ON | this->packetLengthConfig, 2, 0); + return(state); +} + +void CC1101::setGdo0Action(void (*func)(void), uint32_t dir) { + this->mod->hal->attachInterrupt(this->mod->hal->pinToInterrupt(this->mod->getIrq()), func, dir); +} + +void CC1101::clearGdo0Action() { + this->mod->hal->detachInterrupt(this->mod->hal->pinToInterrupt(this->mod->getIrq())); +} + +void CC1101::setPacketReceivedAction(void (*func)(void)) { + this->setGdo0Action(func, this->mod->hal->GpioInterruptRising); +} + +void CC1101::clearPacketReceivedAction() { + this->clearGdo0Action(); +} + +void CC1101::setPacketSentAction(void (*func)(void)) { + this->setGdo2Action(func, this->mod->hal->GpioInterruptFalling); +} + +void CC1101::clearPacketSentAction() { + this->clearGdo2Action(); +} + +void CC1101::setGdo2Action(void (*func)(void), uint32_t dir) { + if(this->mod->getGpio() == RADIOLIB_NC) { + return; + } + this->mod->hal->pinMode(this->mod->getGpio(), this->mod->hal->GpioModeInput); + this->mod->hal->attachInterrupt(this->mod->hal->pinToInterrupt(this->mod->getGpio()), func, dir); +} + +void CC1101::clearGdo2Action() { + if(this->mod->getGpio() == RADIOLIB_NC) { + return; + } + this->mod->hal->detachInterrupt(this->mod->hal->pinToInterrupt(this->mod->getGpio())); +} + +int16_t CC1101::startTransmit(const uint8_t* data, size_t len, uint8_t addr) { + // check packet length + if(len > RADIOLIB_CC1101_MAX_PACKET_LENGTH) { + return(RADIOLIB_ERR_PACKET_TOO_LONG); + } + + // set mode to standby + standby(); + + // flush Tx FIFO + SPIsendCommand(RADIOLIB_CC1101_CMD_FLUSH_TX); + + // set GDO0 mapping + int16_t state = SPIsetRegValue(RADIOLIB_CC1101_REG_IOCFG2, RADIOLIB_CC1101_GDOX_SYNC_WORD_SENT_OR_PKT_RECEIVED, 5, 0); + RADIOLIB_ASSERT(state); + + // optionally write packet length + if(this->packetLengthConfig == RADIOLIB_CC1101_LENGTH_CONFIG_VARIABLE) { + SPIwriteRegister(RADIOLIB_CC1101_REG_FIFO, len); + } + + // check address filtering + uint8_t filter = SPIgetRegValue(RADIOLIB_CC1101_REG_PKTCTRL1, 1, 0); + if(filter != RADIOLIB_CC1101_ADR_CHK_NONE) { + SPIwriteRegister(RADIOLIB_CC1101_REG_FIFO, addr); + } + + // fill the FIFO + SPIwriteRegisterBurst(RADIOLIB_CC1101_REG_FIFO, const_cast(data), len); + + // set RF switch (if present) + this->mod->setRfSwitchState(Module::MODE_TX); + + // set mode to transmit + SPIsendCommand(RADIOLIB_CC1101_CMD_TX); + + return(state); +} + +int16_t CC1101::finishTransmit() { + // set mode to standby to disable transmitter/RF switch + int16_t state = standby(); + RADIOLIB_ASSERT(state); + + // flush Tx FIFO + SPIsendCommand(RADIOLIB_CC1101_CMD_FLUSH_TX); + + return(state); +} + +int16_t CC1101::startReceive() { + // set mode to standby + int16_t state = standby(); + RADIOLIB_ASSERT(state); + + // flush Rx FIFO + SPIsendCommand(RADIOLIB_CC1101_CMD_FLUSH_RX); + + // set GDO0 mapping + // GDO0 is de-asserted at packet end, hence it is inverted here + state = SPIsetRegValue(RADIOLIB_CC1101_REG_IOCFG0, RADIOLIB_CC1101_GDO0_INV | RADIOLIB_CC1101_GDOX_SYNC_WORD_SENT_OR_PKT_RECEIVED, 6, 0); + RADIOLIB_ASSERT(state); + + // set RF switch (if present) + this->mod->setRfSwitchState(Module::MODE_RX); + + // set mode to receive + SPIsendCommand(RADIOLIB_CC1101_CMD_RX); + + return(state); +} + +int16_t CC1101::startReceive(uint32_t timeout, uint32_t irqFlags, uint32_t irqMask, size_t len) { + (void)timeout; + (void)irqFlags; + (void)irqMask; + (void)len; + return(startReceive()); +} + +int16_t CC1101::readData(uint8_t* data, size_t len) { + // get packet length + size_t length = getPacketLength(); + if((len != 0) && (len < length)) { + // user requested less data than we got, only return what was requested + length = len; + } + + // check address filtering + uint8_t filter = SPIgetRegValue(RADIOLIB_CC1101_REG_PKTCTRL1, 1, 0); + if(filter != RADIOLIB_CC1101_ADR_CHK_NONE) { + SPIreadRegister(RADIOLIB_CC1101_REG_FIFO); + } + + // read packet data + SPIreadRegisterBurst(RADIOLIB_CC1101_REG_FIFO, length, data); + + // check if status bytes are enabled (default: RADIOLIB_CC1101_APPEND_STATUS_ON) + bool isAppendStatus = SPIgetRegValue(RADIOLIB_CC1101_REG_PKTCTRL1, 2, 2) == RADIOLIB_CC1101_APPEND_STATUS_ON; + + // If status byte is enabled at least 2 bytes (2 status bytes + any following packet) will remain in FIFO. + int16_t state = RADIOLIB_ERR_NONE; + if (isAppendStatus) { + // read RSSI byte + this->rawRSSI = SPIgetRegValue(RADIOLIB_CC1101_REG_FIFO); + + // read LQI and CRC byte + uint8_t val = SPIgetRegValue(RADIOLIB_CC1101_REG_FIFO); + this->rawLQI = val & 0x7F; + + // check CRC + if(this->crcOn && (val & RADIOLIB_CC1101_CRC_OK) == RADIOLIB_CC1101_CRC_ERROR) { + this->packetLengthQueried = false; + state = RADIOLIB_ERR_CRC_MISMATCH; + } + } + + // clear internal flag so getPacketLength can return the new packet length + this->packetLengthQueried = false; + + // Flush then standby according to RXOFF_MODE (default: RADIOLIB_CC1101_RXOFF_IDLE) + if(SPIgetRegValue(RADIOLIB_CC1101_REG_MCSM1, 3, 2) == RADIOLIB_CC1101_RXOFF_IDLE) { + + // set mode to standby + standby(); + + // flush Rx FIFO + SPIsendCommand(RADIOLIB_CC1101_CMD_FLUSH_RX); + } + + return(state); +} + +int16_t CC1101::setFrequency(float freq) { + // check allowed frequency range + #if RADIOLIB_CHECK_PARAMS + if(!(((freq >= 300.0) && (freq <= 348.0)) || + ((freq >= 387.0) && (freq <= 464.0)) || + ((freq >= 779.0) && (freq <= 928.0)))) { + return(RADIOLIB_ERR_INVALID_FREQUENCY); + } + #endif + + // set mode to standby + SPIsendCommand(RADIOLIB_CC1101_CMD_IDLE); + + //set carrier frequency + uint32_t base = 1; + uint32_t FRF = (freq * (base << 16)) / 26.0; + int16_t state = SPIsetRegValue(RADIOLIB_CC1101_REG_FREQ2, (FRF & 0xFF0000) >> 16, 7, 0); + state |= SPIsetRegValue(RADIOLIB_CC1101_REG_FREQ1, (FRF & 0x00FF00) >> 8, 7, 0); + state |= SPIsetRegValue(RADIOLIB_CC1101_REG_FREQ0, FRF & 0x0000FF, 7, 0); + + if(state == RADIOLIB_ERR_NONE) { + this->frequency = freq; + } + + // Update the TX power accordingly to new freq. (PA values depend on chosen freq) + return(setOutputPower(this->power)); +} + +int16_t CC1101::setBitRate(float br) { + RADIOLIB_CHECK_RANGE(br, 0.025, 600.0, RADIOLIB_ERR_INVALID_BIT_RATE); + + // set mode to standby + SPIsendCommand(RADIOLIB_CC1101_CMD_IDLE); + + // calculate exponent and mantissa values + uint8_t e = 0; + uint8_t m = 0; + getExpMant(br * 1000.0, 256, 28, 14, e, m); + + // set bit rate value + int16_t state = SPIsetRegValue(RADIOLIB_CC1101_REG_MDMCFG4, e, 3, 0); + state |= SPIsetRegValue(RADIOLIB_CC1101_REG_MDMCFG3, m); + if(state == RADIOLIB_ERR_NONE) { + this->bitRate = br; + } + return(state); +} + +int16_t CC1101::setBitRateTolerance(uint8_t brt) { + if (brt > 0x03) return (RADIOLIB_ERR_INVALID_BIT_RATE_TOLERANCE_VALUE); + + // Set Bit Rate tolerance + int16_t state = SPIsetRegValue(RADIOLIB_CC1101_REG_BSCFG, brt, 1, 0); + + return(state); +} + +int16_t CC1101::setRxBandwidth(float rxBw) { + RADIOLIB_CHECK_RANGE(rxBw, 58.0, 812.0, RADIOLIB_ERR_INVALID_RX_BANDWIDTH); + + // set mode to standby + SPIsendCommand(RADIOLIB_CC1101_CMD_IDLE); + + // calculate exponent and mantissa values + for(int8_t e = 3; e >= 0; e--) { + for(int8_t m = 3; m >= 0; m --) { + float point = (RADIOLIB_CC1101_CRYSTAL_FREQ * 1000000.0)/(8 * (m + 4) * ((uint32_t)1 << e)); + if(fabs((rxBw * 1000.0) - point) <= 1000) { + // set Rx channel filter bandwidth + return(SPIsetRegValue(RADIOLIB_CC1101_REG_MDMCFG4, (e << 6) | (m << 4), 7, 4)); + } + + } + } + + return(RADIOLIB_ERR_INVALID_RX_BANDWIDTH); +} + +int16_t CC1101::autoSetRxBandwidth() { + // Uncertainty ~ +/- 40ppm for a cheap CC1101 + // Uncertainty * 2 for both transmitter and receiver + float uncertainty = ((this->frequency) * 40 * 2); + uncertainty = (uncertainty/1000); //Since bitrate is in kBit + float minbw = ((this->bitRate) + uncertainty); + + int possibles[16] = {58, 68, 81, 102, 116, 135, 162, 203, 232, 270, 325, 406, 464, 541, 650, 812}; + + for (int i = 0; i < 16; i++) { + if (possibles[i] > minbw) { + int16_t state = setRxBandwidth(possibles[i]); + return(state); + } + } + return(RADIOLIB_ERR_UNKNOWN); + } + +int16_t CC1101::setFrequencyDeviation(float freqDev) { + // set frequency deviation to lowest available setting (required for digimodes) + float newFreqDev = freqDev; + if(freqDev < 0.0) { + newFreqDev = 1.587; + } + + // check range unless 0 (special value) + if (freqDev != 0) { + RADIOLIB_CHECK_RANGE(newFreqDev, 1.587, 380.8, RADIOLIB_ERR_INVALID_FREQUENCY_DEVIATION); + } + + // set mode to standby + SPIsendCommand(RADIOLIB_CC1101_CMD_IDLE); + + // calculate exponent and mantissa values + uint8_t e = 0; + uint8_t m = 0; + getExpMant(newFreqDev * 1000.0, 8, 17, 7, e, m); + + // set frequency deviation value + int16_t state = SPIsetRegValue(RADIOLIB_CC1101_REG_DEVIATN, (e << 4), 6, 4); + state |= SPIsetRegValue(RADIOLIB_CC1101_REG_DEVIATN, m, 2, 0); + + return(state); +} + +int16_t CC1101::getFrequencyDeviation(float *freqDev) { + if (freqDev == NULL) { + return(RADIOLIB_ERR_NULL_POINTER); + } + + // if ASK/OOK, deviation makes no sense + if (this->modulation == RADIOLIB_CC1101_MOD_FORMAT_ASK_OOK) { + *freqDev = 0.0; + + return(RADIOLIB_ERR_NONE); + } + + // get exponent and mantissa values from registers + uint8_t e = (uint8_t)(SPIgetRegValue(RADIOLIB_CC1101_REG_DEVIATN, 6, 4) >> 4); + uint8_t m = (uint8_t)SPIgetRegValue(RADIOLIB_CC1101_REG_DEVIATN, 2, 0); + + // calculate frequency deviation (pag. 79 of the CC1101 datasheet): + // + // freqDev = (fXosc / 2^17) * (8 + m) * 2^e + // + *freqDev = (1000.0 / (uint32_t(1) << 17)) - (8 + m) * (uint32_t(1) << e); + + return(RADIOLIB_ERR_NONE); +} + +int16_t CC1101::setOutputPower(int8_t pwr) { + // check if power value is configurable + uint8_t powerRaw = 0; + int16_t state = checkOutputPower(pwr, NULL, &powerRaw); + RADIOLIB_ASSERT(state); + + // store the value + this->power = pwr; + + if(this->modulation == RADIOLIB_CC1101_MOD_FORMAT_ASK_OOK){ + // Amplitude modulation: + // PA_TABLE[0] is the power to be used when transmitting a 0 (no power) + // PA_TABLE[1] is the power to be used when transmitting a 1 (full power) + + uint8_t paValues[2] = {0x00, powerRaw}; + SPIwriteRegisterBurst(RADIOLIB_CC1101_REG_PATABLE, paValues, 2); + return(RADIOLIB_ERR_NONE); + + } else { + // Freq modulation: + // PA_TABLE[0] is the power to be used when transmitting. + return(SPIsetRegValue(RADIOLIB_CC1101_REG_PATABLE, powerRaw)); + } +} + +int16_t CC1101::checkOutputPower(int8_t power, int8_t* clipped) { + return(checkOutputPower(power, clipped, NULL)); +} + +int16_t CC1101::checkOutputPower(int8_t power, int8_t* clipped, uint8_t* raw) { + const int8_t allowedPwrs[8] = { -30, -20, -15, -10, 0, 5, 7, 10 }; + + if(clipped) { + if(power <= -30) { + *clipped = -30; + } else if(power >= 10) { + *clipped = 10; + } else { + for(int i = 0; i < 8; i++) { + if(allowedPwrs[i] > power) { + break; + } + *clipped = allowedPwrs[i]; + } + } + } + + // if just a check occurs (and not requesting the raw power value), return now + if(!raw) { + for(size_t i = 0; i < sizeof(allowedPwrs); i++) { + if(allowedPwrs[i] == power) { + return(RADIOLIB_ERR_NONE); + } + } + return(RADIOLIB_ERR_INVALID_OUTPUT_POWER); + } + + // round to the known frequency settings + uint8_t f; + if(this->frequency < 374.0) { + // 315 MHz + f = 0; + } else if(this->frequency < 650.5) { + // 434 MHz + f = 1; + } else if(this->frequency < 891.5) { + // 868 MHz + f = 2; + } else { + // 915 MHz + f = 3; + } + + // get raw power setting + uint8_t paTable[8][4] = {{0x12, 0x12, 0x03, 0x03}, + {0x0D, 0x0E, 0x0F, 0x0E}, + {0x1C, 0x1D, 0x1E, 0x1E}, + {0x34, 0x34, 0x27, 0x27}, + {0x51, 0x60, 0x50, 0x8E}, + {0x85, 0x84, 0x81, 0xCD}, + {0xCB, 0xC8, 0xCB, 0xC7}, + {0xC2, 0xC0, 0xC2, 0xC0}}; + + for(uint8_t i = 0; i < sizeof(allowedPwrs); i++) { + if(power == allowedPwrs[i]) { + *raw = paTable[i][f]; + return(RADIOLIB_ERR_NONE); + } + } + + return(RADIOLIB_ERR_INVALID_OUTPUT_POWER); +} + +int16_t CC1101::setSyncWord(uint8_t* syncWord, uint8_t len, uint8_t maxErrBits, bool requireCarrierSense) { + if((maxErrBits > 1) || (len != 2)) { + return(RADIOLIB_ERR_INVALID_SYNC_WORD); + } + + // sync word must not contain value 0x00 + for(uint8_t i = 0; i < len; i++) { + if(syncWord[i] == 0x00) { + return(RADIOLIB_ERR_INVALID_SYNC_WORD); + } + } + + // enable sync word filtering + int16_t state = enableSyncWordFiltering(maxErrBits, requireCarrierSense); + RADIOLIB_ASSERT(state); + + // set sync word register + state = SPIsetRegValue(RADIOLIB_CC1101_REG_SYNC1, syncWord[0]); + state |= SPIsetRegValue(RADIOLIB_CC1101_REG_SYNC0, syncWord[1]); + + return(state); +} + +int16_t CC1101::setSyncWord(uint8_t syncH, uint8_t syncL, uint8_t maxErrBits, bool requireCarrierSense) { + uint8_t syncWord[] = { syncH, syncL }; + return(setSyncWord(syncWord, sizeof(syncWord), maxErrBits, requireCarrierSense)); +} + +int16_t CC1101::setPreambleLength(uint8_t preambleLength, uint8_t qualityThreshold) { + // check allowed values + uint8_t value; + switch(preambleLength) { + case 16: + value = RADIOLIB_CC1101_NUM_PREAMBLE_2; + break; + case 24: + value = RADIOLIB_CC1101_NUM_PREAMBLE_3; + break; + case 32: + value = RADIOLIB_CC1101_NUM_PREAMBLE_4; + break; + case 48: + value = RADIOLIB_CC1101_NUM_PREAMBLE_6; + break; + case 64: + value = RADIOLIB_CC1101_NUM_PREAMBLE_8; + break; + case 96: + value = RADIOLIB_CC1101_NUM_PREAMBLE_12; + break; + case 128: + value = RADIOLIB_CC1101_NUM_PREAMBLE_16; + break; + case 192: + value = RADIOLIB_CC1101_NUM_PREAMBLE_24; + break; + default: + return(RADIOLIB_ERR_INVALID_PREAMBLE_LENGTH); + } + + // set preabmble quality threshold and the actual length + uint8_t pqt = qualityThreshold/4; + if(pqt > 7) { + pqt = 7; + } + int16_t state = SPIsetRegValue(RADIOLIB_CC1101_REG_PKTCTRL1, pqt << 5, 7, 5); + state |= SPIsetRegValue(RADIOLIB_CC1101_REG_MDMCFG1, value, 6, 4); + return(state); +} + +int16_t CC1101::setNodeAddress(uint8_t nodeAddr, uint8_t numBroadcastAddrs) { + RADIOLIB_CHECK_RANGE(numBroadcastAddrs, 1, 2, RADIOLIB_ERR_INVALID_NUM_BROAD_ADDRS); + + // enable address filtering + int16_t state = SPIsetRegValue(RADIOLIB_CC1101_REG_PKTCTRL1, numBroadcastAddrs + 0x01, 1, 0); + RADIOLIB_ASSERT(state); + + // set node address + return(SPIsetRegValue(RADIOLIB_CC1101_REG_ADDR, nodeAddr)); +} + +int16_t CC1101::disableAddressFiltering() { + // disable address filtering + int16_t state = SPIsetRegValue(RADIOLIB_CC1101_REG_PKTCTRL1, RADIOLIB_CC1101_ADR_CHK_NONE, 1, 0); + RADIOLIB_ASSERT(state); + + // set node address to default (0x00) + return(SPIsetRegValue(RADIOLIB_CC1101_REG_ADDR, 0x00)); +} + + +int16_t CC1101::setOOK(bool enableOOK) { + // Change modulation + if(enableOOK) { + int16_t state = SPIsetRegValue(RADIOLIB_CC1101_REG_MDMCFG2, RADIOLIB_CC1101_MOD_FORMAT_ASK_OOK, 6, 4); + RADIOLIB_ASSERT(state); + + // PA_TABLE[0] is (by default) the power value used when transmitting a "0". + // Set PA_TABLE[1] to be used when transmitting a "1". + state = SPIsetRegValue(RADIOLIB_CC1101_REG_FREND0, 1, 2, 0); + RADIOLIB_ASSERT(state); + + // update current modulation + this->modulation = RADIOLIB_CC1101_MOD_FORMAT_ASK_OOK; + } else { + int16_t state = SPIsetRegValue(RADIOLIB_CC1101_REG_MDMCFG2, RADIOLIB_CC1101_MOD_FORMAT_2_FSK, 6, 4); + RADIOLIB_ASSERT(state); + + // Reset FREND0 to default value. + state = SPIsetRegValue(RADIOLIB_CC1101_REG_FREND0, 0, 2, 0); + RADIOLIB_ASSERT(state); + + // update current modulation + this->modulation = RADIOLIB_CC1101_MOD_FORMAT_2_FSK; + } + + // Update PA_TABLE values according to the new this->modulation. + return(setOutputPower(this->power)); +} + +float CC1101::getRSSI() { + float rssi; + + if(!this->directModeEnabled) { + if(this->rawRSSI >= 128) { + rssi = (((float)this->rawRSSI - 256.0)/2.0) - 74.0; + } else { + rssi = (((float)this->rawRSSI)/2.0) - 74.0; + } + } else { + uint8_t rawRssi = SPIreadRegister(RADIOLIB_CC1101_REG_RSSI); + if(rawRssi >= 128) { + rssi = ((rawRssi - 256) / 2) - 74; + } else { + rssi = (rawRssi / 2) - 74; + } + } + return(rssi); +} + +uint8_t CC1101::getLQI() const { + return(this->rawLQI); +} + +size_t CC1101::getPacketLength(bool update) { + if(!this->packetLengthQueried && update) { + if(this->packetLengthConfig == RADIOLIB_CC1101_LENGTH_CONFIG_VARIABLE) { + this->packetLength = SPIreadRegister(RADIOLIB_CC1101_REG_FIFO); + } else { + this->packetLength = SPIreadRegister(RADIOLIB_CC1101_REG_PKTLEN); + } + + this->packetLengthQueried = true; + } + + return(this->packetLength); +} + +int16_t CC1101::fixedPacketLengthMode(uint8_t len) { + if(len == 0) { + // infinite packet mode + int16_t state = SPIsetRegValue(RADIOLIB_CC1101_REG_PKTCTRL0, RADIOLIB_CC1101_LENGTH_CONFIG_INFINITE, 1, 0); + RADIOLIB_ASSERT(state); + } + + return(setPacketMode(RADIOLIB_CC1101_LENGTH_CONFIG_FIXED, len)); +} + +int16_t CC1101::variablePacketLengthMode(uint8_t maxLen) { + return(setPacketMode(RADIOLIB_CC1101_LENGTH_CONFIG_VARIABLE, maxLen)); +} + +int16_t CC1101::enableSyncWordFiltering(uint8_t maxErrBits, bool requireCarrierSense) { + int16_t state = RADIOLIB_ERR_NONE; + + switch(maxErrBits) { + case 0: + // in 16 bit sync word, expect all 16 bits + state |= SPIsetRegValue(RADIOLIB_CC1101_REG_MDMCFG2, (requireCarrierSense ? RADIOLIB_CC1101_SYNC_MODE_16_16_THR : RADIOLIB_CC1101_SYNC_MODE_16_16), 2, 0); + break; + case 1: + // in 16 bit sync word, expect at least 15 bits + state |= SPIsetRegValue(RADIOLIB_CC1101_REG_MDMCFG2, (requireCarrierSense ? RADIOLIB_CC1101_SYNC_MODE_15_16_THR : RADIOLIB_CC1101_SYNC_MODE_15_16), 2, 0); + break; + default: + state = RADIOLIB_ERR_INVALID_SYNC_WORD; + break; + } + return(state); +} + +int16_t CC1101::disableSyncWordFiltering(bool requireCarrierSense) { + int16_t state = SPIsetRegValue(RADIOLIB_CC1101_REG_MDMCFG2, (requireCarrierSense ? RADIOLIB_CC1101_SYNC_MODE_NONE_THR : RADIOLIB_CC1101_SYNC_MODE_NONE), 2, 0); + return(state); +} + +int16_t CC1101::setCrcFiltering(bool enable) { + this->crcOn = enable; + + if (this->crcOn == true) { + return(SPIsetRegValue(RADIOLIB_CC1101_REG_PKTCTRL0, RADIOLIB_CC1101_CRC_ON, 2, 2)); + } else { + return(SPIsetRegValue(RADIOLIB_CC1101_REG_PKTCTRL0, RADIOLIB_CC1101_CRC_OFF, 2, 2)); + } +} + +int16_t CC1101::setPromiscuousMode(bool enable, bool requireCarrierSense) { + int16_t state = RADIOLIB_ERR_NONE; + + if(this->promiscuous == enable) { + return(state); + } + + if(enable) { + // Lets set PQT to 0 with Promiscuous too + // We have to set the length to set PQT, but it should get disabled with disableSyncWordFiltering() + state = setPreambleLength(16, 0); + RADIOLIB_ASSERT(state); + // disable sync word filtering and insertion + // this also disables preamble + // Can enable Sync Mode with carriersense when promiscuous is enabled. Default is false: Sync Mode None + state = disableSyncWordFiltering(requireCarrierSense); + RADIOLIB_ASSERT(state); + + // disable CRC filtering + state = setCrcFiltering(false); + } else { + state = setPreambleLength(RADIOLIB_CC1101_DEFAULT_PREAMBLELEN, RADIOLIB_CC1101_DEFAULT_PREAMBLELEN/4); + RADIOLIB_ASSERT(state); + + // enable sync word filtering and insertion + state = enableSyncWordFiltering(); + RADIOLIB_ASSERT(state); + + // enable CRC filtering + state = setCrcFiltering(true); + } + + this->promiscuous = enable; + + return(state); +} + +bool CC1101::getPromiscuousMode() { + return (this->promiscuous); +} + +int16_t CC1101::setDataShaping(uint8_t sh) { + // set mode to standby + int16_t state = standby(); + RADIOLIB_ASSERT(state); + + // set data shaping + switch(sh) { + case RADIOLIB_SHAPING_NONE: + state = SPIsetRegValue(RADIOLIB_CC1101_REG_MDMCFG2, RADIOLIB_CC1101_MOD_FORMAT_2_FSK, 6, 4); + break; + case RADIOLIB_SHAPING_0_5: + state = SPIsetRegValue(RADIOLIB_CC1101_REG_MDMCFG2, RADIOLIB_CC1101_MOD_FORMAT_GFSK, 6, 4); + break; + default: + return(RADIOLIB_ERR_INVALID_DATA_SHAPING); + } + return(state); +} + +int16_t CC1101::setEncoding(uint8_t encoding) { + // set mode to standby + int16_t state = standby(); + RADIOLIB_ASSERT(state); + + // set encoding + switch(encoding) { + case RADIOLIB_ENCODING_NRZ: + state = SPIsetRegValue(RADIOLIB_CC1101_REG_MDMCFG2, RADIOLIB_CC1101_MANCHESTER_EN_OFF, 3, 3); + RADIOLIB_ASSERT(state); + return(SPIsetRegValue(RADIOLIB_CC1101_REG_PKTCTRL0, RADIOLIB_CC1101_WHITE_DATA_OFF, 6, 6)); + case RADIOLIB_ENCODING_MANCHESTER: + state = SPIsetRegValue(RADIOLIB_CC1101_REG_MDMCFG2, RADIOLIB_CC1101_MANCHESTER_EN_ON, 3, 3); + RADIOLIB_ASSERT(state); + return(SPIsetRegValue(RADIOLIB_CC1101_REG_PKTCTRL0, RADIOLIB_CC1101_WHITE_DATA_OFF, 6, 6)); + case RADIOLIB_ENCODING_WHITENING: + state = SPIsetRegValue(RADIOLIB_CC1101_REG_MDMCFG2, RADIOLIB_CC1101_MANCHESTER_EN_OFF, 3, 3); + RADIOLIB_ASSERT(state); + return(SPIsetRegValue(RADIOLIB_CC1101_REG_PKTCTRL0, RADIOLIB_CC1101_WHITE_DATA_ON, 6, 6)); + default: + return(RADIOLIB_ERR_INVALID_ENCODING); + } +} + +void CC1101::setRfSwitchPins(uint32_t rxEn, uint32_t txEn) { + this->mod->setRfSwitchPins(rxEn, txEn); +} + +void CC1101::setRfSwitchTable(const uint32_t (&pins)[Module::RFSWITCH_MAX_PINS], const Module::RfSwitchMode_t table[]) { + this->mod->setRfSwitchTable(pins, table); +} + +uint8_t CC1101::randomByte() { + // set mode to Rx + SPIsendCommand(RADIOLIB_CC1101_CMD_RX); + + // wait a bit for the RSSI reading to stabilise + this->mod->hal->delay(10); + + // read RSSI value 8 times, always keep just the least significant bit + uint8_t randByte = 0x00; + for(uint8_t i = 0; i < 8; i++) { + randByte |= ((SPIreadRegister(RADIOLIB_CC1101_REG_RSSI) & 0x01) << i); + } + + // set mode to standby + SPIsendCommand(RADIOLIB_CC1101_CMD_IDLE); + + return(randByte); +} + +int16_t CC1101::getChipVersion() { + return(SPIgetRegValue(RADIOLIB_CC1101_REG_VERSION)); +} + +#if !RADIOLIB_EXCLUDE_DIRECT_RECEIVE +void CC1101::setDirectAction(void (*func)(void)) { + setGdo0Action(func, this->mod->hal->GpioInterruptRising); +} + +void CC1101::readBit(uint32_t pin) { + updateDirectBuffer((uint8_t)this->mod->hal->digitalRead(pin)); +} +#endif + +int16_t CC1101::setDIOMapping(uint32_t pin, uint32_t value) { + if(pin > 2) { + return(RADIOLIB_ERR_INVALID_DIO_PIN); + } + + return(SPIsetRegValue(RADIOLIB_CC1101_REG_IOCFG0 - pin, value)); +} + +int16_t CC1101::config() { + // Reset the radio. Registers may be dirty from previous usage. + reset(); + + // Wait a ridiculous amount of time to be sure radio is ready. + this->mod->hal->delay(150); + + standby(); + + // enable automatic frequency synthesizer calibration and disable pin control + int16_t state = SPIsetRegValue(RADIOLIB_CC1101_REG_MCSM0, RADIOLIB_CC1101_FS_AUTOCAL_IDLE_TO_RXTX, 5, 4); + state |= SPIsetRegValue(RADIOLIB_CC1101_REG_MCSM0, RADIOLIB_CC1101_PIN_CTRL_OFF, 1, 1); + RADIOLIB_ASSERT(state); + + // set GDOs to Hi-Z so that it doesn't output clock on startup (might confuse GDO0 action) + state = SPIsetRegValue(RADIOLIB_CC1101_REG_IOCFG0, RADIOLIB_CC1101_GDOX_HIGH_Z, 5, 0); + state |= SPIsetRegValue(RADIOLIB_CC1101_REG_IOCFG2, RADIOLIB_CC1101_GDOX_HIGH_Z, 5, 0); + RADIOLIB_ASSERT(state); + + // set packet mode + state = packetMode(); + + return(state); +} + +int16_t CC1101::directMode(bool sync) { + // set mode to standby + SPIsendCommand(RADIOLIB_CC1101_CMD_IDLE); + + int16_t state = 0; + this->directModeEnabled = true; + if(sync) { + // set GDO0 and GDO2 mapping + state |= SPIsetRegValue(RADIOLIB_CC1101_REG_IOCFG0, RADIOLIB_CC1101_GDOX_SERIAL_CLOCK , 5, 0); + state |= SPIsetRegValue(RADIOLIB_CC1101_REG_IOCFG2, RADIOLIB_CC1101_GDOX_SERIAL_DATA_SYNC , 5, 0); + + // set continuous mode + state |= SPIsetRegValue(RADIOLIB_CC1101_REG_PKTCTRL0, RADIOLIB_CC1101_PKT_FORMAT_SYNCHRONOUS, 5, 4); + } else { + // set GDO0 mapping + state |= SPIsetRegValue(RADIOLIB_CC1101_REG_IOCFG0, RADIOLIB_CC1101_GDOX_SERIAL_DATA_ASYNC , 5, 0); + + // set asynchronous continuous mode + state |= SPIsetRegValue(RADIOLIB_CC1101_REG_PKTCTRL0, RADIOLIB_CC1101_PKT_FORMAT_ASYNCHRONOUS, 5, 4); + } + + state |= SPIsetRegValue(RADIOLIB_CC1101_REG_PKTCTRL0, RADIOLIB_CC1101_LENGTH_CONFIG_INFINITE, 1, 0); + return(state); +} + +void CC1101::getExpMant(float target, uint16_t mantOffset, uint8_t divExp, uint8_t expMax, uint8_t& exp, uint8_t& mant) { + // get table origin point (exp = 0, mant = 0) + float origin = (mantOffset * RADIOLIB_CC1101_CRYSTAL_FREQ * 1000000.0)/((uint32_t)1 << divExp); + + // iterate over possible exponent values + for(int8_t e = expMax; e >= 0; e--) { + // get table column start value (exp = e, mant = 0); + float intervalStart = ((uint32_t)1 << e) * origin; + + // check if target value is in this column + if(target >= intervalStart) { + // save exponent value + exp = e; + + // calculate size of step between table rows + float stepSize = intervalStart/(float)mantOffset; + + // get target point position (exp = e, mant = m) + mant = ((target - intervalStart) / stepSize); + + // we only need the first match, terminate + return; + } + } +} + +int16_t CC1101::setPacketMode(uint8_t mode, uint16_t len) { + // check length + if (len > RADIOLIB_CC1101_MAX_PACKET_LENGTH) { + return(RADIOLIB_ERR_PACKET_TOO_LONG); + } + + // set PKTCTRL0.LENGTH_CONFIG + int16_t state = SPIsetRegValue(RADIOLIB_CC1101_REG_PKTCTRL0, mode, 1, 0); + RADIOLIB_ASSERT(state); + + // set length to register + state = SPIsetRegValue(RADIOLIB_CC1101_REG_PKTLEN, len); + RADIOLIB_ASSERT(state); + + // no longer in a direct mode + this->directModeEnabled = false; + + // update the cached values + this->packetLength = len; + this->packetLengthConfig = mode; + return(state); +} + +Module* CC1101::getMod() { + return(this->mod); +} + +int16_t CC1101::SPIgetRegValue(uint8_t reg, uint8_t msb, uint8_t lsb) { + // status registers require special command + if((reg > RADIOLIB_CC1101_REG_TEST0) && (reg < RADIOLIB_CC1101_REG_PATABLE)) { + reg |= RADIOLIB_CC1101_CMD_ACCESS_STATUS_REG; + } + + return(this->mod->SPIgetRegValue(reg, msb, lsb)); +} + +int16_t CC1101::SPIsetRegValue(uint8_t reg, uint8_t value, uint8_t msb, uint8_t lsb, uint8_t checkInterval) { + // status registers require special command + if((reg > RADIOLIB_CC1101_REG_TEST0) && (reg < RADIOLIB_CC1101_REG_PATABLE)) { + reg |= RADIOLIB_CC1101_CMD_ACCESS_STATUS_REG; + } + + return(this->mod->SPIsetRegValue(reg, value, msb, lsb, checkInterval)); +} + +void CC1101::SPIreadRegisterBurst(uint8_t reg, uint8_t numBytes, uint8_t* inBytes) { + this->mod->SPIreadRegisterBurst(reg | RADIOLIB_CC1101_CMD_BURST, numBytes, inBytes); +} + +uint8_t CC1101::SPIreadRegister(uint8_t reg) { + // status registers require special command + if((reg > RADIOLIB_CC1101_REG_TEST0) && (reg < RADIOLIB_CC1101_REG_PATABLE)) { + reg |= RADIOLIB_CC1101_CMD_ACCESS_STATUS_REG; + } + + return(this->mod->SPIreadRegister(reg)); +} + +void CC1101::SPIwriteRegister(uint8_t reg, uint8_t data) { + // status registers require special command + if((reg > RADIOLIB_CC1101_REG_TEST0) && (reg < RADIOLIB_CC1101_REG_PATABLE)) { + reg |= RADIOLIB_CC1101_CMD_ACCESS_STATUS_REG; + } + + return(this->mod->SPIwriteRegister(reg, data)); +} + +void CC1101::SPIwriteRegisterBurst(uint8_t reg, uint8_t* data, size_t len) { + this->mod->SPIwriteRegisterBurst(reg | RADIOLIB_CC1101_CMD_BURST, data, len); +} + +void CC1101::SPIsendCommand(uint8_t cmd) { + // pull NSS low + this->mod->hal->digitalWrite(this->mod->getCs(), this->mod->hal->GpioLevelLow); + + // start transfer + this->mod->hal->spiBeginTransaction(); + + // send the command byte + uint8_t status = 0; + this->mod->hal->spiTransfer(&cmd, 1, &status); + + // stop transfer + this->mod->hal->spiEndTransaction(); + this->mod->hal->digitalWrite(this->mod->getCs(), this->mod->hal->GpioLevelHigh); + RADIOLIB_DEBUG_SPI_PRINTLN("CMD\tW\t%02X\t%02X", cmd, status); + (void)status; +} + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/CC1101/CC1101.h b/software/firmware/source/libraries/RadioLib/src/modules/CC1101/CC1101.h new file mode 100644 index 000000000..cb5efe71e --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/CC1101/CC1101.h @@ -0,0 +1,1025 @@ +#if !defined(_RADIOLIB_CC1101_H) && !RADIOLIB_EXCLUDE_CC1101 +#define _RADIOLIB_CC1101_H + +#include "../../TypeDef.h" +#include "../../Module.h" + +#include "../../protocols/PhysicalLayer/PhysicalLayer.h" + +// CC1101 physical layer properties +#define RADIOLIB_CC1101_FREQUENCY_STEP_SIZE 396.7285156 +#define RADIOLIB_CC1101_MAX_PACKET_LENGTH 63 +#define RADIOLIB_CC1101_CRYSTAL_FREQ 26.0 +#define RADIOLIB_CC1101_DIV_EXPONENT 16 + +// CC1101 SPI commands +#define RADIOLIB_CC1101_CMD_READ 0b10000000 +#define RADIOLIB_CC1101_CMD_WRITE 0b00000000 +#define RADIOLIB_CC1101_CMD_BURST 0b01000000 +#define RADIOLIB_CC1101_CMD_ACCESS_STATUS_REG 0b01000000 +#define RADIOLIB_CC1101_CMD_FIFO_RX 0b10000000 +#define RADIOLIB_CC1101_CMD_FIFO_TX 0b00000000 +#define RADIOLIB_CC1101_CMD_RESET 0x30 +#define RADIOLIB_CC1101_CMD_FSTXON 0x31 +#define RADIOLIB_CC1101_CMD_XOFF 0x32 +#define RADIOLIB_CC1101_CMD_CAL 0x33 +#define RADIOLIB_CC1101_CMD_RX 0x34 +#define RADIOLIB_CC1101_CMD_TX 0x35 +#define RADIOLIB_CC1101_CMD_IDLE 0x36 +#define RADIOLIB_CC1101_CMD_WOR 0x38 +#define RADIOLIB_CC1101_CMD_POWER_DOWN 0x39 +#define RADIOLIB_CC1101_CMD_FLUSH_RX 0x3A +#define RADIOLIB_CC1101_CMD_FLUSH_TX 0x3B +#define RADIOLIB_CC1101_CMD_WOR_RESET 0x3C +#define RADIOLIB_CC1101_CMD_NOP 0x3D + +// CC1101 register map +#define RADIOLIB_CC1101_REG_IOCFG2 0x00 +#define RADIOLIB_CC1101_REG_IOCFG1 0x01 +#define RADIOLIB_CC1101_REG_IOCFG0 0x02 +#define RADIOLIB_CC1101_REG_FIFOTHR 0x03 +#define RADIOLIB_CC1101_REG_SYNC1 0x04 +#define RADIOLIB_CC1101_REG_SYNC0 0x05 +#define RADIOLIB_CC1101_REG_PKTLEN 0x06 +#define RADIOLIB_CC1101_REG_PKTCTRL1 0x07 +#define RADIOLIB_CC1101_REG_PKTCTRL0 0x08 +#define RADIOLIB_CC1101_REG_ADDR 0x09 +#define RADIOLIB_CC1101_REG_CHANNR 0x0A +#define RADIOLIB_CC1101_REG_FSCTRL1 0x0B +#define RADIOLIB_CC1101_REG_FSCTRL0 0x0C +#define RADIOLIB_CC1101_REG_FREQ2 0x0D +#define RADIOLIB_CC1101_REG_FREQ1 0x0E +#define RADIOLIB_CC1101_REG_FREQ0 0x0F +#define RADIOLIB_CC1101_REG_MDMCFG4 0x10 +#define RADIOLIB_CC1101_REG_MDMCFG3 0x11 +#define RADIOLIB_CC1101_REG_MDMCFG2 0x12 +#define RADIOLIB_CC1101_REG_MDMCFG1 0x13 +#define RADIOLIB_CC1101_REG_MDMCFG0 0x14 +#define RADIOLIB_CC1101_REG_DEVIATN 0x15 +#define RADIOLIB_CC1101_REG_MCSM2 0x16 +#define RADIOLIB_CC1101_REG_MCSM1 0x17 +#define RADIOLIB_CC1101_REG_MCSM0 0x18 +#define RADIOLIB_CC1101_REG_FOCCFG 0x19 +#define RADIOLIB_CC1101_REG_BSCFG 0x1A +#define RADIOLIB_CC1101_REG_AGCCTRL2 0x1B +#define RADIOLIB_CC1101_REG_AGCCTRL1 0x1C +#define RADIOLIB_CC1101_REG_AGCCTRL0 0x1D +#define RADIOLIB_CC1101_REG_WOREVT1 0x1E +#define RADIOLIB_CC1101_REG_WOREVT0 0x1F +#define RADIOLIB_CC1101_REG_WORCTRL 0x20 +#define RADIOLIB_CC1101_REG_FREND1 0x21 +#define RADIOLIB_CC1101_REG_FREND0 0x22 +#define RADIOLIB_CC1101_REG_FSCAL3 0x23 +#define RADIOLIB_CC1101_REG_FSCAL2 0x24 +#define RADIOLIB_CC1101_REG_FSCAL1 0x25 +#define RADIOLIB_CC1101_REG_FSCAL0 0x26 +#define RADIOLIB_CC1101_REG_RCCTRL1 0x27 +#define RADIOLIB_CC1101_REG_RCCTRL0 0x28 +#define RADIOLIB_CC1101_REG_FSTEST 0x29 +#define RADIOLIB_CC1101_REG_PTEST 0x2A +#define RADIOLIB_CC1101_REG_AGCTEST 0x2B +#define RADIOLIB_CC1101_REG_TEST2 0x2C +#define RADIOLIB_CC1101_REG_TEST1 0x2D +#define RADIOLIB_CC1101_REG_TEST0 0x2E +#define RADIOLIB_CC1101_REG_PARTNUM 0x30 +#define RADIOLIB_CC1101_REG_VERSION 0x31 +#define RADIOLIB_CC1101_REG_FREQEST 0x32 +#define RADIOLIB_CC1101_REG_LQI 0x33 +#define RADIOLIB_CC1101_REG_RSSI 0x34 +#define RADIOLIB_CC1101_REG_MARCSTATE 0x35 +#define RADIOLIB_CC1101_REG_WORTIME1 0x36 +#define RADIOLIB_CC1101_REG_WORTIME0 0x37 +#define RADIOLIB_CC1101_REG_PKTSTATUS 0x38 +#define RADIOLIB_CC1101_REG_VCO_VC_DAC 0x39 +#define RADIOLIB_CC1101_REG_TXBYTES 0x3A +#define RADIOLIB_CC1101_REG_RXBYTES 0x3B +#define RADIOLIB_CC1101_REG_RCCTRL1_STATUS 0x3C +#define RADIOLIB_CC1101_REG_RCCTRL0_STATUS 0x3D +#define RADIOLIB_CC1101_REG_PATABLE 0x3E +#define RADIOLIB_CC1101_REG_FIFO 0x3F + +// status byte (returned during SPI transactions) MSB LSB DESCRIPTION +#define RADIOLIB_CC1101_STATUS_CHIP_READY 0b00000000 // 7 7 chip ready +#define RADIOLIB_CC1101_STATUS_CHIP_NOT_READY 0b10000000 // 7 7 chip not ready (power/crystal not stable) +#define RADIOLIB_CC1101_STATUS_IDLE 0b00000000 // 6 4 idle +#define RADIOLIB_CC1101_STATUS_RX 0b00010000 // 6 4 Rx +#define RADIOLIB_CC1101_STATUS_TX 0b00100000 // 6 4 Tx +#define RADIOLIB_CC1101_STATUS_FSTXON 0b00110000 // 6 4 Fast Tx ready +#define RADIOLIB_CC1101_STATUS_CALIBRATE 0b01000000 // 6 4 synthesizer calibration running +#define RADIOLIB_CC1101_STATUS_SETTLING 0b01010000 // 6 4 PLL settling +#define RADIOLIB_CC1101_STATUS_RXFIFO_OVERFLOW 0b01100000 // 6 4 Rx FIFO overflow +#define RADIOLIB_CC1101_STATUS_TXFIFO_UNDERFLOW 0b01110000 // 6 4 Tx FIFO underflow + +// RADIOLIB_CC1101_REG_IOCFG2 +#define RADIOLIB_CC1101_GDO2_NORM 0b00000000 // 6 6 GDO2 output: active high (default) +#define RADIOLIB_CC1101_GDO2_INV 0b01000000 // 6 6 active low + +// RADIOLIB_CC1101_REG_IOCFG1 +#define RADIOLIB_CC1101_GDO_DS_LOW 0b00000000 // 7 7 GDOx output drive strength: low (default) +#define RADIOLIB_CC1101_GDO_DS_HIGH 0b10000000 // 7 7 high +#define RADIOLIB_CC1101_GDO1_NORM 0b00000000 // 6 6 GDO1 output: active high (default) +#define RADIOLIB_CC1101_GDO1_INV 0b01000000 // 6 6 active low + +// RADIOLIB_CC1101_REG_IOCFG0 +#define RADIOLIB_CC1101_GDO0_TEMP_SENSOR_OFF 0b00000000 // 7 7 analog temperature sensor output: disabled (default) +#define RADIOLIB_CC1101_GDO0_TEMP_SENSOR_ON 0b10000000 // 7 7 enabled +#define RADIOLIB_CC1101_GDO0_NORM 0b00000000 // 6 6 GDO0 output: active high (default) +#define RADIOLIB_CC1101_GDO0_INV 0b01000000 // 6 6 active low + +// RADIOLIB_CC1101_REG_IOCFG2 + REG_IOCFG1 + REG_IOCFG0 +#define RADIOLIB_CC1101_GDOX_RX_FIFO_FULL 0x00 // 5 0 Rx FIFO full or above threshold +#define RADIOLIB_CC1101_GDOX_RX_FIFO_FULL_OR_PKT_END 0x01 // 5 0 Rx FIFO full or above threshold or reached packet end +#define RADIOLIB_CC1101_GDOX_TX_FIFO_ABOVE_THR 0x02 // 5 0 Tx FIFO above threshold +#define RADIOLIB_CC1101_GDOX_TX_FIFO_FULL 0x03 // 5 0 Tx FIFO full +#define RADIOLIB_CC1101_GDOX_RX_FIFO_OVERFLOW 0x04 // 5 0 Rx FIFO overflowed +#define RADIOLIB_CC1101_GDOX_TX_FIFO_UNDERFLOW 0x05 // 5 0 Tx FIFO underflowed +#define RADIOLIB_CC1101_GDOX_SYNC_WORD_SENT_OR_PKT_RECEIVED 0x06 // 5 0 sync word was sent or packet was received +#define RADIOLIB_CC1101_GDOX_PKT_RECEIVED_CRC_OK 0x07 // 5 0 packet received and CRC check passed +#define RADIOLIB_CC1101_GDOX_PREAMBLE_QUALITY_REACHED 0x08 // 5 0 received preamble quality is above threshold +#define RADIOLIB_CC1101_GDOX_CHANNEL_CLEAR 0x09 // 5 0 RSSI level below threshold (channel is clear) +#define RADIOLIB_CC1101_GDOX_PLL_LOCKED 0x0A // 5 0 PLL is locked +#define RADIOLIB_CC1101_GDOX_SERIAL_CLOCK 0x0B // 5 0 serial data clock +#define RADIOLIB_CC1101_GDOX_SERIAL_DATA_SYNC 0x0C // 5 0 serial data output in: synchronous mode +#define RADIOLIB_CC1101_GDOX_SERIAL_DATA_ASYNC 0x0D // 5 0 asynchronous mode +#define RADIOLIB_CC1101_GDOX_CARRIER_SENSE 0x0E // 5 0 RSSI above threshold +#define RADIOLIB_CC1101_GDOX_CRC_OK 0x0F // 5 0 CRC check passed +#define RADIOLIB_CC1101_GDOX_RX_HARD_DATA1 0x16 // 5 0 direct access to demodulated data +#define RADIOLIB_CC1101_GDOX_RX_HARD_DATA0 0x17 // 5 0 direct access to demodulated data +#define RADIOLIB_CC1101_GDOX_PA_PD 0x1B // 5 0 power amplifier circuit is powered down +#define RADIOLIB_CC1101_GDOX_LNA_PD 0x1C // 5 0 low-noise amplifier circuit is powered down +#define RADIOLIB_CC1101_GDOX_RX_SYMBOL_TICK 0x1D // 5 0 direct access to symbol tick of received data +#define RADIOLIB_CC1101_GDOX_WOR_EVNT0 0x24 // 5 0 wake-on-radio event 0 +#define RADIOLIB_CC1101_GDOX_WOR_EVNT1 0x25 // 5 0 wake-on-radio event 1 +#define RADIOLIB_CC1101_GDOX_CLK_256 0x26 // 5 0 256 Hz clock +#define RADIOLIB_CC1101_GDOX_CLK_32K 0x27 // 5 0 32 kHz clock +#define RADIOLIB_CC1101_GDOX_CHIP_RDYN 0x29 // 5 0 (default for GDO2) +#define RADIOLIB_CC1101_GDOX_XOSC_STABLE 0x2B // 5 0 +#define RADIOLIB_CC1101_GDOX_HIGH_Z 0x2E // 5 0 high impedance state (default for GDO1) +#define RADIOLIB_CC1101_GDOX_HW_TO_0 0x2F // 5 0 +#define RADIOLIB_CC1101_GDOX_CLOCK_XOSC_1 0x30 // 5 0 crystal oscillator clock: f = f(XOSC)/1 +#define RADIOLIB_CC1101_GDOX_CLOCK_XOSC_1_5 0x31 // 5 0 f = f(XOSC)/1.5 +#define RADIOLIB_CC1101_GDOX_CLOCK_XOSC_2 0x32 // 5 0 f = f(XOSC)/2 +#define RADIOLIB_CC1101_GDOX_CLOCK_XOSC_3 0x33 // 5 0 f = f(XOSC)/3 +#define RADIOLIB_CC1101_GDOX_CLOCK_XOSC_4 0x34 // 5 0 f = f(XOSC)/4 +#define RADIOLIB_CC1101_GDOX_CLOCK_XOSC_6 0x35 // 5 0 f = f(XOSC)/6 +#define RADIOLIB_CC1101_GDOX_CLOCK_XOSC_8 0x36 // 5 0 f = f(XOSC)/8 +#define RADIOLIB_CC1101_GDOX_CLOCK_XOSC_12 0x37 // 5 0 f = f(XOSC)/12 +#define RADIOLIB_CC1101_GDOX_CLOCK_XOSC_16 0x38 // 5 0 f = f(XOSC)/16 +#define RADIOLIB_CC1101_GDOX_CLOCK_XOSC_24 0x39 // 5 0 f = f(XOSC)/24 +#define RADIOLIB_CC1101_GDOX_CLOCK_XOSC_32 0x3A // 5 0 f = f(XOSC)/32 +#define RADIOLIB_CC1101_GDOX_CLOCK_XOSC_48 0x3B // 5 0 f = f(XOSC)/48 +#define RADIOLIB_CC1101_GDOX_CLOCK_XOSC_64 0x3C // 5 0 f = f(XOSC)/64 +#define RADIOLIB_CC1101_GDOX_CLOCK_XOSC_96 0x3D // 5 0 f = f(XOSC)/96 +#define RADIOLIB_CC1101_GDOX_CLOCK_XOSC_128 0x3E // 5 0 f = f(XOSC)/128 +#define RADIOLIB_CC1101_GDOX_CLOCK_XOSC_192 0x3F // 5 0 f = f(XOSC)/192 (default for GDO0) + +// RADIOLIB_CC1101_REG_FIFOTHR +#define RADIOLIB_CC1101_ADC_RETENTION_OFF 0b00000000 // 6 6 do not retain ADC settings in sleep mode (default) +#define RADIOLIB_CC1101_ADC_RETENTION_ON 0b01000000 // 6 6 retain ADC settings in sleep mode +#define RADIOLIB_CC1101_RX_ATTEN_0_DB 0b00000000 // 5 4 Rx attenuation: 0 dB (default) +#define RADIOLIB_CC1101_RX_ATTEN_6_DB 0b00010000 // 5 4 6 dB +#define RADIOLIB_CC1101_RX_ATTEN_12_DB 0b00100000 // 5 4 12 dB +#define RADIOLIB_CC1101_RX_ATTEN_18_DB 0b00110000 // 5 4 18 dB +#define RADIOLIB_CC1101_FIFO_THR_TX_61_RX_4 0b00000000 // 3 0 TX fifo threshold: 61, RX fifo threshold: 4 +#define RADIOLIB_CC1101_FIFO_THR_TX_33_RX_32 0b00000111 // 3 0 TX fifo threshold: 33, RX fifo threshold: 32 +#define RADIOLIB_CC1101_FIFO_THRESH_TX 33 +#define RADIOLIB_CC1101_FIFO_THRESH_RX 32 + +// RADIOLIB_CC1101_REG_SYNC1 +#define RADIOLIB_CC1101_SYNC_WORD_MSB 0xD3 // 7 0 sync word MSB + +// RADIOLIB_CC1101_REG_SYNC0 +#define RADIOLIB_CC1101_SYNC_WORD_LSB 0x91 // 7 0 sync word LSB + +// RADIOLIB_CC1101_REG_PKTLEN +#define RADIOLIB_CC1101_PACKET_LENGTH 0xFF // 7 0 packet length in bytes + +// RADIOLIB_CC1101_REG_PKTCTRL1 +#define RADIOLIB_CC1101_PQT 0x00 // 7 5 preamble quality threshold +#define RADIOLIB_CC1101_CRC_AUTOFLUSH_OFF 0b00000000 // 3 3 automatic Rx FIFO flush on CRC check fail: disabled (default) +#define RADIOLIB_CC1101_CRC_AUTOFLUSH_ON 0b00001000 // 3 3 enabled +#define RADIOLIB_CC1101_APPEND_STATUS_OFF 0b00000000 // 2 2 append 2 status bytes to packet: disabled +#define RADIOLIB_CC1101_APPEND_STATUS_ON 0b00000100 // 2 2 enabled (default) +#define RADIOLIB_CC1101_ADR_CHK_NONE 0b00000000 // 1 0 address check: none (default) +#define RADIOLIB_CC1101_ADR_CHK_NO_BROADCAST 0b00000001 // 1 0 without broadcast +#define RADIOLIB_CC1101_ADR_CHK_SINGLE_BROADCAST 0b00000010 // 1 0 broadcast address 0x00 +#define RADIOLIB_CC1101_ADR_CHK_DOUBLE_BROADCAST 0b00000011 // 1 0 broadcast addresses 0x00 and 0xFF + +// RADIOLIB_CC1101_REG_PKTCTRL0 +#define RADIOLIB_CC1101_WHITE_DATA_OFF 0b00000000 // 6 6 data whitening: disabled +#define RADIOLIB_CC1101_WHITE_DATA_ON 0b01000000 // 6 6 enabled (default) +#define RADIOLIB_CC1101_PKT_FORMAT_NORMAL 0b00000000 // 5 4 packet format: normal (FIFOs) +#define RADIOLIB_CC1101_PKT_FORMAT_SYNCHRONOUS 0b00010000 // 5 4 synchronous serial +#define RADIOLIB_CC1101_PKT_FORMAT_RANDOM 0b00100000 // 5 4 random transmissions +#define RADIOLIB_CC1101_PKT_FORMAT_ASYNCHRONOUS 0b00110000 // 5 4 asynchronous serial +#define RADIOLIB_CC1101_CRC_OFF 0b00000000 // 2 2 CRC disabled +#define RADIOLIB_CC1101_CRC_ON 0b00000100 // 2 2 CRC enabled (default) +#define RADIOLIB_CC1101_LENGTH_CONFIG_FIXED 0b00000000 // 1 0 packet length: fixed +#define RADIOLIB_CC1101_LENGTH_CONFIG_VARIABLE 0b00000001 // 1 0 variable (default) +#define RADIOLIB_CC1101_LENGTH_CONFIG_INFINITE 0b00000010 // 1 0 infinite + +// RADIOLIB_CC1101_REG_ADDR +#define RADIOLIB_CC1101_DEVICE_ADDR 0x00 // 7 0 device address + +// RADIOLIB_CC1101_REG_CHANNR +#define RADIOLIB_CC1101_CHAN 0x00 // 7 0 channel number + +// RADIOLIB_CC1101_REG_FSCTRL1 +#define RADIOLIB_CC1101_FREQ_IF 0x0F // 4 0 IF frequency setting; f_IF = (f(XOSC) / 2^10) * CC1101_FREQ_IF + +// CC1101_REG_FSCTRL0 +#define RADIOLIB_CC1101_FREQOFF 0x00 // 7 0 base frequency offset (2s-compliment) + +// RADIOLIB_CC1101_REG_FREQ2 + REG_FREQ1 + REG_FREQ0 +#define RADIOLIB_CC1101_FREQ_MSB 0x1E // 5 0 base frequency setting: f_carrier = (f(XOSC) / 2^16) * FREQ +#define RADIOLIB_CC1101_FREQ_MID 0xC4 // 7 0 where f(XOSC) = 26 MHz +#define RADIOLIB_CC1101_FREQ_LSB 0xEC // 7 0 FREQ = 3-byte value of FREQ registers + +// RADIOLIB_CC1101_REG_MDMCFG4 +#define RADIOLIB_CC1101_CHANBW_E 0b10000000 // 7 6 channel bandwidth: BW_channel = f(XOSC) / (8 * (4 + CHANBW_M)*2^CHANBW_E) [Hz] +#define RADIOLIB_CC1101_CHANBW_M 0b00000000 // 5 4 default value for 26 MHz crystal: 203 125 Hz +#define RADIOLIB_CC1101_DRATE_E 0x0C // 3 0 symbol rate: R_data = (((256 + DRATE_M) * 2^DRATE_E) / 2^28) * f(XOSC) [Baud] + +// RADIOLIB_CC1101_REG_MDMCFG3 +#define RADIOLIB_CC1101_DRATE_M 0x22 // 7 0 default value for 26 MHz crystal: 115 051 Baud + +// RADIOLIB_CC1101_REG_MDMCFG2 +#define RADIOLIB_CC1101_DEM_DCFILT_OFF 0b10000000 // 7 7 digital DC filter: disabled +#define RADIOLIB_CC1101_DEM_DCFILT_ON 0b00000000 // 7 7 enabled - only for data rates above 250 kBaud (default) +#define RADIOLIB_CC1101_MOD_FORMAT_2_FSK 0b00000000 // 6 4 modulation format: 2-FSK (default) +#define RADIOLIB_CC1101_MOD_FORMAT_GFSK 0b00010000 // 6 4 GFSK +#define RADIOLIB_CC1101_MOD_FORMAT_ASK_OOK 0b00110000 // 6 4 ASK/OOK +#define RADIOLIB_CC1101_MOD_FORMAT_4_FSK 0b01000000 // 6 4 4-FSK +#define RADIOLIB_CC1101_MOD_FORMAT_MFSK 0b01110000 // 6 4 MFSK - only for data rates above 26 kBaud +#define RADIOLIB_CC1101_MANCHESTER_EN_OFF 0b00000000 // 3 3 Manchester encoding: disabled (default) +#define RADIOLIB_CC1101_MANCHESTER_EN_ON 0b00001000 // 3 3 enabled +#define RADIOLIB_CC1101_SYNC_MODE_NONE 0b00000000 // 2 0 synchronization: no preamble/sync +#define RADIOLIB_CC1101_SYNC_MODE_15_16 0b00000001 // 2 0 15/16 sync word bits +#define RADIOLIB_CC1101_SYNC_MODE_16_16 0b00000010 // 2 0 16/16 sync word bits (default) +#define RADIOLIB_CC1101_SYNC_MODE_30_32 0b00000011 // 2 0 30/32 sync word bits +#define RADIOLIB_CC1101_SYNC_MODE_NONE_THR 0b00000100 // 2 0 no preamble sync, carrier sense above threshold +#define RADIOLIB_CC1101_SYNC_MODE_15_16_THR 0b00000101 // 2 0 15/16 sync word bits, carrier sense above threshold +#define RADIOLIB_CC1101_SYNC_MODE_16_16_THR 0b00000110 // 2 0 16/16 sync word bits, carrier sense above threshold +#define RADIOLIB_CC1101_SYNC_MODE_30_32_THR 0b00000111 // 2 0 30/32 sync word bits, carrier sense above threshold + +// RADIOLIB_CC1101_REG_MDMCFG1 +#define RADIOLIB_CC1101_FEC_OFF 0b00000000 // 7 7 forward error correction: disabled (default) +#define RADIOLIB_CC1101_FEC_ON 0b10000000 // 7 7 enabled - only for fixed packet length +#define RADIOLIB_CC1101_NUM_PREAMBLE_2 0b00000000 // 6 4 number of preamble bytes: 2 +#define RADIOLIB_CC1101_NUM_PREAMBLE_3 0b00010000 // 6 4 3 +#define RADIOLIB_CC1101_NUM_PREAMBLE_4 0b00100000 // 6 4 4 (default) +#define RADIOLIB_CC1101_NUM_PREAMBLE_6 0b00110000 // 6 4 6 +#define RADIOLIB_CC1101_NUM_PREAMBLE_8 0b01000000 // 6 4 8 +#define RADIOLIB_CC1101_NUM_PREAMBLE_12 0b01010000 // 6 4 12 +#define RADIOLIB_CC1101_NUM_PREAMBLE_16 0b01100000 // 6 4 16 +#define RADIOLIB_CC1101_NUM_PREAMBLE_24 0b01110000 // 6 4 24 +#define RADIOLIB_CC1101_CHANSPC_E 0x02 // 1 0 channel spacing: df_channel = (f(XOSC) / 2^18) * (256 + CHANSPC_M) * 2^CHANSPC_E [Hz] + +// RADIOLIB_CC1101_REG_MDMCFG0 +#define RADIOLIB_CC1101_CHANSPC_M 0xF8 // 7 0 default value for 26 MHz crystal: 199 951 kHz + +// RADIOLIB_CC1101_REG_DEVIATN +#define RADIOLIB_CC1101_DEVIATION_E 0b01000000 // 6 4 frequency deviation: f_dev = (f(XOSC) / 2^17) * (8 + DEVIATION_M) * 2^DEVIATION_E [Hz] +#define RADIOLIB_CC1101_DEVIATION_M 0b00000111 // 2 0 default value for 26 MHz crystal: +- 47 607 Hz +#define RADIOLIB_CC1101_MSK_PHASE_CHANGE_PERIOD 0x07 // 2 0 phase change symbol period fraction: 1 / (MSK_PHASE_CHANGE_PERIOD + 1) + +// RADIOLIB_CC1101_REG_MCSM2 +#define RADIOLIB_CC1101_RX_TIMEOUT_RSSI_OFF 0b00000000 // 4 4 Rx timeout based on RSSI value: disabled (default) +#define RADIOLIB_CC1101_RX_TIMEOUT_RSSI_ON 0b00010000 // 4 4 enabled +#define RADIOLIB_CC1101_RX_TIMEOUT_QUAL_OFF 0b00000000 // 3 3 check for sync word on Rx timeout +#define RADIOLIB_CC1101_RX_TIMEOUT_QUAL_ON 0b00001000 // 3 3 check for PQI set on Rx timeout +#define RADIOLIB_CC1101_RX_TIMEOUT_OFF 0b00000111 // 2 0 Rx timeout: disabled (default) +#define RADIOLIB_CC1101_RX_TIMEOUT_MAX 0b00000000 // 2 0 max value (actual value depends on WOR_RES, EVENT0 and f(XOSC)) + +// RADIOLIB_CC1101_REG_MCSM1 +#define RADIOLIB_CC1101_CCA_MODE_ALWAYS 0b00000000 // 5 4 clear channel indication: always +#define RADIOLIB_CC1101_CCA_MODE_RSSI_THR 0b00010000 // 5 4 RSSI below threshold +#define RADIOLIB_CC1101_CCA_MODE_RX_PKT 0b00100000 // 5 4 unless receiving packet +#define RADIOLIB_CC1101_CCA_MODE_RSSI_THR_RX_PKT 0b00110000 // 5 4 RSSI below threshold unless receiving packet (default) +#define RADIOLIB_CC1101_RXOFF_IDLE 0b00000000 // 3 2 next mode after packet reception: idle (default) +#define RADIOLIB_CC1101_RXOFF_FSTXON 0b00000100 // 3 2 FSTxOn +#define RADIOLIB_CC1101_RXOFF_TX 0b00001000 // 3 2 Tx +#define RADIOLIB_CC1101_RXOFF_RX 0b00001100 // 3 2 Rx +#define RADIOLIB_CC1101_TXOFF_IDLE 0b00000000 // 1 0 next mode after packet transmission: idle (default) +#define RADIOLIB_CC1101_TXOFF_FSTXON 0b00000001 // 1 0 FSTxOn +#define RADIOLIB_CC1101_TXOFF_TX 0b00000010 // 1 0 Tx +#define RADIOLIB_CC1101_TXOFF_RX 0b00000011 // 1 0 Rx + +// RADIOLIB_CC1101_REG_MCSM0 +#define RADIOLIB_CC1101_FS_AUTOCAL_NEVER 0b00000000 // 5 4 automatic calibration: never (default) +#define RADIOLIB_CC1101_FS_AUTOCAL_IDLE_TO_RXTX 0b00010000 // 5 4 every transition from idle to Rx/Tx +#define RADIOLIB_CC1101_FS_AUTOCAL_RXTX_TO_IDLE 0b00100000 // 5 4 every transition from Rx/Tx to idle +#define RADIOLIB_CC1101_FS_AUTOCAL_RXTX_TO_IDLE_4TH 0b00110000 // 5 4 every 4th transition from Rx/Tx to idle +#define RADIOLIB_CC1101_PO_TIMEOUT_COUNT_1 0b00000000 // 3 2 number of counter expirations before CHP_RDYN goes low: 1 (default) +#define RADIOLIB_CC1101_PO_TIMEOUT_COUNT_16 0b00000100 // 3 2 16 +#define RADIOLIB_CC1101_PO_TIMEOUT_COUNT_64 0b00001000 // 3 2 64 +#define RADIOLIB_CC1101_PO_TIMEOUT_COUNT_256 0b00001100 // 3 2 256 +#define RADIOLIB_CC1101_PIN_CTRL_OFF 0b00000000 // 1 1 pin radio control: disabled (default) +#define RADIOLIB_CC1101_PIN_CTRL_ON 0b00000010 // 1 1 enabled +#define RADIOLIB_CC1101_XOSC_FORCE_OFF 0b00000000 // 0 0 do not force XOSC to remain on in sleep (default) +#define RADIOLIB_CC1101_XOSC_FORCE_ON 0b00000001 // 0 0 force XOSC to remain on in sleep + +// RADIOLIB_CC1101_REG_FOCCFG +#define RADIOLIB_CC1101_FOC_BS_CS_GATE_OFF 0b00000000 // 5 5 do not freeze frequency compensation until CS goes high +#define RADIOLIB_CC1101_FOC_BS_CS_GATE_ON 0b00100000 // 5 5 freeze frequency compensation until CS goes high (default) +#define RADIOLIB_CC1101_FOC_PRE_K 0b00000000 // 4 3 frequency compensation loop gain before sync word: K +#define RADIOLIB_CC1101_FOC_PRE_2K 0b00001000 // 4 3 2K +#define RADIOLIB_CC1101_FOC_PRE_3K 0b00010000 // 4 3 3K (default) +#define RADIOLIB_CC1101_FOC_PRE_4K 0b00011000 // 4 3 4K +#define RADIOLIB_CC1101_FOC_POST_K 0b00000000 // 2 2 frequency compensation loop gain after sync word: same as FOC_PRE +#define RADIOLIB_CC1101_FOC_POST_K_2 0b00000100 // 2 2 K/2 (default) +#define RADIOLIB_CC1101_FOC_LIMIT_NO_COMPENSATION 0b00000000 // 1 0 frequency compensation saturation point: no compensation - required for ASK/OOK +#define RADIOLIB_CC1101_FOC_LIMIT_BW_CHAN_8 0b00000001 // 1 0 +- BW_chan/8 +#define RADIOLIB_CC1101_FOC_LIMIT_BW_CHAN_4 0b00000010 // 1 0 +- BW_chan/4 (default) +#define RADIOLIB_CC1101_FOC_LIMIT_BW_CHAN_2 0b00000011 // 1 0 +- BW_chan/2 + +// RADIOLIB_CC1101_REG_BSCFG +#define RADIOLIB_CC1101_BS_PRE_KI 0b00000000 // 7 6 clock recovery integral gain before sync word: Ki +#define RADIOLIB_CC1101_BS_PRE_2KI 0b01000000 // 7 6 2Ki (default) +#define RADIOLIB_CC1101_BS_PRE_3KI 0b10000000 // 7 6 3Ki +#define RADIOLIB_CC1101_BS_PRE_4KI 0b11000000 // 7 6 4Ki +#define RADIOLIB_CC1101_BS_PRE_KP 0b00000000 // 5 4 clock recovery proportional gain before sync word: Kp +#define RADIOLIB_CC1101_BS_PRE_2KP 0b00010000 // 5 4 2Kp +#define RADIOLIB_CC1101_BS_PRE_3KP 0b00100000 // 5 4 3Kp (default) +#define RADIOLIB_CC1101_BS_PRE_4KP 0b00110000 // 5 4 4Kp +#define RADIOLIB_CC1101_BS_POST_KI 0b00000000 // 3 3 clock recovery integral gain after sync word: same as BS_PRE +#define RADIOLIB_CC1101_BS_POST_KI_2 0b00001000 // 3 3 Ki/2 (default) +#define RADIOLIB_CC1101_BS_POST_KP 0b00000000 // 2 2 clock recovery proportional gain after sync word: same as BS_PRE +#define RADIOLIB_CC1101_BS_POST_KP_1 0b00000100 // 2 2 Kp (default) +#define RADIOLIB_CC1101_BS_LIMIT_NO_COMPENSATION 0b00000000 // 1 0 data rate compensation saturation point: no compensation +#define RADIOLIB_CC1101_BS_LIMIT_3_125 0b00000001 // 1 0 +- 3.125 % +#define RADIOLIB_CC1101_BS_LIMIT_6_25 0b00000010 // 1 0 +- 6.25 % +#define RADIOLIB_CC1101_BS_LIMIT_12_5 0b00000011 // 1 0 +- 12.5 % + +// RADIOLIB_CC1101_REG_AGCCTRL2 +#define RADIOLIB_CC1101_MAX_DVGA_GAIN_0 0b00000000 // 7 6 reduce maximum available DVGA gain: no reduction (default) +#define RADIOLIB_CC1101_MAX_DVGA_GAIN_1 0b01000000 // 7 6 disable top gain setting +#define RADIOLIB_CC1101_MAX_DVGA_GAIN_2 0b10000000 // 7 6 disable top two gain setting +#define RADIOLIB_CC1101_MAX_DVGA_GAIN_3 0b11000000 // 7 6 disable top three gain setting +#define RADIOLIB_CC1101_LNA_GAIN_REDUCE_0_DB 0b00000000 // 5 3 reduce maximum LNA gain by: 0 dB (default) +#define RADIOLIB_CC1101_LNA_GAIN_REDUCE_2_6_DB 0b00001000 // 5 3 2.6 dB +#define RADIOLIB_CC1101_LNA_GAIN_REDUCE_6_1_DB 0b00010000 // 5 3 6.1 dB +#define RADIOLIB_CC1101_LNA_GAIN_REDUCE_7_4_DB 0b00011000 // 5 3 7.4 dB +#define RADIOLIB_CC1101_LNA_GAIN_REDUCE_9_2_DB 0b00100000 // 5 3 9.2 dB +#define RADIOLIB_CC1101_LNA_GAIN_REDUCE_11_5_DB 0b00101000 // 5 3 11.5 dB +#define RADIOLIB_CC1101_LNA_GAIN_REDUCE_14_6_DB 0b00110000 // 5 3 14.6 dB +#define RADIOLIB_CC1101_LNA_GAIN_REDUCE_17_1_DB 0b00111000 // 5 3 17.1 dB +#define RADIOLIB_CC1101_MAGN_TARGET_24_DB 0b00000000 // 2 0 average amplitude target for filter: 24 dB +#define RADIOLIB_CC1101_MAGN_TARGET_27_DB 0b00000001 // 2 0 27 dB +#define RADIOLIB_CC1101_MAGN_TARGET_30_DB 0b00000010 // 2 0 30 dB +#define RADIOLIB_CC1101_MAGN_TARGET_33_DB 0b00000011 // 2 0 33 dB (default) +#define RADIOLIB_CC1101_MAGN_TARGET_36_DB 0b00000100 // 2 0 36 dB +#define RADIOLIB_CC1101_MAGN_TARGET_38_DB 0b00000101 // 2 0 38 dB +#define RADIOLIB_CC1101_MAGN_TARGET_40_DB 0b00000110 // 2 0 40 dB +#define RADIOLIB_CC1101_MAGN_TARGET_42_DB 0b00000111 // 2 0 42 dB + +// RADIOLIB_CC1101_REG_AGCCTRL1 +#define RADIOLIB_CC1101_AGC_LNA_PRIORITY_LNA2 0b00000000 // 6 6 LNA priority setting: LNA2 first +#define RADIOLIB_CC1101_AGC_LNA_PRIORITY_LNA 0b01000000 // 6 6 LNA first (default) +#define RADIOLIB_CC1101_CARRIER_SENSE_REL_THR_OFF 0b00000000 // 5 4 RSSI relative change to assert carrier sense: disabled (default) +#define RADIOLIB_CC1101_CARRIER_SENSE_REL_THR_6_DB 0b00010000 // 5 4 6 dB +#define RADIOLIB_CC1101_CARRIER_SENSE_REL_THR_10_DB 0b00100000 // 5 4 10 dB +#define RADIOLIB_CC1101_CARRIER_SENSE_REL_THR_14_DB 0b00110000 // 5 4 14 dB +#define RADIOLIB_CC1101_CARRIER_SENSE_ABS_THR 0x00 // 3 0 RSSI threshold to assert carrier sense in 2s compliment, Thr = MAGN_TARGET + CARRIER_SENSE_ABS_TH [dB] + +// RADIOLIB_CC1101_REG_AGCCTRL0 +#define RADIOLIB_CC1101_HYST_LEVEL_NONE 0b00000000 // 7 6 AGC hysteresis level: none +#define RADIOLIB_CC1101_HYST_LEVEL_LOW 0b01000000 // 7 6 low +#define RADIOLIB_CC1101_HYST_LEVEL_MEDIUM 0b10000000 // 7 6 medium (default) +#define RADIOLIB_CC1101_HYST_LEVEL_HIGH 0b11000000 // 7 6 high +#define RADIOLIB_CC1101_WAIT_TIME_8_SAMPLES 0b00000000 // 5 4 AGC wait time: 8 samples +#define RADIOLIB_CC1101_WAIT_TIME_16_SAMPLES 0b00010000 // 5 4 16 samples (default) +#define RADIOLIB_CC1101_WAIT_TIME_24_SAMPLES 0b00100000 // 5 4 24 samples +#define RADIOLIB_CC1101_WAIT_TIME_32_SAMPLES 0b00110000 // 5 4 32 samples +#define RADIOLIB_CC1101_AGC_FREEZE_NEVER 0b00000000 // 3 2 freeze AGC gain: never (default) +#define RADIOLIB_CC1101_AGC_FREEZE_SYNC_WORD 0b00000100 // 3 2 when sync word is found +#define RADIOLIB_CC1101_AGC_FREEZE_MANUAL_A 0b00001000 // 3 2 manually freeze analog control +#define RADIOLIB_CC1101_AGC_FREEZE_MANUAL_AD 0b00001100 // 3 2 manually freeze analog and digital control +#define RADIOLIB_CC1101_FILTER_LENGTH_8 0b00000000 // 1 0 averaging length for channel filter: 8 samples +#define RADIOLIB_CC1101_FILTER_LENGTH_16 0b00000001 // 1 0 16 samples (default) +#define RADIOLIB_CC1101_FILTER_LENGTH_32 0b00000010 // 1 0 32 samples +#define RADIOLIB_CC1101_FILTER_LENGTH_64 0b00000011 // 1 0 64 samples +#define RADIOLIB_CC1101_ASK_OOK_BOUNDARY_4_DB 0b00000000 // 1 0 ASK/OOK decision boundary: 4 dB +#define RADIOLIB_CC1101_ASK_OOK_BOUNDARY_8_DB 0b00000001 // 1 0 8 dB (default) +#define RADIOLIB_CC1101_ASK_OOK_BOUNDARY_12_DB 0b00000010 // 1 0 12 dB +#define RADIOLIB_CC1101_ASK_OOK_BOUNDARY_16_DB 0b00000011 // 1 0 16 dB + +// RADIOLIB_CC1101_REG_WOREVT1 + REG_WOREVT0 +#define RADIOLIB_CC1101_EVENT0_TIMEOUT_MSB 0x87 // 7 0 EVENT0 timeout: t_event0 = (750 / f(XOSC)) * EVENT0_TIMEOUT * 2^(5 * WOR_RES) [s] +#define RADIOLIB_CC1101_EVENT0_TIMEOUT_LSB 0x6B // 7 0 default value for 26 MHz crystal: 1.0 s + +// RADIOLIB_CC1101_REG_WORCTRL +#define RADIOLIB_CC1101_RC_POWER_UP 0b00000000 // 7 7 power up RC oscillator +#define RADIOLIB_CC1101_RC_POWER_DOWN 0b10000000 // 7 7 power down RC oscillator +#define RADIOLIB_CC1101_EVENT1_TIMEOUT_4 0b00000000 // 6 4 EVENT1 timeout: 4 RC periods +#define RADIOLIB_CC1101_EVENT1_TIMEOUT_6 0b00010000 // 6 4 6 RC periods +#define RADIOLIB_CC1101_EVENT1_TIMEOUT_8 0b00100000 // 6 4 8 RC periods +#define RADIOLIB_CC1101_EVENT1_TIMEOUT_12 0b00110000 // 6 4 12 RC periods +#define RADIOLIB_CC1101_EVENT1_TIMEOUT_16 0b01000000 // 6 4 16 RC periods +#define RADIOLIB_CC1101_EVENT1_TIMEOUT_24 0b01010000 // 6 4 24 RC periods +#define RADIOLIB_CC1101_EVENT1_TIMEOUT_32 0b01100000 // 6 4 32 RC periods +#define RADIOLIB_CC1101_EVENT1_TIMEOUT_48 0b01110000 // 6 4 48 RC periods (default) +#define RADIOLIB_CC1101_RC_CAL_OFF 0b00000000 // 3 3 disable RC oscillator calibration +#define RADIOLIB_CC1101_RC_CAL_ON 0b00001000 // 3 3 enable RC oscillator calibration (default) +#define RADIOLIB_CC1101_WOR_RES_1 0b00000000 // 1 0 EVENT0 resolution: 1 period (default) +#define RADIOLIB_CC1101_WOR_RES_2_5 0b00000001 // 1 0 2^5 periods +#define RADIOLIB_CC1101_WOR_RES_2_10 0b00000010 // 1 0 2^10 periods +#define RADIOLIB_CC1101_WOR_RES_2_15 0b00000011 // 1 0 2^15 periods + +// RADIOLIB_CC1101_REG_FREND1 +#define RADIOLIB_CC1101_LNA_CURRENT 0x01 // 7 6 front-end LNA PTAT current output adjustment +#define RADIOLIB_CC1101_LNA2MIX_CURRENT 0x01 // 5 4 front-end PTAT output adjustment +#define RADIOLIB_CC1101_LODIV_BUF_CURRENT_RX 0x01 // 3 2 Rx LO buffer current adjustment +#define RADIOLIB_CC1101_MIX_CURRENT 0x02 // 1 0 mixer current adjustment + +// RADIOLIB_CC1101_REG_FREND0 +#define RADIOLIB_CC1101_LODIV_BUF_CURRENT_TX 0x01 // 5 4 Tx LO buffer current adjustment +#define RADIOLIB_CC1101_PA_POWER 0x00 // 2 0 set power amplifier power according to PATABLE + +// RADIOLIB_CC1101_REG_FSCAL3 +#define RADIOLIB_CC1101_CHP_CURR_CAL_OFF 0b00000000 // 5 4 disable charge pump calibration +#define RADIOLIB_CC1101_CHP_CURR_CAL_ON 0b00100000 // 5 4 enable charge pump calibration (default) +#define RADIOLIB_CC1101_FSCAL3 0x09 // 3 0 charge pump output current: I_out = I_0 * 2^(FSCAL3/4) [A] + +// RADIOLIB_CC1101_REG_FSCAL2 +#define RADIOLIB_CC1101_VCO_CORE_LOW 0b00000000 // 5 5 VCO: low (default) +#define RADIOLIB_CC1101_VCO_CORE_HIGH 0b00100000 // 5 5 high +#define RADIOLIB_CC1101_FSCAL2 0x0A // 4 0 VCO current result/override + +// RADIOLIB_CC1101_REG_FSCAL1 +#define RADIOLIB_CC1101_FSCAL1 0x20 // 5 0 capacitor array setting for coarse VCO tuning + +// RADIOLIB_CC1101_REG_FSCAL0 +#define RADIOLIB_CC1101_FSCAL0 0x0D // 6 0 frequency synthesizer calibration setting + +// RADIOLIB_CC1101_REG_RCCTRL1 +#define RADIOLIB_CC1101_RCCTRL1 0x41 // 6 0 RC oscillator configuration + +// RADIOLIB_CC1101_REG_RCCTRL0 +#define RADIOLIB_CC1101_RCCTRL0 0x00 // 6 0 RC oscillator configuration + +// RADIOLIB_CC1101_REG_PTEST +#define RADIOLIB_CC1101_TEMP_SENS_IDLE_OFF 0x7F // 7 0 temperature sensor will not be available in idle mode (default) +#define RADIOLIB_CC1101_TEMP_SENS_IDLE_ON 0xBF // 7 0 temperature sensor will be available in idle mode + +// RADIOLIB_CC1101_REG_TEST0 +#define RADIOLIB_CC1101_VCO_SEL_CAL_OFF 0b00000000 // 1 1 disable VCO selection calibration stage +#define RADIOLIB_CC1101_VCO_SEL_CAL_ON 0b00000010 // 1 1 enable VCO selection calibration stage + +// RADIOLIB_CC1101_REG_PARTNUM +#define RADIOLIB_CC1101_PARTNUM 0x00 + +// RADIOLIB_CC1101_REG_VERSION +#define RADIOLIB_CC1101_VERSION_CURRENT 0x14 +#define RADIOLIB_CC1101_VERSION_LEGACY 0x04 +#define RADIOLIB_CC1101_VERSION_CLONE 0x17 + +// RADIOLIB_CC1101_REG_MARCSTATE +#define RADIOLIB_CC1101_MARC_STATE_SLEEP 0x00 // 4 0 main radio control state: sleep +#define RADIOLIB_CC1101_MARC_STATE_IDLE 0x01 // 4 0 idle +#define RADIOLIB_CC1101_MARC_STATE_XOFF 0x02 // 4 0 XOFF +#define RADIOLIB_CC1101_MARC_STATE_VCOON_MC 0x03 // 4 0 VCOON_MC +#define RADIOLIB_CC1101_MARC_STATE_REGON_MC 0x04 // 4 0 REGON_MC +#define RADIOLIB_CC1101_MARC_STATE_MANCAL 0x05 // 4 0 MANCAL +#define RADIOLIB_CC1101_MARC_STATE_VCOON 0x06 // 4 0 VCOON +#define RADIOLIB_CC1101_MARC_STATE_REGON 0x07 // 4 0 REGON +#define RADIOLIB_CC1101_MARC_STATE_STARTCAL 0x08 // 4 0 STARTCAL +#define RADIOLIB_CC1101_MARC_STATE_BWBOOST 0x09 // 4 0 BWBOOST +#define RADIOLIB_CC1101_MARC_STATE_FS_LOCK 0x0A // 4 0 FS_LOCK +#define RADIOLIB_CC1101_MARC_STATE_IFADCON 0x0B // 4 0 IFADCON +#define RADIOLIB_CC1101_MARC_STATE_ENDCAL 0x0C // 4 0 ENDCAL +#define RADIOLIB_CC1101_MARC_STATE_RX 0x0D // 4 0 RX +#define RADIOLIB_CC1101_MARC_STATE_RX_END 0x0E // 4 0 RX_END +#define RADIOLIB_CC1101_MARC_STATE_RX_RST 0x0F // 4 0 RX_RST +#define RADIOLIB_CC1101_MARC_STATE_TXRX_SWITCH 0x10 // 4 0 TXRX_SWITCH +#define RADIOLIB_CC1101_MARC_STATE_RXFIFO_OVERFLOW 0x11 // 4 0 RXFIFO_OVERFLOW +#define RADIOLIB_CC1101_MARC_STATE_FSTXON 0x12 // 4 0 FSTXON +#define RADIOLIB_CC1101_MARC_STATE_TX 0x13 // 4 0 TX +#define RADIOLIB_CC1101_MARC_STATE_TX_END 0x14 // 4 0 TX_END +#define RADIOLIB_CC1101_MARC_STATE_RXTX_SWITCH 0x15 // 4 0 RXTX_SWITCH +#define RADIOLIB_CC1101_MARC_STATE_TXFIFO_UNDERFLOW 0x16 // 4 0 TXFIFO_UNDERFLOW + +// RADIOLIB_CC1101_REG_WORTIME1 + REG_WORTIME0 +#define RADIOLIB_CC1101_WORTIME_MSB 0x00 // 7 0 WOR timer value +#define RADIOLIB_CC1101_WORTIME_LSB 0x00 // 7 0 + +// RADIOLIB_CC1101_REG_PKTSTATUS +#define RADIOLIB_CC1101_CRC_OK 0b10000000 // 7 7 CRC check passed +#define RADIOLIB_CC1101_CRC_ERROR 0b00000000 // 7 7 CRC check failed +#define RADIOLIB_CC1101_CS 0b01000000 // 6 6 carrier sense +#define RADIOLIB_CC1101_PQT_REACHED 0b00100000 // 5 5 preamble quality reached +#define RADIOLIB_CC1101_CCA 0b00010000 // 4 4 channel clear +#define RADIOLIB_CC1101_SFD 0b00001000 // 3 3 start of frame delimiter - sync word received +#define RADIOLIB_CC1101_GDO2_ACTIVE 0b00000100 // 2 2 GDO2 is active/asserted +#define RADIOLIB_CC1101_GDO0_ACTIVE 0b00000001 // 0 0 GDO0 is active/asserted + +// RadioLib defaults +#define RADIOLIB_CC1101_DEFAULT_FREQ 434.0 +#define RADIOLIB_CC1101_DEFAULT_BR 4.8 +#define RADIOLIB_CC1101_DEFAULT_FREQDEV 5.0 +#define RADIOLIB_CC1101_DEFAULT_RXBW 58.0 +#define RADIOLIB_CC1101_DEFAULT_POWER 10 +#define RADIOLIB_CC1101_DEFAULT_PREAMBLELEN 16 +#define RADIOLIB_CC1101_DEFAULT_SW {0x12, 0xAD} +#define RADIOLIB_CC1101_DEFAULT_SW_LEN 2 + +/*! + \class CC1101 + \brief Control class for %CC1101 module. +*/ +class CC1101: public PhysicalLayer { + public: + // introduce PhysicalLayer overloads + using PhysicalLayer::transmit; + using PhysicalLayer::receive; + using PhysicalLayer::startTransmit; + using PhysicalLayer::readData; + + /*! + \brief Default constructor. + \param module Instance of Module that will be used to communicate with the radio. + */ + // cppcheck-suppress noExplicitConstructor + CC1101(Module* module); + + // basic methods + + /*! + \brief Initialization method. + \param freq Carrier frequency in MHz. Defaults to 434 MHz. + \param br Bit rate to be used in kbps. Defaults to 4.8 kbps. + \param freqDev Frequency deviation from carrier frequency in kHz Defaults to 5.0 kHz. + \param rxBw Receiver bandwidth in kHz. Defaults to 135.0 kHz. + \param pwr Output power in dBm. Defaults to 10 dBm. + \param preambleLength Preamble Length in bits. Defaults to 16 bits. + \returns \ref status_codes + */ + int16_t begin( + float freq = RADIOLIB_CC1101_DEFAULT_FREQ, + float br = RADIOLIB_CC1101_DEFAULT_BR, + float freqDev = RADIOLIB_CC1101_DEFAULT_FREQDEV, + float rxBw = RADIOLIB_CC1101_DEFAULT_RXBW, + int8_t pwr = RADIOLIB_CC1101_DEFAULT_POWER, + uint8_t preambleLength = RADIOLIB_CC1101_DEFAULT_PREAMBLELEN); + + /*! + \brief Reset method - resets the chip using manual reset sequence (without RESET pin). + */ + void reset(); + + /*! + \brief Blocking binary transmit method. + Overloads for string-based transmissions are implemented in PhysicalLayer. + \param data Binary data to be sent. + \param len Number of bytes to send. + \param addr Address to send the data to. Will only be added if address filtering was enabled. + \returns \ref status_codes + */ + int16_t transmit(const uint8_t* data, size_t len, uint8_t addr = 0) override; + + /*! + \brief Blocking binary receive method. + Overloads for string-based transmissions are implemented in PhysicalLayer. + \param data Binary data to be sent. + \param len Number of bytes to send. + \returns \ref status_codes + */ + int16_t receive(uint8_t* data, size_t len) override; + + /*! + \brief Sets the module to standby mode. + \returns \ref status_codes + */ + int16_t standby() override; + + /*! + \brief Sets the module to standby. + \param mode Standby mode to be used. No effect, implemented only for PhysicalLayer compatibility. + \returns \ref status_codes + */ + int16_t standby(uint8_t mode) override; + + /*! + \brief Starts synchronous direct mode transmission. + \param frf Raw RF frequency value. Defaults to 0, required for quick frequency shifts in RTTY. + \returns \ref status_codes + */ + int16_t transmitDirect(uint32_t frf = 0) override; + + /*! + \brief Starts synchronous direct mode reception. + \returns \ref status_codes + */ + int16_t receiveDirect() override; + + /*! + \brief Starts asynchronous direct mode transmission. + \param frf Raw RF frequency value. Defaults to 0, required for quick frequency shifts in RTTY. + \returns \ref status_codes + */ + int16_t transmitDirectAsync(uint32_t frf = 0); + + /*! + \brief Starts asynchronous direct mode reception. + \returns \ref status_codes + */ + int16_t receiveDirectAsync(); + + /*! + \brief Stops direct mode. It is required to call this method to switch from direct transmissions + to packet-based transmissions. + */ + int16_t packetMode(); + + // interrupt methods + + /*! + \brief Sets interrupt service routine to call when GDO0 activates. + \param func ISR to call. + \param dir Signal change direction. + */ + void setGdo0Action(void (*func)(void), uint32_t dir); + + /*! + \brief Clears interrupt service routine to call when GDO0 activates. + */ + void clearGdo0Action(); + + /*! + \brief Sets interrupt service routine to call when GDO2 activates. + \param func ISR to call. + \param dir Signal change direction. + */ + void setGdo2Action(void (*func)(void), uint32_t dir); + + /*! + \brief Clears interrupt service routine to call when GDO0 activates. + */ + void clearGdo2Action(); + + /*! + \brief Sets interrupt service routine to call when a packet is received. + \param func ISR to call. + */ + void setPacketReceivedAction(void (*func)(void)) override; + + /*! + \brief Clears interrupt service routine to call when a packet is received. + */ + void clearPacketReceivedAction() override; + + /*! + \brief Sets interrupt service routine to call when a packet is sent. + \param func ISR to call. + */ + void setPacketSentAction(void (*func)(void)) override; + + /*! + \brief Clears interrupt service routine to call when a packet is sent. + */ + void clearPacketSentAction() override; + + /*! + \brief Interrupt-driven binary transmit method. + Overloads for string-based transmissions are implemented in PhysicalLayer. + \param data Binary data to be sent. + \param len Number of bytes to send. + \param addr Address to send the data to. Will only be added if address filtering was enabled. + \returns \ref status_codes + */ + int16_t startTransmit(const uint8_t* data, size_t len, uint8_t addr = 0) override; + + /*! + \brief Clean up after transmission is done. + \returns \ref status_codes + */ + int16_t finishTransmit() override; + + /*! + \brief Interrupt-driven receive method. GDO0 will be activated when full packet is received. + \returns \ref status_codes + */ + int16_t startReceive() override; + + /*! + \brief Interrupt-driven receive method, implemented for compatibility with PhysicalLayer. + \param timeout Ignored. + \param irqFlags Ignored. + \param irqMask Ignored. + \param len Ignored. + \returns \ref status_codes + */ + int16_t startReceive(uint32_t timeout, uint32_t irqFlags, uint32_t irqMask, size_t len) override; + + /*! + \brief Reads data received after calling startReceive method. When the packet length is not known in advance, + getPacketLength method must be called BEFORE calling readData! + \param data Pointer to array to save the received binary data. + \param len Number of bytes that will be read. When set to 0, the packet length will be retreived automatically. + When more bytes than received are requested, only the number of bytes requested will be returned. + \returns \ref status_codes + */ + int16_t readData(uint8_t* data, size_t len) override; + + // configuration methods + + /*! + \brief Sets carrier frequency. Allowed values are in bands 300.0 to 348.0 MHz, + 387.0 to 464.0 MHz and 779.0 to 928.0 MHz. + \param freq Carrier frequency to be set in MHz. + \returns \ref status_codes + */ + int16_t setFrequency(float freq) override; + + /*! + \brief Sets bit rate. Allowed values range from 0.025 to 600.0 kbps. + \param br Bit rate to be set in kbps. + \returns \ref status_codes + */ + int16_t setBitRate(float br) override; + + /*! + \brief Sets bit rate tolerance in BSCFG register. Allowed values are 0:(0%), 1(3,125%), 2:(6,25%) and 3:(12,5%). + \param brt Bit rate tolerance to be set. + \returns \ref status_codes + */ + int16_t setBitRateTolerance(uint8_t brt); + + /*! + \brief Sets receiver bandwidth. Allowed values are 58, 68, 81, 102, 116, 135, 162, + 203, 232, 270, 325, 406, 464, 541, 650 and 812 kHz. + \param rxBw Receiver bandwidth to be set in kHz. + \returns \ref status_codes + */ + int16_t setRxBandwidth(float rxBw); + + /*! + \brief calculates and sets Rx bandwidth based on the freq, baud and freq uncertainty. + Reimplement of atlas0fd00m's (RfCat) CalculatePktChanBw function. + Modified for worse ppm with the CC1101, and adjusted for the supportted CC1101 bw. + \returns \ref status_codes + */ + int16_t autoSetRxBandwidth(); + + /*! + \brief Sets frequency deviation. Allowed values range from 1.587 to 380.8 kHz. + \param freqDev Frequency deviation to be set in kHz. + \returns \ref status_codes + */ + int16_t setFrequencyDeviation(float freqDev) override; + + /*! + \brief Gets frequency deviation. + \param[out] freqDev Pointer to variable where to save the frequency deviation. + \returns \ref status_codes + */ + int16_t getFrequencyDeviation(float *freqDev); + + /*! + \brief Sets output power. Allowed values are -30, -20, -15, -10, 0, 5, 7 or 10 dBm. + \param pwr Output power to be set in dBm. + \returns \ref status_codes + */ + int16_t setOutputPower(int8_t pwr) override; + + /*! + \brief Check if output power is configurable. + This method is needed for compatibility with PhysicalLayer::checkOutputPower. + \param power Output power in dBm. + \param clipped Clipped output power value to what is possible within the module's range. + \returns \ref status_codes + */ + int16_t checkOutputPower(int8_t power, int8_t* clipped) override; + + /*! + \brief Check if output power is configurable. + \param power Output power in dBm. + \param clipped Clipped output power value to what is possible within the module's range. + \param raw Raw internal value. + \returns \ref status_codes + */ + int16_t checkOutputPower(int8_t power, int8_t* clipped, uint8_t* raw); + + /*! + \brief Sets 16-bit sync word as a two byte value. + \param syncH MSB of the sync word. + \param syncL LSB of the sync word. + \param maxErrBits Maximum allowed number of bit errors in received sync word. Defaults to 0. + \param requireCarrierSense Require carrier sense above threshold in addition to sync word. + \returns \ref status_codes + */ + int16_t setSyncWord(uint8_t syncH, uint8_t syncL, uint8_t maxErrBits = 0, bool requireCarrierSense = false); + + /*! + \brief Sets 1 or 2 bytes of sync word. + \param syncWord Pointer to the array of sync word bytes. + \param len Sync word length in bytes. + \param maxErrBits Maximum allowed number of bit errors in received sync word. Defaults to 0. + \param requireCarrierSense Require carrier sense above threshold in addition to sync word. + \returns \ref status_codes + */ + int16_t setSyncWord(uint8_t* syncWord, uint8_t len, uint8_t maxErrBits = 0, bool requireCarrierSense = false); + + /*! + \brief Sets preamble length. + \param preambleLength Preamble length to be set (in bits), allowed values: 16, 24, 32, 48, 64, 96, 128 and 192. + \param qualityThreshold Preamble quality threshold (PQT) to set. + \returns \ref status_codes + */ + int16_t setPreambleLength(uint8_t preambleLength, uint8_t qualityThreshold); + + /*! + \brief Sets node and broadcast addresses. Calling this method will also enable address filtering. + \param nodeAddr Node address to be set. + \param numBroadcastAddrs Number of broadcast addresses to be used. Can be set to 0 (no broadcast), + 1 (broadcast at 0x00) or 2 (broadcast at 0x00 and 0xFF). + \returns \ref status_codes + */ + int16_t setNodeAddress(uint8_t nodeAddr, uint8_t numBroadcastAddrs = 0); + + /*! + \brief Disables address filtering. Calling this method will also erase previously set addresses. + \returns \ref status_codes + */ + int16_t disableAddressFiltering(); + + /*! + \brief Enables/disables OOK modulation instead of FSK. + \param enableOOK Enable (true) or disable (false) OOK. + \returns \ref status_codes + */ + int16_t setOOK(bool enableOOK); + + /*! + \brief Gets RSSI (Recorded Signal Strength Indicator) of the last received packet. + In direct or asynchronous direct mode, returns the current RSSI level. + \returns RSSI in dBm. + */ + float getRSSI() override; + + /*! + \brief Gets LQI (Link Quality Indicator) of the last received packet. + \returns Last packet LQI (lower is better). + */ + uint8_t getLQI() const; + + /*! + \brief Query modem for the packet length of received payload. + \param update Update received packet length. Will return cached value when set to false. + \returns Length of last received packet in bytes. + */ + size_t getPacketLength(bool update = true) override; + + /*! + \brief Set modem in fixed packet length mode. + \param len Packet length. + \returns \ref status_codes + */ + int16_t fixedPacketLengthMode(uint8_t len = RADIOLIB_CC1101_MAX_PACKET_LENGTH); + + /*! + \brief Set modem in variable packet length mode. + \param maxLen Maximum packet length. + \returns \ref status_codes + */ + int16_t variablePacketLengthMode(uint8_t maxLen = RADIOLIB_CC1101_MAX_PACKET_LENGTH); + + /*! + \brief Enable sync word filtering and generation. + \param maxErrBits Maximum number of allowed error bits in sync word. + \param requireCarrierSense Require carrier sense above threshold in addition to sync word. + \returns \ref status_codes + */ + int16_t enableSyncWordFiltering(uint8_t maxErrBits = 0, bool requireCarrierSense = false); + + /*! + \brief Disable preamble and sync word filtering and generation. + \param requireCarrierSense Require carrier sense above threshold. + \returns \ref status_codes + */ + int16_t disableSyncWordFiltering(bool requireCarrierSense = false); + + /*! + \brief Enable CRC filtering and generation. + \param enable Set or unset CRC generation and filtering. + \returns \ref status_codes + */ + int16_t setCrcFiltering(bool enable = true); + + /*! + \brief Set modem in "sniff" mode: no packet filtering (e.g., no preamble, sync word, address, CRC). + \param enable Set or unset promiscuous mode. + \param requireCarrierSense Set carriersense required above threshold, defaults to false. + \returns \ref status_codes + */ + int16_t setPromiscuousMode(bool enable = true, bool requireCarrierSense = false); + + /*! + \brief Get whether the modem is in promiscuous mode: no packet filtering + (e.g., no preamble, sync word, address, CRC). + \returns Whether the modem is in promiscuous mode. + */ + bool getPromiscuousMode(); + + /*! + \brief Sets Gaussian filter bandwidth-time product that will be used for data shaping. + Allowed value is RADIOLIB_SHAPING_0_5. Set to RADIOLIB_SHAPING_NONE to disable data shaping. + \param sh Gaussian shaping bandwidth-time product that will be used for data shaping. + \returns \ref status_codes + */ + int16_t setDataShaping(uint8_t sh) override; + + /*! + \brief Sets transmission encoding. Allowed values are RADIOLIB_ENCODING_NRZ, RADIOLIB_ENCODING_MANCHESTER, and RADIOLIB_ENCODING_WHITENING. + Note that encoding on CC1101 is applied to the entire stream including preamble, sync word, and CRC. + \param encoding Encoding to be used. + \returns \ref status_codes + */ + int16_t setEncoding(uint8_t encoding) override; + + /*! \copydoc Module::setRfSwitchPins */ + void setRfSwitchPins(uint32_t rxEn, uint32_t txEn); + + /*! \copydoc Module::setRfSwitchTable */ + void setRfSwitchTable(const uint32_t (&pins)[Module::RFSWITCH_MAX_PINS], const Module::RfSwitchMode_t table[]); + + /*! + \brief Get one truly random byte from RSSI noise. + \returns TRNG byte. + */ + uint8_t randomByte() override; + + /*! + \brief Read version SPI register. Should return CC1101_VERSION_LEGACY (0x04) or + CC1101_VERSION_CURRENT (0x14) if CC1101 is connected and working. + \returns Version register contents or \ref status_codes + */ + int16_t getChipVersion(); + + #if !RADIOLIB_EXCLUDE_DIRECT_RECEIVE + /*! + \brief Set interrupt service routine function to call when data bit is receveid in direct mode. + \param func Pointer to interrupt service routine. + */ + void setDirectAction(void (*func)(void)) override; + + /*! + \brief Function to read and process data bit in direct reception mode. + \param pin Pin on which to read. + */ + void readBit(uint32_t pin) override; + #endif + + /*! + \brief Configure DIO pin mapping to get a given signal on a DIO pin (if available). + \param pin Pin number onto which a signal is to be placed. + \param value The value that indicates which function to place on that pin. See chip datasheet for details. + \returns \ref status_codes + */ + int16_t setDIOMapping(uint32_t pin, uint32_t value) override; + + #if !RADIOLIB_GODMODE && !RADIOLIB_LOW_LEVEL + protected: + #endif + Module* getMod() override; + + // SPI read overrides to set bit for burst write and status registers access + int16_t SPIgetRegValue(uint8_t reg, uint8_t msb = 7, uint8_t lsb = 0); + int16_t SPIsetRegValue(uint8_t reg, uint8_t value, uint8_t msb = 7, uint8_t lsb = 0, uint8_t checkInterval = 2); + void SPIreadRegisterBurst(uint8_t reg, uint8_t numBytes, uint8_t* inBytes); + uint8_t SPIreadRegister(uint8_t reg); + void SPIwriteRegisterBurst(uint8_t reg, uint8_t* data, size_t len); + void SPIwriteRegister(uint8_t reg, uint8_t data); + + void SPIsendCommand(uint8_t cmd); + + #if !RADIOLIB_GODMODE + private: + #endif + Module* mod; + + float frequency = RADIOLIB_CC1101_DEFAULT_FREQ; + float bitRate = RADIOLIB_CC1101_DEFAULT_BR; + uint8_t rawRSSI = 0; + uint8_t rawLQI = 0; + uint8_t modulation = RADIOLIB_CC1101_MOD_FORMAT_2_FSK; + + size_t packetLength = 0; + bool packetLengthQueried = false; + uint8_t packetLengthConfig = RADIOLIB_CC1101_LENGTH_CONFIG_VARIABLE; + + bool promiscuous = false; + bool crcOn = true; + bool directModeEnabled = false; + + int8_t power = RADIOLIB_CC1101_DEFAULT_POWER; + + int16_t config(); + int16_t transmitDirect(bool sync, uint32_t frf); + int16_t receiveDirect(bool sync); + int16_t directMode(bool sync); + static void getExpMant(float target, uint16_t mantOffset, uint8_t divExp, uint8_t expMax, uint8_t& exp, uint8_t& mant); + int16_t setPacketMode(uint8_t mode, uint16_t len); +}; + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/LLCC68/LLCC68.cpp b/software/firmware/source/libraries/RadioLib/src/modules/LLCC68/LLCC68.cpp new file mode 100644 index 000000000..12de7a52d --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/LLCC68/LLCC68.cpp @@ -0,0 +1,135 @@ +#include "LLCC68.h" +#if !RADIOLIB_EXCLUDE_SX126X + +LLCC68::LLCC68(Module* mod) : SX1262(mod) { + chipType = RADIOLIB_LLCC68_CHIP_TYPE; + this->XTAL = true; +} + +int16_t LLCC68::begin(float freq, float bw, uint8_t sf, uint8_t cr, uint8_t syncWord, int8_t pwr, uint16_t preambleLength, float tcxoVoltage, bool useRegulatorLDO) { + // execute common part + int16_t state = SX126x::begin(cr, syncWord, preambleLength, tcxoVoltage, useRegulatorLDO); + RADIOLIB_ASSERT(state); + + // configure publicly accessible settings + state = setFrequency(freq); + RADIOLIB_ASSERT(state); + + state = setBandwidth(bw); + RADIOLIB_ASSERT(state); + + state = setSpreadingFactor(sf); + RADIOLIB_ASSERT(state); + + state = setOutputPower(pwr); + RADIOLIB_ASSERT(state); + + state = SX126x::fixPaClamping(); + RADIOLIB_ASSERT(state); + + return(state); +} + +int16_t LLCC68::setBandwidth(float bw) { + RADIOLIB_CHECK_RANGE(bw, 100.0, 510.0, RADIOLIB_ERR_INVALID_BANDWIDTH); + return(SX1262::setBandwidth(bw)); +} + +int16_t LLCC68::setSpreadingFactor(uint8_t sf) { + switch(SX126x::bandwidth) { + case RADIOLIB_SX126X_LORA_BW_125_0: + RADIOLIB_CHECK_RANGE(sf, 5, 9, RADIOLIB_ERR_INVALID_SPREADING_FACTOR); + break; + case RADIOLIB_SX126X_LORA_BW_250_0: + RADIOLIB_CHECK_RANGE(sf, 5, 10, RADIOLIB_ERR_INVALID_SPREADING_FACTOR); + break; + case RADIOLIB_SX126X_LORA_BW_500_0: + RADIOLIB_CHECK_RANGE(sf, 5, 11, RADIOLIB_ERR_INVALID_SPREADING_FACTOR); + break; + default: + return(RADIOLIB_ERR_INVALID_SPREADING_FACTOR); + } + + return(SX1262::setSpreadingFactor(sf)); +} + +int16_t LLCC68::setDataRate(DataRate_t dr) { + int16_t state = RADIOLIB_ERR_UNKNOWN; + + // select interpretation based on active modem + uint8_t modem = this->getPacketType(); + if(modem == RADIOLIB_SX126X_PACKET_TYPE_GFSK) { + // set the bit rate + state = this->setBitRate(dr.fsk.bitRate); + RADIOLIB_ASSERT(state); + + // set the frequency deviation + state = this->setFrequencyDeviation(dr.fsk.freqDev); + + } else if(modem == RADIOLIB_SX126X_PACKET_TYPE_LORA) { + // set the spreading factor + state = this->setSpreadingFactor(dr.lora.spreadingFactor); + RADIOLIB_ASSERT(state); + + // set the bandwidth + state = this->setBandwidth(dr.lora.bandwidth); + RADIOLIB_ASSERT(state); + + // set the coding rate + state = this->setCodingRate(dr.lora.codingRate); + } + + return(state); +} + +int16_t LLCC68::checkDataRate(DataRate_t dr) { + int16_t state = RADIOLIB_ERR_UNKNOWN; + + // select interpretation based on active modem + uint8_t modem = this->getPacketType(); + if(modem == RADIOLIB_SX126X_PACKET_TYPE_GFSK) { + RADIOLIB_CHECK_RANGE(dr.fsk.bitRate, 0.6, 300.0, RADIOLIB_ERR_INVALID_BIT_RATE); + RADIOLIB_CHECK_RANGE(dr.fsk.freqDev, 0.6, 200.0, RADIOLIB_ERR_INVALID_FREQUENCY_DEVIATION); + return(RADIOLIB_ERR_NONE); + + } else if(modem == RADIOLIB_SX126X_PACKET_TYPE_LORA) { + RADIOLIB_CHECK_RANGE(dr.lora.bandwidth, 100.0, 510.0, RADIOLIB_ERR_INVALID_BANDWIDTH); + RADIOLIB_CHECK_RANGE(dr.lora.codingRate, 5, 8, RADIOLIB_ERR_INVALID_CODING_RATE); + uint8_t bw_div2 = dr.lora.bandwidth / 2 + 0.01; + switch (bw_div2) { + case 62: // 125.0: + RADIOLIB_CHECK_RANGE(dr.lora.spreadingFactor, 5, 9, RADIOLIB_ERR_INVALID_SPREADING_FACTOR); + break; + case 125: // 250.0 + RADIOLIB_CHECK_RANGE(dr.lora.spreadingFactor, 5, 10, RADIOLIB_ERR_INVALID_SPREADING_FACTOR); + break; + case 250: // 500.0 + RADIOLIB_CHECK_RANGE(dr.lora.spreadingFactor, 5, 11, RADIOLIB_ERR_INVALID_SPREADING_FACTOR); + break; + default: + return(RADIOLIB_ERR_INVALID_BANDWIDTH); + } + return(RADIOLIB_ERR_NONE); + + } + + return(state); +} + +int16_t LLCC68::setModem(ModemType_t modem) { + switch(modem) { + case(ModemType_t::RADIOLIB_MODEM_LORA): { + return(this->begin()); + } break; + case(ModemType_t::RADIOLIB_MODEM_FSK): { + return(this->beginFSK()); + } break; + case(ModemType_t::RADIOLIB_MODEM_LRFHSS): { + return(this->beginLRFHSS()); + } break; + default: + return(RADIOLIB_ERR_WRONG_MODEM); + } +} + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/LLCC68/LLCC68.h b/software/firmware/source/libraries/RadioLib/src/modules/LLCC68/LLCC68.h new file mode 100644 index 000000000..a0b6d19e8 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/LLCC68/LLCC68.h @@ -0,0 +1,89 @@ +#if !defined(_RADIOLIB_LLCC68_H) +#define _RADIOLIB_LLCC68_H + +#include "../../TypeDef.h" + +#if !RADIOLIB_EXCLUDE_SX126X + +#include "../../Module.h" +#include "../SX126x/SX1262.h" + +//RADIOLIB_SX126X_REG_VERSION_STRING +#define RADIOLIB_LLCC68_CHIP_TYPE "LLCC68" + +/*! + \class LLCC68 + \brief Derived class for %LLCC68 modules. +*/ +class LLCC68: public SX1262 { + public: + /*! + \brief Default constructor. + \param mod Instance of Module that will be used to communicate with the radio. + */ + LLCC68(Module* mod); // cppcheck-suppress noExplicitConstructor + + /*! + \brief Initialization method for LoRa modem. + \param freq Carrier frequency in MHz. Defaults to 434.0 MHz. + \param bw LoRa bandwidth in kHz. Defaults to 125.0 kHz. + \param sf LoRa spreading factor. Defaults to 9. + \param cr LoRa coding rate denominator. Defaults to 7 (coding rate 4/7). + \param syncWord 1-byte LoRa sync word. Defaults to RADIOLIB_SX126X_SYNC_WORD_PRIVATE (0x12). + \param pwr Output power in dBm. Defaults to 10 dBm. + \param preambleLength LoRa preamble length in symbols. Defaults to 8 symbols. + \param tcxoVoltage TCXO reference voltage to be set on DIO3. Defaults to 0 V (XTAL). + If you are seeing -706/-707 error codes, it likely means you are using a module with TCXO. + To use TCXO, either set this value to its reference voltage, or set SX126x::XTAL to false. + \param useRegulatorLDO Whether to use only LDO regulator (true) or DC-DC regulator (false). Defaults to false. + \returns \ref status_codes + */ + int16_t begin(float freq = 434.0, float bw = 125.0, uint8_t sf = 9, uint8_t cr = 7, uint8_t syncWord = RADIOLIB_SX126X_SYNC_WORD_PRIVATE, int8_t pwr = 10, uint16_t preambleLength = 8, float tcxoVoltage = 0, bool useRegulatorLDO = false); + + // configuration methods + + /*! + \brief Sets LoRa bandwidth. Allowed values are 125.0, 250.0 and 500.0 kHz. + \param bw LoRa bandwidth to be set in kHz. + \returns \ref status_codes + */ + int16_t setBandwidth(float bw); + + /*! + \brief Sets LoRa spreading factor. Allowed values range from 5 to 11, depending on currently set spreading factor. + \param sf LoRa spreading factor to be set. + \returns \ref status_codes + */ + int16_t setSpreadingFactor(uint8_t sf); + + /*! + \brief Set data. + \param dr Data rate struct. Interpretation depends on currently active modem (FSK or LoRa). + \returns \ref status_codes + */ + int16_t setDataRate(DataRate_t dr) override; + + /*! + \brief Check the data rate can be configured by this module. + \param dr Data rate struct. Interpretation depends on currently active modem (FSK or LoRa). + \returns \ref status_codes + */ + int16_t checkDataRate(DataRate_t dr) override; + + /*! + \brief Set modem for the radio to use. Will perform full reset and reconfigure the radio + using its default parameters. + \param modem Modem type to set - FSK, LoRa or LR-FHSS. + \returns \ref status_codes + */ + int16_t setModem(ModemType_t modem) override; + +#if !RADIOLIB_GODMODE + private: +#endif + +}; + +#endif + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/LR11x0/LR1110.cpp b/software/firmware/source/libraries/RadioLib/src/modules/LR11x0/LR1110.cpp new file mode 100644 index 000000000..1fbc696b5 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/LR11x0/LR1110.cpp @@ -0,0 +1,129 @@ +#include "LR1110.h" +#include + +#if !RADIOLIB_EXCLUDE_LR11X0 + +LR1110::LR1110(Module* mod) : LR11x0(mod) { + chipType = RADIOLIB_LR11X0_DEVICE_LR1110; +} + +int16_t LR1110::begin(float freq, float bw, uint8_t sf, uint8_t cr, uint8_t syncWord, int8_t power, uint16_t preambleLength, float tcxoVoltage) { + // execute common part + int16_t state = LR11x0::begin(bw, sf, cr, syncWord, preambleLength, tcxoVoltage); + RADIOLIB_ASSERT(state); + + // configure publicly accessible settings + state = setFrequency(freq); + RADIOLIB_ASSERT(state); + + state = setOutputPower(power); + return(state); +} + +int16_t LR1110::beginGFSK(float freq, float br, float freqDev, float rxBw, int8_t power, uint16_t preambleLength, float tcxoVoltage) { + // execute common part + int16_t state = LR11x0::beginGFSK(br, freqDev, rxBw, preambleLength, tcxoVoltage); + RADIOLIB_ASSERT(state); + + // configure publicly accessible settings + state = setFrequency(freq); + RADIOLIB_ASSERT(state); + + state = setOutputPower(power); + return(state); +} + +int16_t LR1110::beginLRFHSS(float freq, uint8_t bw, uint8_t cr, bool narrowGrid, int8_t power, float tcxoVoltage) { + // execute common part + int16_t state = LR11x0::beginLRFHSS(bw, cr, narrowGrid, tcxoVoltage); + RADIOLIB_ASSERT(state); + + // configure publicly accessible settings + state = setFrequency(freq); + RADIOLIB_ASSERT(state); + + state = setOutputPower(power); + return(state); +} + +int16_t LR1110::setFrequency(float freq) { + return(this->setFrequency(freq, false)); +} + +int16_t LR1110::setFrequency(float freq, bool skipCalibration, float band) { + RADIOLIB_CHECK_RANGE(freq, 150.0, 960.0, RADIOLIB_ERR_INVALID_FREQUENCY); + + // check if we need to recalibrate image + int16_t state; + if(!skipCalibration && (fabsf(freq - this->freqMHz) >= RADIOLIB_LR11X0_CAL_IMG_FREQ_TRIG_MHZ)) { + state = LR11x0::calibrateImageRejection(freq - band, freq + band); + RADIOLIB_ASSERT(state); + } + + // set frequency + state = LR11x0::setRfFrequency((uint32_t)(freq*1000000.0f)); + RADIOLIB_ASSERT(state); + this->freqMHz = freq; + return(state); +} + +int16_t LR1110::setOutputPower(int8_t power) { + return(this->setOutputPower(power, false)); +} + +int16_t LR1110::setOutputPower(int8_t power, bool forceHighPower) { + // check if power value is configurable + int16_t state = this->checkOutputPower(power, NULL, forceHighPower); + RADIOLIB_ASSERT(state); + + // determine whether to use HP or LP PA and check range accordingly + bool useHp = forceHighPower || (power > 14); + + // TODO how and when to configure OCP? + + // update PA config - always use VBAT for high-power PA + state = setPaConfig((uint8_t)useHp, (uint8_t)useHp, 0x04, 0x07); + RADIOLIB_ASSERT(state); + + // set output power + state = setTxParams(power, RADIOLIB_LR11X0_PA_RAMP_48U); + return(state); +} + +int16_t LR1110::checkOutputPower(int8_t power, int8_t* clipped) { + return(checkOutputPower(power, clipped, false)); +} + +int16_t LR1110::checkOutputPower(int8_t power, int8_t* clipped, bool forceHighPower) { + if(forceHighPower || (power > 14)) { + if(clipped) { + *clipped = RADIOLIB_MAX(-9, RADIOLIB_MIN(22, power)); + } + RADIOLIB_CHECK_RANGE(power, -9, 22, RADIOLIB_ERR_INVALID_OUTPUT_POWER); + + } else { + if(clipped) { + *clipped = RADIOLIB_MAX(-17, RADIOLIB_MIN(14, power)); + } + RADIOLIB_CHECK_RANGE(power, -17, 14, RADIOLIB_ERR_INVALID_OUTPUT_POWER); + + } + return(RADIOLIB_ERR_NONE); +} + +int16_t LR1110::setModem(ModemType_t modem) { + switch(modem) { + case(ModemType_t::RADIOLIB_MODEM_LORA): { + return(this->begin()); + } break; + case(ModemType_t::RADIOLIB_MODEM_FSK): { + return(this->beginGFSK()); + } break; + case(ModemType_t::RADIOLIB_MODEM_LRFHSS): { + return(this->beginLRFHSS()); + } break; + } + return(RADIOLIB_ERR_WRONG_MODEM); +} + +#endif \ No newline at end of file diff --git a/software/firmware/source/libraries/RadioLib/src/modules/LR11x0/LR1110.h b/software/firmware/source/libraries/RadioLib/src/modules/LR11x0/LR1110.h new file mode 100644 index 000000000..b20388593 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/LR11x0/LR1110.h @@ -0,0 +1,145 @@ +#if !defined(_RADIOLIB_LR1110_H) +#define _RADIOLIB_LR1110_H + +#include "../../TypeDef.h" + +#if !RADIOLIB_EXCLUDE_LR11X0 + +#include "../../Module.h" +#include "LR11x0.h" + +/*! + \class LR1110 + \brief Derived class for %LR1110 modules. +*/ +class LR1110: public LR11x0 { + public: + /*! + \brief Default constructor. + \param mod Instance of Module that will be used to communicate with the radio. + */ + LR1110(Module* mod); // cppcheck-suppress noExplicitConstructor + + // basic methods + + /*! + \brief Initialization method for LoRa modem. + \param freq Carrier frequency in MHz. Defaults to 434.0 MHz. + \param bw LoRa bandwidth in kHz. Defaults to 125.0 kHz. + \param sf LoRa spreading factor. Defaults to 9. + \param cr LoRa coding rate denominator. Defaults to 7 (coding rate 4/7). + \param syncWord 1-byte LoRa sync word. Defaults to RADIOLIB_LR11X0_LORA_SYNC_WORD_PRIVATE (0x12). + \param power Output power in dBm. Defaults to 10 dBm. + \param preambleLength LoRa preamble length in symbols. Defaults to 8 symbols. + \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 LR11x0::XTAL to true. + \returns \ref status_codes + */ + int16_t begin(float freq = 434.0, float bw = 125.0, uint8_t sf = 9, uint8_t cr = 7, uint8_t syncWord = RADIOLIB_LR11X0_LORA_SYNC_WORD_PRIVATE, int8_t power = 10, uint16_t preambleLength = 8, float tcxoVoltage = 1.6); + + /*! + \brief Initialization method for FSK modem. + \param freq Carrier frequency in MHz. Defaults to 434.0 MHz. + \param br FSK bit rate in kbps. Defaults to 4.8 kbps. + \param freqDev Frequency deviation from carrier frequency in kHz. Defaults to 5.0 kHz. + \param rxBw Receiver bandwidth in kHz. Defaults to 156.2 kHz. + \param power Output power in dBm. Defaults to 10 dBm. + \param preambleLength FSK preamble length in bits. Defaults to 16 bits. + \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 LR11x0::XTAL to true. + \returns \ref status_codes + */ + int16_t beginGFSK(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); + + /*! + \brief Initialization method for LR-FHSS modem. + \param freq Carrier frequency in MHz. Defaults to 434.0 MHz. + \param bw LR-FHSS bandwidth, one of RADIOLIB_LR11X0_LR_FHSS_BW_* values. Defaults to 722.66 kHz. + \param cr LR-FHSS coding rate, one of RADIOLIB_LR11X0_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 LR11x0::XTAL to true. + \returns \ref status_codes + */ + int16_t beginLRFHSS(float freq = 434.0, uint8_t bw = RADIOLIB_LR11X0_LR_FHSS_BW_722_66, uint8_t cr = RADIOLIB_LR11X0_LR_FHSS_CR_2_3, bool narrowGrid = true, int8_t power = 10, float tcxoVoltage = 1.6); + + // configuration methods + + /*! + \brief Sets carrier frequency. Allowed values are in range from 150.0 to 960.0 MHz. + Will automatically perform image calibration if the frequency changes by + more than RADIOLIB_LR11X0_CAL_IMG_FREQ_TRIG MHz. + \param freq Carrier frequency to be set in MHz. + \returns \ref status_codes + */ + int16_t setFrequency(float freq) override; + + /*! + \brief Sets carrier frequency. Allowed values are in range from 150.0 to 960.0 MHz. + Will automatically perform image calibration if the frequency changes by + more than RADIOLIB_LR11X0_CAL_IMG_FREQ_TRIG MHz. + \param freq Carrier frequency to be set in MHz. + \param skipCalibration Skip automated image calibration. + \param band Half bandwidth for image calibration. For example, + if carrier is 434 MHz and band is set to 4 MHz, then the image will be calibrate + for band 430 - 438 MHz. Unused if calibrate is set to false, defaults to 4 MHz + \returns \ref status_codes + */ + int16_t setFrequency(float freq, bool skipCalibration, float band = 4); + + /*! + \brief Sets output power. Allowed values are in range from -9 to 22 dBm (high-power PA) or -17 to 14 dBm (low-power PA). + \param power Output power to be set in dBm, output PA is determined automatically preferring the low-power PA. + \returns \ref status_codes + */ + int16_t setOutputPower(int8_t power) override; + + /*! + \brief Sets output power. Allowed values are in range from -9 to 22 dBm (high-power PA) or -17 to 14 dBm (low-power PA). + \param power Output power to be set in dBm. + \param forceHighPower Force using the high-power PA. If set to false, PA will be determined automatically + based on configured output power, preferring the low-power PA. If set to true, only high-power PA will be used. + \returns \ref status_codes + */ + int16_t setOutputPower(int8_t power, bool forceHighPower); + + /*! + \brief Check if output power is configurable. + This method is needed for compatibility with PhysicalLayer::checkOutputPower. + \param power Output power in dBm, PA will be determined automatically. + \param clipped Clipped output power value to what is possible within the module's range. + \returns \ref status_codes + */ + int16_t checkOutputPower(int8_t power, int8_t* clipped) override; + + /*! + \brief Check if output power is configurable. + \param power Output power in dBm. + \param clipped Clipped output power value to what is possible within the module's range. + \param forceHighPower Force using the high-power PA. If set to false, PA will be determined automatically + based on configured output power, preferring the low-power PA. If set to true, only high-power PA will be used. + \returns \ref status_codes + */ + int16_t checkOutputPower(int8_t power, int8_t* clipped, bool forceHighPower); + + /*! + \brief Set modem for the radio to use. Will perform full reset and reconfigure the radio + using its default parameters. + \param modem Modem type to set - FSK, LoRa or LR-FHSS. + \returns \ref status_codes + */ + int16_t setModem(ModemType_t modem) override; + +#if !RADIOLIB_GODMODE + private: +#endif + +}; + +#endif + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/LR11x0/LR1120.cpp b/software/firmware/source/libraries/RadioLib/src/modules/LR11x0/LR1120.cpp new file mode 100644 index 000000000..fb6278223 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/LR11x0/LR1120.cpp @@ -0,0 +1,149 @@ +#include "LR1120.h" +#include + +#if !RADIOLIB_EXCLUDE_LR11X0 + +LR1120::LR1120(Module* mod) : LR11x0(mod) { + chipType = RADIOLIB_LR11X0_DEVICE_LR1120; +} + +int16_t LR1120::begin(float freq, float bw, uint8_t sf, uint8_t cr, uint8_t syncWord, int8_t power, uint16_t preambleLength, float tcxoVoltage) { + // execute common part + int16_t state = LR11x0::begin(bw, sf, cr, syncWord, preambleLength, tcxoVoltage, freq > 1000.0); + RADIOLIB_ASSERT(state); + + // configure publicly accessible settings + state = setFrequency(freq); + RADIOLIB_ASSERT(state); + + state = setOutputPower(power); + return(state); +} + +int16_t LR1120::beginGFSK(float freq, float br, float freqDev, float rxBw, int8_t power, uint16_t preambleLength, float tcxoVoltage) { + // execute common part + int16_t state = LR11x0::beginGFSK(br, freqDev, rxBw, preambleLength, tcxoVoltage); + RADIOLIB_ASSERT(state); + + // configure publicly accessible settings + state = setFrequency(freq); + RADIOLIB_ASSERT(state); + + state = setOutputPower(power); + return(state); +} + +int16_t LR1120::beginLRFHSS(float freq, uint8_t bw, uint8_t cr, bool narrowGrid, int8_t power, float tcxoVoltage) { + // execute common part + int16_t state = LR11x0::beginLRFHSS(bw, cr, narrowGrid, tcxoVoltage); + RADIOLIB_ASSERT(state); + + // configure publicly accessible settings + state = setFrequency(freq); + RADIOLIB_ASSERT(state); + + state = setOutputPower(power); + return(state); +} + +int16_t LR1120::setFrequency(float freq) { + return(this->setFrequency(freq, false)); +} + +int16_t LR1120::setFrequency(float freq, bool skipCalibration, float band) { + if(!(((freq >= 150.0) && (freq <= 960.0)) || + ((freq >= 1900.0) && (freq <= 2200.0)) || + ((freq >= 2400.0) && (freq <= 2500.0)))) { + return(RADIOLIB_ERR_INVALID_FREQUENCY); + } + + // check if we need to recalibrate image + int16_t state; + if(!skipCalibration && (fabsf(freq - this->freqMHz) >= RADIOLIB_LR11X0_CAL_IMG_FREQ_TRIG_MHZ)) { + state = LR11x0::calibrateImageRejection(freq - band, freq + band); + RADIOLIB_ASSERT(state); + } + + // set frequency + state = LR11x0::setRfFrequency((uint32_t)(freq*1000000.0f)); + RADIOLIB_ASSERT(state); + this->freqMHz = freq; + this->highFreq = (freq > 1000.0); + return(RADIOLIB_ERR_NONE); +} + +int16_t LR1120::setOutputPower(int8_t power) { + return(this->setOutputPower(power, false)); +} + +int16_t LR1120::setOutputPower(int8_t power, bool forceHighPower) { + // check if power value is configurable + int16_t state = this->checkOutputPower(power, NULL, forceHighPower); + RADIOLIB_ASSERT(state); + + // determine whether to use HP or LP PA and check range accordingly + uint8_t paSel = 0; + uint8_t paSupply = 0; + if(this->highFreq) { + paSel = 2; + } else if(forceHighPower || (power > 14)) { + paSel = 1; + paSupply = 1; + } + + // TODO how and when to configure OCP? + + // update PA config - always use VBAT for high-power PA + state = setPaConfig(paSel, paSupply, 0x04, 0x07); + RADIOLIB_ASSERT(state); + + // set output power + state = setTxParams(power, RADIOLIB_LR11X0_PA_RAMP_48U); + return(state); +} + +int16_t LR1120::checkOutputPower(int8_t power, int8_t* clipped) { + return(checkOutputPower(power, clipped, false)); +} + +int16_t LR1120::checkOutputPower(int8_t power, int8_t* clipped, bool forceHighPower) { + if(this->highFreq) { + if(clipped) { + *clipped = RADIOLIB_MAX(-18, RADIOLIB_MIN(13, power)); + } + RADIOLIB_CHECK_RANGE(power, -18, 13, RADIOLIB_ERR_INVALID_OUTPUT_POWER); + return(RADIOLIB_ERR_NONE); + } + + if(forceHighPower || (power > 14)) { + if(clipped) { + *clipped = RADIOLIB_MAX(-9, RADIOLIB_MIN(22, power)); + } + RADIOLIB_CHECK_RANGE(power, -9, 22, RADIOLIB_ERR_INVALID_OUTPUT_POWER); + + } else { + if(clipped) { + *clipped = RADIOLIB_MAX(-17, RADIOLIB_MIN(14, power)); + } + RADIOLIB_CHECK_RANGE(power, -17, 14, RADIOLIB_ERR_INVALID_OUTPUT_POWER); + + } + return(RADIOLIB_ERR_NONE); +} + +int16_t LR1120::setModem(ModemType_t modem) { + switch(modem) { + case(ModemType_t::RADIOLIB_MODEM_LORA): { + return(this->begin()); + } break; + case(ModemType_t::RADIOLIB_MODEM_FSK): { + return(this->beginGFSK()); + } break; + case(ModemType_t::RADIOLIB_MODEM_LRFHSS): { + return(this->beginLRFHSS()); + } break; + } + return(RADIOLIB_ERR_WRONG_MODEM); +} + +#endif \ No newline at end of file diff --git a/software/firmware/source/libraries/RadioLib/src/modules/LR11x0/LR1120.h b/software/firmware/source/libraries/RadioLib/src/modules/LR11x0/LR1120.h new file mode 100644 index 000000000..3c532ebd1 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/LR11x0/LR1120.h @@ -0,0 +1,158 @@ +#if !defined(_RADIOLIB_LR1120_H) +#define _RADIOLIB_LR1120_H + +#include "../../TypeDef.h" + +#if !RADIOLIB_EXCLUDE_LR11X0 + +#include "../../Module.h" +#include "LR11x0.h" + +/*! + \class LR1120 + \brief Derived class for %LR1120 modules. +*/ +class LR1120: public LR11x0 { + public: + /*! + \brief Default constructor. + \param mod Instance of Module that will be used to communicate with the radio. + */ + LR1120(Module* mod); // cppcheck-suppress noExplicitConstructor + + // basic methods + + /*! + \brief Initialization method for LoRa modem. + \param freq Carrier frequency in MHz. Defaults to 434.0 MHz. + \param bw LoRa bandwidth in kHz. Defaults to 125.0 kHz. + \param sf LoRa spreading factor. Defaults to 9. + \param cr LoRa coding rate denominator. Defaults to 7 (coding rate 4/7). + \param syncWord 1-byte LoRa sync word. Defaults to RADIOLIB_LR11X0_LORA_SYNC_WORD_PRIVATE (0x12). + \param power Output power in dBm. Defaults to 10 dBm. + \param preambleLength LoRa preamble length in symbols. Defaults to 8 symbols. + \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 LR11x0::XTAL to true. + \returns \ref status_codes + */ + int16_t begin(float freq = 434.0, float bw = 125.0, uint8_t sf = 9, uint8_t cr = 7, uint8_t syncWord = RADIOLIB_LR11X0_LORA_SYNC_WORD_PRIVATE, int8_t power = 10, uint16_t preambleLength = 8, float tcxoVoltage = 1.6); + + /*! + \brief Initialization method for FSK modem. + \param freq Carrier frequency in MHz. Defaults to 434.0 MHz. + \param br FSK bit rate in kbps. Defaults to 4.8 kbps. + \param freqDev Frequency deviation from carrier frequency in kHz. Defaults to 5.0 kHz. + \param rxBw Receiver bandwidth in kHz. Defaults to 156.2 kHz. + \param power Output power in dBm. Defaults to 10 dBm. + \param preambleLength FSK preamble length in bits. Defaults to 16 bits. + \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 LR11x0::XTAL to true. + \returns \ref status_codes + */ + int16_t beginGFSK(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); + + /*! + \brief Initialization method for LR-FHSS modem. + \param freq Carrier frequency in MHz. Defaults to 434.0 MHz. + \param bw LR-FHSS bandwidth, one of RADIOLIB_LR11X0_LR_FHSS_BW_* values. Defaults to 722.66 kHz. + \param cr LR-FHSS coding rate, one of RADIOLIB_LR11X0_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 LR11x0::XTAL to true. + \returns \ref status_codes + */ + int16_t beginLRFHSS(float freq = 434.0, uint8_t bw = RADIOLIB_LR11X0_LR_FHSS_BW_722_66, uint8_t cr = RADIOLIB_LR11X0_LR_FHSS_CR_2_3, bool narrowGrid = true, int8_t power = 10, float tcxoVoltage = 1.6); + + // configuration methods + + /*! + \brief Sets carrier frequency. Allowed values are in range from 150.0 to 960.0 MHz, + 1900 - 2200 MHz and 2400 - 2500 MHz. + Will automatically perform image calibration if the frequency changes by + more than RADIOLIB_LR11X0_CAL_IMG_FREQ_TRIG MHz. + NOTE: When switching between sub-GHz and high-frequency bands, after changing the frequency, + setOutputPower() must be called in order to set the correct power amplifier! + \param freq Carrier frequency to be set in MHz. + \returns \ref status_codes + */ + int16_t setFrequency(float freq) override; + + /*! + \brief Sets carrier frequency. Allowed values are in range from 150.0 to 960.0 MHz, + 1900 - 2200 MHz and 2400 - 2500 MHz. + Will automatically perform image calibration if the frequency changes by + more than RADIOLIB_LR11X0_CAL_IMG_FREQ_TRIG MHz. + NOTE: When switching between sub-GHz and high-frequency bands, after changing the frequency, + setOutputPower() must be called in order to set the correct power amplifier! + \param freq Carrier frequency to be set in MHz. + \param skipCalibration Skip automated image calibration. + \param band Half bandwidth for image calibration. For example, + if carrier is 434 MHz and band is set to 4 MHz, then the image will be calibrate + for band 430 - 438 MHz. Unused if calibrate is set to false, defaults to 4 MHz + \returns \ref status_codes + */ + int16_t setFrequency(float freq, bool skipCalibration, float band = 4); + + /*! + \brief Sets output power. Allowed values are in range from -9 to 22 dBm (high-power PA) or -17 to 14 dBm (low-power PA). + \param power Output power to be set in dBm, output PA is determined automatically preferring the low-power PA. + \returns \ref status_codes + */ + int16_t setOutputPower(int8_t power) override; + + /*! + \brief Sets output power. Allowed values are in range from -9 to 22 dBm (high-power PA), -17 to 14 dBm (low-power PA) + or -18 to 13 dBm (high-frequency PA). + \param power Output power to be set in dBm. + \param forceHighPower Force using the high-power PA in sub-GHz bands, or high-frequency PA in 2.4 GHz band. + If set to false, PA will be determined automatically based on configured output power and frequency, + preferring the low-power PA but always using high-frequency PA in 2.4 GHz band. + Ignored when operating in 2.4 GHz band. + \returns \ref status_codes + */ + int16_t setOutputPower(int8_t power, bool forceHighPower); + + /*! + \brief Check if output power is configurable. + This method is needed for compatibility with PhysicalLayer::checkOutputPower. + \param power Output power in dBm, PA will be determined automatically. + \param clipped Clipped output power value to what is possible within the module's range. + \returns \ref status_codes + */ + int16_t checkOutputPower(int8_t power, int8_t* clipped) override; + + /*! + \brief Check if output power is configurable. + \param power Output power in dBm. + \param clipped Clipped output power value to what is possible within the module's range. + \param forceHighPower Force using the high-power PA. If set to false, PA will be determined automatically + based on configured output power, preferring the low-power PA. If set to true, only high-power PA will be used. + Ignored when operating in 2.4 GHz band. + \returns \ref status_codes + */ + int16_t checkOutputPower(int8_t power, int8_t* clipped, bool forceHighPower); + + /*! + \brief Set modem for the radio to use. Will perform full reset and reconfigure the radio + using its default parameters. + \param modem Modem type to set - FSK, LoRa or LR-FHSS. + \returns \ref status_codes + */ + int16_t setModem(ModemType_t modem) override; + +#if !RADIOLIB_GODMODE + private: +#endif + // flag to determine whether we are in the sub-GHz or 2.4 GHz range + // this is needed to automatically detect which PA to use + bool highFreq = false; + +}; + +#endif + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/LR11x0/LR1121.cpp b/software/firmware/source/libraries/RadioLib/src/modules/LR11x0/LR1121.cpp new file mode 100644 index 000000000..6292a3bef --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/LR11x0/LR1121.cpp @@ -0,0 +1,8 @@ +#include "LR1121.h" +#if !RADIOLIB_EXCLUDE_LR11X0 + +LR1121::LR1121(Module* mod) : LR1120(mod) { + chipType = RADIOLIB_LR11X0_DEVICE_LR1121; +} + +#endif \ No newline at end of file diff --git a/software/firmware/source/libraries/RadioLib/src/modules/LR11x0/LR1121.h b/software/firmware/source/libraries/RadioLib/src/modules/LR11x0/LR1121.h new file mode 100644 index 000000000..b1766c4bf --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/LR11x0/LR1121.h @@ -0,0 +1,32 @@ +#if !defined(_RADIOLIB_LR1121_H) +#define _RADIOLIB_LR1121_H + +#include "../../TypeDef.h" + +#if !RADIOLIB_EXCLUDE_LR11X0 + +#include "../../Module.h" +#include "LR11x0.h" +#include "LR1120.h" + +/*! + \class LR1121 + \brief Derived class for %LR1121 modules. +*/ +class LR1121: public LR1120 { + public: + /*! + \brief Default constructor. + \param mod Instance of Module that will be used to communicate with the radio. + */ + LR1121(Module* mod); // cppcheck-suppress noExplicitConstructor + +#if !RADIOLIB_GODMODE + private: +#endif + +}; + +#endif + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/LR11x0/LR11x0.cpp b/software/firmware/source/libraries/RadioLib/src/modules/LR11x0/LR11x0.cpp new file mode 100644 index 000000000..6891cd59e --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/LR11x0/LR11x0.cpp @@ -0,0 +1,3946 @@ +#include "LR11x0.h" + +#include "../../utils/CRC.h" +#include "../../utils/Cryptography.h" + +#include +#include + +#if !RADIOLIB_EXCLUDE_LR11X0 + +LR11x0::LR11x0(Module* mod) : PhysicalLayer(RADIOLIB_LR11X0_FREQUENCY_STEP_SIZE, RADIOLIB_LR11X0_MAX_PACKET_LENGTH) { + this->mod = mod; + this->XTAL = false; + this->irqMap[RADIOLIB_IRQ_TX_DONE] = RADIOLIB_LR11X0_IRQ_TX_DONE; + this->irqMap[RADIOLIB_IRQ_RX_DONE] = RADIOLIB_LR11X0_IRQ_RX_DONE; + this->irqMap[RADIOLIB_IRQ_PREAMBLE_DETECTED] = RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED; + this->irqMap[RADIOLIB_IRQ_SYNC_WORD_VALID] = RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID; + this->irqMap[RADIOLIB_IRQ_HEADER_VALID] = RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID; + this->irqMap[RADIOLIB_IRQ_HEADER_ERR] = RADIOLIB_LR11X0_IRQ_HEADER_ERR; + this->irqMap[RADIOLIB_IRQ_CRC_ERR] = RADIOLIB_LR11X0_IRQ_CRC_ERR; + this->irqMap[RADIOLIB_IRQ_CAD_DONE] = RADIOLIB_LR11X0_IRQ_CAD_DONE; + this->irqMap[RADIOLIB_IRQ_CAD_DETECTED] = RADIOLIB_LR11X0_IRQ_CAD_DETECTED; + this->irqMap[RADIOLIB_IRQ_TIMEOUT] = RADIOLIB_LR11X0_IRQ_TIMEOUT; +} + +int16_t LR11x0::begin(float bw, uint8_t sf, uint8_t cr, uint8_t syncWord, uint16_t preambleLength, float tcxoVoltage, bool high) { + // set module properties and perform initial setup + int16_t state = this->modSetup(tcxoVoltage, RADIOLIB_LR11X0_PACKET_TYPE_LORA); + RADIOLIB_ASSERT(state); + + // configure publicly accessible settings + state = setBandwidth(bw, high); + RADIOLIB_ASSERT(state); + + state = setSpreadingFactor(sf); + RADIOLIB_ASSERT(state); + + state = setCodingRate(cr); + RADIOLIB_ASSERT(state); + + state = setSyncWord(syncWord); + RADIOLIB_ASSERT(state); + + state = setPreambleLength(preambleLength); + RADIOLIB_ASSERT(state); + + // set publicly accessible settings that are not a part of begin method + state = setCRC(2); + RADIOLIB_ASSERT(state); + + state = invertIQ(false); + RADIOLIB_ASSERT(state); + + state = setRegulatorLDO(); + RADIOLIB_ASSERT(state); + + return(RADIOLIB_ERR_NONE); +} + +int16_t LR11x0::beginGFSK(float br, float freqDev, float rxBw, uint16_t preambleLength, float tcxoVoltage) { + // set module properties and perform initial setup + int16_t state = this->modSetup(tcxoVoltage, RADIOLIB_LR11X0_PACKET_TYPE_GFSK); + RADIOLIB_ASSERT(state); + + // configure publicly accessible settings + state = setBitRate(br); + RADIOLIB_ASSERT(state); + + state = setFrequencyDeviation(freqDev); + RADIOLIB_ASSERT(state); + + state = setRxBandwidth(rxBw); + RADIOLIB_ASSERT(state); + + state = setPreambleLength(preambleLength); + RADIOLIB_ASSERT(state); + + // set publicly accessible settings that are not a part of begin method + uint8_t sync[] = { 0x12, 0xAD }; + state = setSyncWord(sync, 2); + RADIOLIB_ASSERT(state); + + state = setDataShaping(RADIOLIB_SHAPING_NONE); + RADIOLIB_ASSERT(state); + + state = setEncoding(RADIOLIB_ENCODING_NRZ); + RADIOLIB_ASSERT(state); + + state = variablePacketLengthMode(RADIOLIB_LR11X0_MAX_PACKET_LENGTH); + RADIOLIB_ASSERT(state); + + state = setCRC(2); + RADIOLIB_ASSERT(state); + + state = setRegulatorLDO(); + RADIOLIB_ASSERT(state); + + return(RADIOLIB_ERR_NONE); +} + +int16_t LR11x0::beginLRFHSS(uint8_t bw, uint8_t cr, bool narrowGrid, float tcxoVoltage) { + // set module properties and perform initial setup + int16_t state = this->modSetup(tcxoVoltage, RADIOLIB_LR11X0_PACKET_TYPE_LR_FHSS); + RADIOLIB_ASSERT(state); + + // set grid spacing + this->lrFhssGrid = narrowGrid ? RADIOLIB_LR11X0_LR_FHSS_GRID_STEP_NON_FCC : RADIOLIB_LR11X0_LR_FHSS_GRID_STEP_FCC; + + // configure publicly accessible settings + state = setLrFhssConfig(bw, cr); + RADIOLIB_ASSERT(state); + + uint8_t syncWord[] = { 0x12, 0xAD, 0x10, 0x1B }; + state = setSyncWord(syncWord, 4); + RADIOLIB_ASSERT(state); + + state = setRegulatorLDO(); + RADIOLIB_ASSERT(state); + + // set fixed configuration + return(setModulationParamsLrFhss(RADIOLIB_LR11X0_LR_FHSS_BIT_RATE_RAW, RADIOLIB_LR11X0_LR_FHSS_SHAPING_GAUSSIAN_BT_1_0)); +} + +int16_t LR11x0::beginGNSS(uint8_t constellations, float tcxoVoltage) { + // set module properties and perform initial setup - packet type does not matter + int16_t state = this->modSetup(tcxoVoltage, RADIOLIB_LR11X0_PACKET_TYPE_LORA); + RADIOLIB_ASSERT(state); + + state = this->clearErrors(); + RADIOLIB_ASSERT(state); + + state = this->configLfClock(RADIOLIB_LR11X0_LF_BUSY_RELEASE_DISABLED | RADIOLIB_LR11X0_LF_CLK_XOSC); + RADIOLIB_ASSERT(state); + + uint16_t errs = 0; + state = this->getErrors(&errs); + RADIOLIB_ASSERT(state); + if(errs & 0x40) { + RADIOLIB_DEBUG_BASIC_PRINTLN("LF_XOSC_START_ERR"); + return(RADIOLIB_ERR_SPI_CMD_FAILED); + } + + state = this->gnssSetConstellationToUse(constellations); + RADIOLIB_ASSERT(state); + + state = setRegulatorLDO(); + RADIOLIB_ASSERT(state); + + return(RADIOLIB_ERR_NONE); +} + +int16_t LR11x0::reset() { + // run the reset sequence + this->mod->hal->pinMode(this->mod->getRst(), this->mod->hal->GpioModeOutput); + this->mod->hal->digitalWrite(this->mod->getRst(), this->mod->hal->GpioLevelLow); + this->mod->hal->delay(10); + this->mod->hal->digitalWrite(this->mod->getRst(), this->mod->hal->GpioLevelHigh); + + // the typical transition duration should be 273 ms + this->mod->hal->delay(300); + + // wait for BUSY to go low + RadioLibTime_t start = this->mod->hal->millis(); + while(this->mod->hal->digitalRead(this->mod->getGpio())) { + this->mod->hal->yield(); + if(this->mod->hal->millis() - start >= 3000) { + RADIOLIB_DEBUG_BASIC_PRINTLN("BUSY pin timeout after reset!"); + return(RADIOLIB_ERR_SPI_CMD_TIMEOUT); + } + } + + return(RADIOLIB_ERR_NONE); +} + +int16_t LR11x0::transmit(const uint8_t* data, size_t len, uint8_t addr) { + // set mode to standby + int16_t state = standby(); + RADIOLIB_ASSERT(state); + + // check packet length + if(len > RADIOLIB_LR11X0_MAX_PACKET_LENGTH) { + return(RADIOLIB_ERR_PACKET_TOO_LONG); + } + + // get currently active modem + uint8_t modem = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + state = getPacketType(&modem); + RADIOLIB_ASSERT(state); + RadioLibTime_t timeout = getTimeOnAir(len); + if(modem == RADIOLIB_LR11X0_PACKET_TYPE_LORA) { + // calculate timeout (150% of expected time-on-air) + timeout = (timeout * 3) / 2; + + } else if((modem == RADIOLIB_LR11X0_PACKET_TYPE_GFSK) || (modem == RADIOLIB_LR11X0_PACKET_TYPE_LR_FHSS)) { + // calculate timeout (500% of expected time-on-air) + timeout = timeout * 5; + + } else { + return(RADIOLIB_ERR_UNKNOWN); + } + + RADIOLIB_DEBUG_BASIC_PRINTLN("Timeout in %lu us", timeout); + + // start transmission + state = startTransmit(data, len, addr); + RADIOLIB_ASSERT(state); + + // wait for packet transmission or timeout + RadioLibTime_t start = this->mod->hal->micros(); + while(!this->mod->hal->digitalRead(this->mod->getIrq())) { + this->mod->hal->yield(); + if(this->mod->hal->micros() - start > timeout) { + finishTransmit(); + return(RADIOLIB_ERR_TX_TIMEOUT); + } + } + RadioLibTime_t elapsed = this->mod->hal->micros() - start; + + // update data rate + this->dataRateMeasured = (len*8.0)/((float)elapsed/1000000.0); + + return(finishTransmit()); +} + +int16_t LR11x0::receive(uint8_t* data, size_t len) { + // set mode to standby + int16_t state = standby(); + RADIOLIB_ASSERT(state); + + RadioLibTime_t timeout = 0; + + // get currently active modem + uint8_t modem = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + state = getPacketType(&modem); + RADIOLIB_ASSERT(state); + if(modem == RADIOLIB_LR11X0_PACKET_TYPE_LORA) { + // calculate timeout (100 LoRa symbols, the default for SX127x series) + float symbolLength = (float)(uint32_t(1) << this->spreadingFactor) / (float)this->bandwidthKhz; + timeout = (RadioLibTime_t)(symbolLength * 100.0); + + } else if(modem == RADIOLIB_LR11X0_PACKET_TYPE_GFSK) { + // calculate timeout (500 % of expected time-one-air) + size_t maxLen = len; + if(len == 0) { + maxLen = 0xFF; + } + float brBps = ((float)(RADIOLIB_LR11X0_CRYSTAL_FREQ) * 1000000.0 * 32.0) / (float)this->bitRate; + timeout = (RadioLibTime_t)(((maxLen * 8.0) / brBps) * 1000.0 * 5.0); + + } else if(modem == RADIOLIB_LR11X0_PACKET_TYPE_LR_FHSS) { + // this modem cannot receive + return(RADIOLIB_ERR_WRONG_MODEM); + + } else { + return(RADIOLIB_ERR_UNKNOWN); + + } + + RADIOLIB_DEBUG_BASIC_PRINTLN("Timeout in %lu ms", timeout); + + // start reception + uint32_t timeoutValue = (uint32_t)(((float)timeout * 1000.0) / 30.52); + state = startReceive(timeoutValue); + RADIOLIB_ASSERT(state); + + // wait for packet reception or timeout + bool softTimeout = false; + RadioLibTime_t start = this->mod->hal->millis(); + while(!this->mod->hal->digitalRead(this->mod->getIrq())) { + this->mod->hal->yield(); + // safety check, the timeout should be done by the radio + if(this->mod->hal->millis() - start > timeout) { + softTimeout = true; + break; + } + } + + // if it was a timeout, this will return an error code + // TODO taken from SX126x, does this really work? + state = standby(); + if((state != RADIOLIB_ERR_NONE) && (state != RADIOLIB_ERR_SPI_CMD_TIMEOUT)) { + return(state); + } + + // check whether this was a timeout or not + if((getIrqStatus() & RADIOLIB_LR11X0_IRQ_TIMEOUT) || softTimeout) { + standby(); + clearIrq(RADIOLIB_LR11X0_IRQ_ALL); + return(RADIOLIB_ERR_RX_TIMEOUT); + } + + // read the received data + return(readData(data, len)); +} + +int16_t LR11x0::transmitDirect(uint32_t frf) { + // set RF switch (if present) + this->mod->setRfSwitchState(Module::MODE_TX); + + // user requested to start transmitting immediately (required for RTTY) + int16_t state = RADIOLIB_ERR_NONE; + if(frf != 0) { + state = setRfFrequency(frf); + } + RADIOLIB_ASSERT(state); + + // start transmitting + return(setTxCw()); +} + +int16_t LR11x0::receiveDirect() { + // set RF switch (if present) + this->mod->setRfSwitchState(Module::MODE_RX); + + // LR11x0 is unable to output received data directly + return(RADIOLIB_ERR_UNKNOWN); +} + +int16_t LR11x0::scanChannel() { + ChannelScanConfig_t cfg = { + .cad = { + .symNum = RADIOLIB_LR11X0_CAD_PARAM_DEFAULT, + .detPeak = RADIOLIB_LR11X0_CAD_PARAM_DEFAULT, + .detMin = RADIOLIB_LR11X0_CAD_PARAM_DEFAULT, + .exitMode = RADIOLIB_LR11X0_CAD_PARAM_DEFAULT, + .timeout = 0, + .irqFlags = RADIOLIB_IRQ_CAD_DEFAULT_FLAGS, + .irqMask = RADIOLIB_IRQ_CAD_DEFAULT_MASK, + }, + }; + return(this->scanChannel(cfg)); +} + +int16_t LR11x0::scanChannel(const ChannelScanConfig_t &config) { + // set mode to CAD + int state = startChannelScan(config); + RADIOLIB_ASSERT(state); + + // wait for channel activity detected or timeout + while(!this->mod->hal->digitalRead(this->mod->getIrq())) { + this->mod->hal->yield(); + } + + // check CAD result + return(getChannelScanResult()); +} + +int16_t LR11x0::standby() { + return(LR11x0::standby(RADIOLIB_LR11X0_STANDBY_RC)); +} + +int16_t LR11x0::standby(uint8_t mode, bool wakeup) { + // set RF switch (if present) + this->mod->setRfSwitchState(Module::MODE_IDLE); + + if(wakeup) { + // pull NSS low for a while to wake up + this->mod->hal->digitalWrite(this->mod->getCs(), this->mod->hal->GpioLevelLow); + this->mod->hal->delay(1); + this->mod->hal->digitalWrite(this->mod->getCs(), this->mod->hal->GpioLevelHigh); + } + + uint8_t buff[] = { mode }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_SET_STANDBY, true, buff, 1)); +} + +int16_t LR11x0::sleep() { + return(LR11x0::sleep(true, 0)); +} + +int16_t LR11x0::sleep(bool retainConfig, uint32_t sleepTime) { + // set RF switch (if present) + this->mod->setRfSwitchState(Module::MODE_IDLE); + + uint8_t buff[] = { + (uint8_t)retainConfig, + (uint8_t)((sleepTime >> 24) & 0xFF), (uint8_t)((sleepTime >> 16) & 0xFF), + (uint8_t)((sleepTime >> 16) & 0xFF), (uint8_t)(sleepTime & 0xFF), + }; + if(sleepTime) { + buff[0] |= RADIOLIB_LR11X0_SLEEP_WAKEUP_ENABLED; + } + + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_SET_SLEEP, true, buff, sizeof(buff)); + + // wait for the module to safely enter sleep mode + this->mod->hal->delay(1); + + return(state); +} + +void LR11x0::setIrqAction(void (*func)(void)) { + this->mod->hal->attachInterrupt(this->mod->hal->pinToInterrupt(this->mod->getIrq()), func, this->mod->hal->GpioInterruptRising); +} + +void LR11x0::clearIrqAction() { + this->mod->hal->detachInterrupt(this->mod->hal->pinToInterrupt(this->mod->getIrq())); +} + +void LR11x0::setPacketReceivedAction(void (*func)(void)) { + this->setIrqAction(func); +} + +void LR11x0::clearPacketReceivedAction() { + this->clearIrqAction(); +} + +void LR11x0::setPacketSentAction(void (*func)(void)) { + this->setIrqAction(func); +} + +void LR11x0::clearPacketSentAction() { + this->clearIrqAction(); +} + +int16_t LR11x0::startTransmit(const uint8_t* data, size_t len, uint8_t addr) { + // suppress unused variable warning + (void)addr; + + // check packet length + if(len > RADIOLIB_LR11X0_MAX_PACKET_LENGTH) { + return(RADIOLIB_ERR_PACKET_TOO_LONG); + } + + // maximum packet length is decreased by 1 when address filtering is active + if((this->addrComp != RADIOLIB_LR11X0_GFSK_ADDR_FILTER_DISABLED) && (len > RADIOLIB_LR11X0_MAX_PACKET_LENGTH - 1)) { + return(RADIOLIB_ERR_PACKET_TOO_LONG); + } + + // set packet Length + int16_t state = RADIOLIB_ERR_NONE; + uint8_t modem = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + state = getPacketType(&modem); + RADIOLIB_ASSERT(state); + if(modem == RADIOLIB_LR11X0_PACKET_TYPE_LORA) { + state = setPacketParamsLoRa(this->preambleLengthLoRa, this->headerType, len, this->crcTypeLoRa, this->invertIQEnabled); + + } else if(modem == RADIOLIB_LR11X0_PACKET_TYPE_GFSK) { + state = setPacketParamsGFSK(this->preambleLengthGFSK, this->preambleDetLength, this->syncWordLength, this->addrComp, this->packetType, len, this->crcTypeGFSK, this->whitening); + + } else if(modem != RADIOLIB_LR11X0_PACKET_TYPE_LR_FHSS) { + return(RADIOLIB_ERR_UNKNOWN); + + } + RADIOLIB_ASSERT(state); + + // set DIO mapping + state = setDioIrqParams(RADIOLIB_LR11X0_IRQ_TX_DONE | RADIOLIB_LR11X0_IRQ_TIMEOUT); + RADIOLIB_ASSERT(state); + + if(modem == RADIOLIB_LR11X0_PACKET_TYPE_LR_FHSS) { + // in LR-FHSS mode, the packet is built by the device + // TODO add configurable device offset + state = lrFhssBuildFrame(this->lrFhssHdrCount, this->lrFhssCr, this->lrFhssGrid, true, this->lrFhssBw, this->lrFhssHopSeq, 0, const_cast(data), len); + RADIOLIB_ASSERT(state); + + } else { + // write packet to buffer + state = writeBuffer8(const_cast(data), len); + RADIOLIB_ASSERT(state); + + } + + // clear interrupt flags + state = clearIrq(RADIOLIB_LR11X0_IRQ_ALL); + RADIOLIB_ASSERT(state); + + // set RF switch (if present) + this->mod->setRfSwitchState(Module::MODE_TX); + + // start transmission + state = setTx(RADIOLIB_LR11X0_TX_TIMEOUT_NONE); + RADIOLIB_ASSERT(state); + + // wait for BUSY to go low (= PA ramp up done) + while(this->mod->hal->digitalRead(this->mod->getGpio())) { + this->mod->hal->yield(); + } + + return(state); +} + +int16_t LR11x0::finishTransmit() { + // clear interrupt flags + clearIrq(RADIOLIB_LR11X0_IRQ_ALL); + + // set mode to standby to disable transmitter/RF switch + return(standby()); +} + +int16_t LR11x0::startReceive() { + return(this->startReceive(RADIOLIB_LR11X0_RX_TIMEOUT_INF, RADIOLIB_IRQ_RX_DEFAULT_FLAGS, RADIOLIB_IRQ_RX_DEFAULT_MASK, 0)); +} + +int16_t LR11x0::startReceive(uint32_t timeout, uint32_t irqFlags, uint32_t irqMask, size_t len) { + (void)len; + + // check active modem + int16_t state = RADIOLIB_ERR_NONE; + uint8_t modem = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + state = getPacketType(&modem); + RADIOLIB_ASSERT(state); + if((modem != RADIOLIB_LR11X0_PACKET_TYPE_LORA) && + (modem != RADIOLIB_LR11X0_PACKET_TYPE_GFSK)) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // set DIO mapping + uint32_t irq = irqMask; + if(timeout != RADIOLIB_LR11X0_RX_TIMEOUT_INF) { + irq |= (1UL << RADIOLIB_IRQ_TIMEOUT); + } + state = setDioIrqParams(getIrqMapped(irqFlags & irq)); + RADIOLIB_ASSERT(state); + + // clear interrupt flags + state = clearIrq(RADIOLIB_LR11X0_IRQ_ALL); + RADIOLIB_ASSERT(state); + + // set implicit mode and expected len if applicable + if((this->headerType == RADIOLIB_LR11X0_LORA_HEADER_IMPLICIT) && (modem == RADIOLIB_LR11X0_PACKET_TYPE_LORA)) { + state = setPacketParamsLoRa(this->preambleLengthLoRa, this->headerType, this->implicitLen, this->crcTypeLoRa, this->invertIQEnabled); + RADIOLIB_ASSERT(state); + } + + // set RF switch (if present) + this->mod->setRfSwitchState(Module::MODE_RX); + + // set mode to receive + state = setRx(timeout); + + return(state); +} + +uint32_t LR11x0::getIrqStatus() { + // there is no dedicated "get IRQ" command, the IRQ bits are sent after the status bytes + uint8_t buff[6] = { 0 }; + this->mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_STATUS] = Module::BITS_0; + mod->SPItransferStream(NULL, 0, false, NULL, buff, sizeof(buff), true); + this->mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_STATUS] = Module::BITS_8; + uint32_t irq = ((uint32_t)(buff[2]) << 24) | ((uint32_t)(buff[3]) << 16) | ((uint32_t)(buff[4]) << 8) | (uint32_t)buff[5]; + return(irq); +} + +int16_t LR11x0::readData(uint8_t* data, size_t len) { + // check active modem + int16_t state = RADIOLIB_ERR_NONE; + uint8_t modem = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + state = getPacketType(&modem); + RADIOLIB_ASSERT(state); + if((modem != RADIOLIB_LR11X0_PACKET_TYPE_LORA) && + (modem != RADIOLIB_LR11X0_PACKET_TYPE_GFSK)) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // check integrity CRC + uint32_t irq = getIrqStatus(); + int16_t crcState = RADIOLIB_ERR_NONE; + // Report CRC mismatch when there's a payload CRC error, or a header error and no valid header (to avoid false alarm from previous packet) + if((irq & RADIOLIB_LR11X0_IRQ_CRC_ERR) || ((irq & RADIOLIB_LR11X0_IRQ_HEADER_ERR) && !(irq & RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID))) { + crcState = RADIOLIB_ERR_CRC_MISMATCH; + } + + // get packet length + // the offset is needed since LR11x0 seems to move the buffer base by 4 bytes on every packet + uint8_t offset = 0; + size_t length = getPacketLength(true, &offset); + if((len != 0) && (len < length)) { + // user requested less data than we got, only return what was requested + length = len; + } + + // read packet data + state = readBuffer8(data, length, offset); + RADIOLIB_ASSERT(state); + + // clear the Rx buffer + state = clearRxBuffer(); + RADIOLIB_ASSERT(state); + + // clear interrupt flags + state = clearIrq(RADIOLIB_LR11X0_IRQ_ALL); + + // check if CRC failed - this is done after reading data to give user the option to keep them + RADIOLIB_ASSERT(crcState); + + return(state); +} + +int16_t LR11x0::startChannelScan() { + ChannelScanConfig_t cfg = { + .cad = { + .symNum = RADIOLIB_LR11X0_CAD_PARAM_DEFAULT, + .detPeak = RADIOLIB_LR11X0_CAD_PARAM_DEFAULT, + .detMin = RADIOLIB_LR11X0_CAD_PARAM_DEFAULT, + .exitMode = RADIOLIB_LR11X0_CAD_PARAM_DEFAULT, + .timeout = 0, + .irqFlags = RADIOLIB_IRQ_CAD_DEFAULT_FLAGS, + .irqMask = RADIOLIB_IRQ_CAD_DEFAULT_MASK, + }, + }; + return(this->startChannelScan(cfg)); +} + +int16_t LR11x0::startChannelScan(const ChannelScanConfig_t &config) { + // check active modem + int16_t state = RADIOLIB_ERR_NONE; + uint8_t modem = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + state = getPacketType(&modem); + RADIOLIB_ASSERT(state); + if(modem != RADIOLIB_LR11X0_PACKET_TYPE_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // set mode to standby + state = standby(); + RADIOLIB_ASSERT(state); + + // set RF switch (if present) + this->mod->setRfSwitchState(Module::MODE_RX); + + // set DIO pin mapping + uint16_t irqFlags = (config.cad.irqFlags == RADIOLIB_IRQ_NOT_SUPPORTED) ? RADIOLIB_LR11X0_IRQ_CAD_DETECTED | RADIOLIB_LR11X0_IRQ_CAD_DONE : config.cad.irqFlags; + state = setDioIrqParams(getIrqMapped(irqFlags), getIrqMapped(irqFlags)); + RADIOLIB_ASSERT(state); + + // clear interrupt flags + state = clearIrq(RADIOLIB_LR11X0_IRQ_ALL); + RADIOLIB_ASSERT(state); + + // set mode to CAD + return(startCad(config.cad.symNum, config.cad.detPeak, config.cad.detMin, config.cad.exitMode, config.cad.timeout)); +} + +int16_t LR11x0::getChannelScanResult() { + // check active modem + int16_t state = RADIOLIB_ERR_NONE; + uint8_t modem = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + state = getPacketType(&modem); + RADIOLIB_ASSERT(state); + if(modem != RADIOLIB_LR11X0_PACKET_TYPE_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // check CAD result + uint32_t cadResult = getIrqStatus(); + if(cadResult & RADIOLIB_LR11X0_IRQ_CAD_DETECTED) { + // detected some LoRa activity + return(RADIOLIB_LORA_DETECTED); + } else if(cadResult & RADIOLIB_LR11X0_IRQ_CAD_DONE) { + // channel is free + return(RADIOLIB_CHANNEL_FREE); + } + + return(RADIOLIB_ERR_UNKNOWN); +} + +int16_t LR11x0::setBandwidth(float bw, bool high) { + // check active modem + uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + int16_t state = getPacketType(&type); + RADIOLIB_ASSERT(state); + if(type != RADIOLIB_LR11X0_PACKET_TYPE_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // ensure byte conversion doesn't overflow + if (high) { + RADIOLIB_CHECK_RANGE(bw, 203.125, 815.0, RADIOLIB_ERR_INVALID_BANDWIDTH); + + if(fabsf(bw - 203.125) <= 0.001) { + this->bandwidth = RADIOLIB_LR11X0_LORA_BW_203_125; + } else if(fabsf(bw - 406.25) <= 0.001) { + this->bandwidth = RADIOLIB_LR11X0_LORA_BW_406_25; + } else if(fabsf(bw - 812.5) <= 0.001) { + this->bandwidth = RADIOLIB_LR11X0_LORA_BW_812_50; + } else { + return(RADIOLIB_ERR_INVALID_BANDWIDTH); + } + } else { + RADIOLIB_CHECK_RANGE(bw, 0.0, 510.0, RADIOLIB_ERR_INVALID_BANDWIDTH); + + // check allowed bandwidth values + uint8_t bw_div2 = bw / 2 + 0.01; + switch (bw_div2) { + case 31: // 62.5: + this->bandwidth = RADIOLIB_LR11X0_LORA_BW_62_5; + break; + case 62: // 125.0: + this->bandwidth = RADIOLIB_LR11X0_LORA_BW_125_0; + break; + case 125: // 250.0 + this->bandwidth = RADIOLIB_LR11X0_LORA_BW_250_0; + break; + case 250: // 500.0 + this->bandwidth = RADIOLIB_LR11X0_LORA_BW_500_0; + break; + default: + return(RADIOLIB_ERR_INVALID_BANDWIDTH); + } + } + + // update modulation parameters + this->bandwidthKhz = bw; + return(setModulationParamsLoRa(this->spreadingFactor, this->bandwidth, this->codingRate, this->ldrOptimize)); +} + +int16_t LR11x0::setSpreadingFactor(uint8_t sf, bool legacy) { + // check active modem + uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + int16_t state = getPacketType(&type); + RADIOLIB_ASSERT(state); + if(type != RADIOLIB_LR11X0_PACKET_TYPE_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + RADIOLIB_CHECK_RANGE(sf, 5, 12, RADIOLIB_ERR_INVALID_SPREADING_FACTOR); + + // TODO enable SF6 legacy mode + if(legacy && (sf == 6)) { + //this->mod->SPIsetRegValue(RADIOLIB_LR11X0_REG_SF6_SX127X_COMPAT, RADIOLIB_LR11X0_SF6_SX127X, 18, 18); + } + + // update modulation parameters + this->spreadingFactor = sf; + return(setModulationParamsLoRa(this->spreadingFactor, this->bandwidth, this->codingRate, this->ldrOptimize)); +} + +int16_t LR11x0::setCodingRate(uint8_t cr, bool longInterleave) { + // check active modem + uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + int16_t state = getPacketType(&type); + RADIOLIB_ASSERT(state); + if(type != RADIOLIB_LR11X0_PACKET_TYPE_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + RADIOLIB_CHECK_RANGE(cr, 5, 8, RADIOLIB_ERR_INVALID_CODING_RATE); + + if(longInterleave) { + switch(cr) { + case 5: + case 6: + this->codingRate = cr; + break; + case 8: + this->codingRate = cr - 1; + break; + default: + return(RADIOLIB_ERR_INVALID_CODING_RATE); + } + + } else { + this->codingRate = cr - 4; + + } + + // update modulation parameters + return(setModulationParamsLoRa(this->spreadingFactor, this->bandwidth, this->codingRate, this->ldrOptimize)); +} + +int16_t LR11x0::setSyncWord(uint8_t syncWord) { + // check active modem + uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + int16_t state = getPacketType(&type); + RADIOLIB_ASSERT(state); + if(type != RADIOLIB_LR11X0_PACKET_TYPE_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + return(setLoRaSyncWord(syncWord)); +} + +int16_t LR11x0::setBitRate(float br) { + RADIOLIB_CHECK_RANGE(br, 0.6, 300.0, RADIOLIB_ERR_INVALID_BIT_RATE); + + // check active modem + uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + int16_t state = getPacketType(&type); + RADIOLIB_ASSERT(state); + if(type != RADIOLIB_LR11X0_PACKET_TYPE_GFSK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // set bit rate value + // TODO implement fractional bit rate configuration + this->bitRate = br * 1000.0; + return(setModulationParamsGFSK(this->bitRate, this->pulseShape, this->rxBandwidth, this->frequencyDev)); +} + +int16_t LR11x0::setFrequencyDeviation(float freqDev) { + // check active modem + uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + int16_t state = getPacketType(&type); + RADIOLIB_ASSERT(state); + if(type != RADIOLIB_LR11X0_PACKET_TYPE_GFSK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // set frequency deviation to lowest available setting (required for digimodes) + float newFreqDev = freqDev; + if(freqDev < 0.0) { + newFreqDev = 0.6; + } + + RADIOLIB_CHECK_RANGE(newFreqDev, 0.6, 200.0, RADIOLIB_ERR_INVALID_FREQUENCY_DEVIATION); + this->frequencyDev = newFreqDev * 1000.0; + return(setModulationParamsGFSK(this->bitRate, this->pulseShape, this->rxBandwidth, this->frequencyDev)); +} + +int16_t LR11x0::setRxBandwidth(float rxBw) { + // check active modem + uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + int16_t state = getPacketType(&type); + RADIOLIB_ASSERT(state); + if(type != RADIOLIB_LR11X0_PACKET_TYPE_GFSK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // check modulation parameters + /*if(2 * this->frequencyDev + this->bitRate > rxBw * 1000.0) { + return(RADIOLIB_ERR_INVALID_MODULATION_PARAMETERS); + }*/ + + // check allowed receiver bandwidth values + if(fabsf(rxBw - 4.8) <= 0.001) { + this->rxBandwidth = RADIOLIB_LR11X0_GFSK_RX_BW_4_8; + } else if(fabsf(rxBw - 5.8) <= 0.001) { + this->rxBandwidth = RADIOLIB_LR11X0_GFSK_RX_BW_5_8; + } else if(fabsf(rxBw - 7.3) <= 0.001) { + this->rxBandwidth = RADIOLIB_LR11X0_GFSK_RX_BW_7_3; + } else if(fabsf(rxBw - 9.7) <= 0.001) { + this->rxBandwidth = RADIOLIB_LR11X0_GFSK_RX_BW_9_7; + } else if(fabsf(rxBw - 11.7) <= 0.001) { + this->rxBandwidth = RADIOLIB_LR11X0_GFSK_RX_BW_11_7; + } else if(fabsf(rxBw - 14.6) <= 0.001) { + this->rxBandwidth = RADIOLIB_LR11X0_GFSK_RX_BW_14_6; + } else if(fabsf(rxBw - 19.5) <= 0.001) { + this->rxBandwidth = RADIOLIB_LR11X0_GFSK_RX_BW_19_5; + } else if(fabsf(rxBw - 23.4) <= 0.001) { + this->rxBandwidth = RADIOLIB_LR11X0_GFSK_RX_BW_23_4; + } else if(fabsf(rxBw - 29.3) <= 0.001) { + this->rxBandwidth = RADIOLIB_LR11X0_GFSK_RX_BW_29_3; + } else if(fabsf(rxBw - 39.0) <= 0.001) { + this->rxBandwidth = RADIOLIB_LR11X0_GFSK_RX_BW_39_0; + } else if(fabsf(rxBw - 46.9) <= 0.001) { + this->rxBandwidth = RADIOLIB_LR11X0_GFSK_RX_BW_46_9; + } else if(fabsf(rxBw - 58.6) <= 0.001) { + this->rxBandwidth = RADIOLIB_LR11X0_GFSK_RX_BW_58_6; + } else if(fabsf(rxBw - 78.2) <= 0.001) { + this->rxBandwidth = RADIOLIB_LR11X0_GFSK_RX_BW_78_2; + } else if(fabsf(rxBw - 93.8) <= 0.001) { + this->rxBandwidth = RADIOLIB_LR11X0_GFSK_RX_BW_93_8; + } else if(fabsf(rxBw - 117.3) <= 0.001) { + this->rxBandwidth = RADIOLIB_LR11X0_GFSK_RX_BW_117_3; + } else if(fabsf(rxBw - 156.2) <= 0.001) { + this->rxBandwidth = RADIOLIB_LR11X0_GFSK_RX_BW_156_2; + } else if(fabsf(rxBw - 187.2) <= 0.001) { + this->rxBandwidth = RADIOLIB_LR11X0_GFSK_RX_BW_187_2; + } else if(fabsf(rxBw - 234.3) <= 0.001) { + this->rxBandwidth = RADIOLIB_LR11X0_GFSK_RX_BW_234_3; + } else if(fabsf(rxBw - 312.0) <= 0.001) { + this->rxBandwidth = RADIOLIB_LR11X0_GFSK_RX_BW_312_0; + } else if(fabsf(rxBw - 373.6) <= 0.001) { + this->rxBandwidth = RADIOLIB_LR11X0_GFSK_RX_BW_373_6; + } else if(fabsf(rxBw - 467.0) <= 0.001) { + this->rxBandwidth = RADIOLIB_LR11X0_GFSK_RX_BW_467_0; + } else { + return(RADIOLIB_ERR_INVALID_RX_BANDWIDTH); + } + + // update modulation parameters + return(setModulationParamsGFSK(this->bitRate, this->pulseShape, this->rxBandwidth, this->frequencyDev)); +} + +int16_t LR11x0::setSyncWord(uint8_t* syncWord, size_t len) { + if((!syncWord) || (!len) || (len > RADIOLIB_LR11X0_GFSK_SYNC_WORD_LEN)) { + return(RADIOLIB_ERR_INVALID_SYNC_WORD); + } + + // check active modem + uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + int16_t state = getPacketType(&type); + RADIOLIB_ASSERT(state); + if(type == RADIOLIB_LR11X0_PACKET_TYPE_GFSK) { + // update sync word length + this->syncWordLength = len*8; + state = setPacketParamsGFSK(this->preambleLengthGFSK, this->preambleDetLength, this->syncWordLength, this->addrComp, this->packetType, RADIOLIB_LR11X0_MAX_PACKET_LENGTH, this->crcTypeGFSK, this->whitening); + RADIOLIB_ASSERT(state); + + // sync word is passed most-significant byte first + uint8_t fullSyncWord[RADIOLIB_LR11X0_GFSK_SYNC_WORD_LEN] = { 0 }; + memcpy(fullSyncWord, syncWord, len); + return(setGfskSyncWord(fullSyncWord)); + + } else if(type == RADIOLIB_LR11X0_PACKET_TYPE_LORA) { + // with length set to 1 and LoRa modem active, assume it is the LoRa sync word + if(len > 1) { + return(RADIOLIB_ERR_INVALID_SYNC_WORD); + } + return(setSyncWord(syncWord[0])); + + } else if(type == RADIOLIB_LR11X0_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); + } + uint32_t sync = 0; + memcpy(&sync, syncWord, sizeof(uint32_t)); + return(lrFhssSetSyncWord(sync)); + + } + + return(RADIOLIB_ERR_WRONG_MODEM); +} + +int16_t LR11x0::setSyncBits(uint8_t *syncWord, uint8_t bitsLen) { + if((!syncWord) || (!bitsLen) || (bitsLen > 8*RADIOLIB_LR11X0_GFSK_SYNC_WORD_LEN)) { + return(RADIOLIB_ERR_INVALID_SYNC_WORD); + } + + // check active modem + uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + int16_t state = getPacketType(&type); + RADIOLIB_ASSERT(state); + if(type != RADIOLIB_LR11X0_PACKET_TYPE_GFSK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + uint8_t bytesLen = bitsLen / 8; + if ((bitsLen % 8) != 0) { + bytesLen++; + } + + return(setSyncWord(syncWord, bytesLen)); +} + +int16_t LR11x0::setNodeAddress(uint8_t nodeAddr) { + // check active modem + uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + int16_t state = getPacketType(&type); + RADIOLIB_ASSERT(state); + if(type != RADIOLIB_LR11X0_PACKET_TYPE_GFSK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // enable address filtering (node only) + this->addrComp = RADIOLIB_LR11X0_GFSK_ADDR_FILTER_NODE; + state = setPacketParamsGFSK(this->preambleLengthGFSK, this->preambleDetLength, this->syncWordLength, this->addrComp, this->packetType, RADIOLIB_LR11X0_MAX_PACKET_LENGTH, this->crcTypeGFSK, this->whitening); + RADIOLIB_ASSERT(state); + + // set node address + this->node = nodeAddr; + return(setPacketAdrs(this->node, 0)); +} + +int16_t LR11x0::setBroadcastAddress(uint8_t broadAddr) { + // check active modem + uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + int16_t state = getPacketType(&type); + RADIOLIB_ASSERT(state); + if(type != RADIOLIB_LR11X0_PACKET_TYPE_GFSK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // enable address filtering (node and broadcast) + this->addrComp = RADIOLIB_LR11X0_GFSK_ADDR_FILTER_NODE_BROADCAST; + state = setPacketParamsGFSK(this->preambleLengthGFSK, this->preambleDetLength, this->syncWordLength, this->addrComp, this->packetType, RADIOLIB_LR11X0_MAX_PACKET_LENGTH, this->crcTypeGFSK, this->whitening); + RADIOLIB_ASSERT(state); + + // set node and broadcast address + return(setPacketAdrs(this->node, broadAddr)); +} + +int16_t LR11x0::disableAddressFiltering() { + // check active modem + uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + int16_t state = getPacketType(&type); + RADIOLIB_ASSERT(state); + if(type != RADIOLIB_LR11X0_PACKET_TYPE_GFSK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // disable address filterin + this->addrComp = RADIOLIB_LR11X0_GFSK_ADDR_FILTER_DISABLED; + return(setPacketParamsGFSK(this->preambleLengthGFSK, this->preambleDetLength, this->syncWordLength, this->addrComp, this->packetType, RADIOLIB_LR11X0_MAX_PACKET_LENGTH, this->crcTypeGFSK, this->whitening)); +} + +int16_t LR11x0::setDataShaping(uint8_t sh) { + // check active modem + uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + int16_t state = getPacketType(&type); + RADIOLIB_ASSERT(state); + if(type != RADIOLIB_LR11X0_PACKET_TYPE_GFSK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // set data shaping + switch(sh) { + case RADIOLIB_SHAPING_NONE: + this->pulseShape = RADIOLIB_LR11X0_GFSK_SHAPING_NONE; + break; + case RADIOLIB_SHAPING_0_3: + this->pulseShape = RADIOLIB_LR11X0_GFSK_SHAPING_GAUSSIAN_BT_0_3; + break; + case RADIOLIB_SHAPING_0_5: + this->pulseShape = RADIOLIB_LR11X0_GFSK_SHAPING_GAUSSIAN_BT_0_5; + break; + case RADIOLIB_SHAPING_0_7: + this->pulseShape = RADIOLIB_LR11X0_GFSK_SHAPING_GAUSSIAN_BT_0_7; + break; + case RADIOLIB_SHAPING_1_0: + this->pulseShape = RADIOLIB_LR11X0_GFSK_SHAPING_GAUSSIAN_BT_1_0; + break; + default: + return(RADIOLIB_ERR_INVALID_DATA_SHAPING); + } + + // update modulation parameters + return(setModulationParamsGFSK(this->bitRate, this->pulseShape, this->rxBandwidth, this->frequencyDev)); +} + +int16_t LR11x0::setEncoding(uint8_t encoding) { + return(setWhitening(encoding)); +} + +int16_t LR11x0::fixedPacketLengthMode(uint8_t len) { + return(setPacketMode(RADIOLIB_LR11X0_GFSK_PACKET_LENGTH_FIXED, len)); +} + +int16_t LR11x0::variablePacketLengthMode(uint8_t maxLen) { + return(setPacketMode(RADIOLIB_LR11X0_GFSK_PACKET_LENGTH_VARIABLE, maxLen)); +} + +int16_t LR11x0::setWhitening(bool enabled, uint16_t initial) { + // check active modem + uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + int16_t state = getPacketType(&type); + RADIOLIB_ASSERT(state); + if(type != RADIOLIB_LR11X0_PACKET_TYPE_GFSK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + if(!enabled) { + // disable whitening + this->whitening = RADIOLIB_LR11X0_GFSK_WHITENING_DISABLED; + + } else { + // enable whitening + this->whitening = RADIOLIB_LR11X0_GFSK_WHITENING_ENABLED; + + // write initial whitening value + state = setGfskWhitParams(initial); + RADIOLIB_ASSERT(state); + } + + return(setPacketParamsGFSK(this->preambleLengthGFSK, this->preambleDetLength, this->syncWordLength, this->addrComp, this->packetType, RADIOLIB_LR11X0_MAX_PACKET_LENGTH, this->crcTypeGFSK, this->whitening)); +} + +int16_t LR11x0::setDataRate(DataRate_t dr) { + // select interpretation based on active modem + uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + int16_t state = getPacketType(&type); + RADIOLIB_ASSERT(state); + + if(type == RADIOLIB_LR11X0_PACKET_TYPE_GFSK) { + // set the bit rate + state = this->setBitRate(dr.fsk.bitRate); + RADIOLIB_ASSERT(state); + + // set the frequency deviation + state = this->setFrequencyDeviation(dr.fsk.freqDev); + + } else if(type == RADIOLIB_LR11X0_PACKET_TYPE_LORA) { + // set the spreading factor + state = this->setSpreadingFactor(dr.lora.spreadingFactor); + RADIOLIB_ASSERT(state); + + // set the bandwidth + state = this->setBandwidth(dr.lora.bandwidth); + RADIOLIB_ASSERT(state); + + // set the coding rate + state = this->setCodingRate(dr.lora.codingRate); + + } else if(type == RADIOLIB_LR11X0_PACKET_TYPE_LR_FHSS) { + // set the basic config + state = this->setLrFhssConfig(dr.lrFhss.bw, dr.lrFhss.cr); + RADIOLIB_ASSERT(state); + + // set hopping grid + this->lrFhssGrid = dr.lrFhss.narrowGrid ? RADIOLIB_LR11X0_LR_FHSS_GRID_STEP_NON_FCC : RADIOLIB_LR11X0_LR_FHSS_GRID_STEP_FCC; + + } + + return(state); +} + +int16_t LR11x0::checkDataRate(DataRate_t dr) { + // select interpretation based on active modem + uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + int16_t state = getPacketType(&type); + RADIOLIB_ASSERT(state); + + if(type == RADIOLIB_LR11X0_PACKET_TYPE_GFSK) { + RADIOLIB_CHECK_RANGE(dr.fsk.bitRate, 0.6, 300.0, RADIOLIB_ERR_INVALID_BIT_RATE); + RADIOLIB_CHECK_RANGE(dr.fsk.freqDev, 0.6, 200.0, RADIOLIB_ERR_INVALID_FREQUENCY_DEVIATION); + return(RADIOLIB_ERR_NONE); + + } else if(type == RADIOLIB_LR11X0_PACKET_TYPE_LORA) { + RADIOLIB_CHECK_RANGE(dr.lora.spreadingFactor, 5, 12, RADIOLIB_ERR_INVALID_SPREADING_FACTOR); + RADIOLIB_CHECK_RANGE(dr.lora.bandwidth, 0.0, 510.0, RADIOLIB_ERR_INVALID_BANDWIDTH); + RADIOLIB_CHECK_RANGE(dr.lora.codingRate, 5, 8, RADIOLIB_ERR_INVALID_CODING_RATE); + return(RADIOLIB_ERR_NONE); + + } + + return(RADIOLIB_ERR_UNKNOWN); +} + +int16_t LR11x0::setPreambleLength(size_t preambleLength) { + // check active modem + uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + int16_t state = getPacketType(&type); + RADIOLIB_ASSERT(state); + if(type == RADIOLIB_LR11X0_PACKET_TYPE_LORA) { + this->preambleLengthLoRa = preambleLength; + return(setPacketParamsLoRa(this->preambleLengthLoRa, this->headerType, this->implicitLen, this->crcTypeLoRa, (uint8_t)this->invertIQEnabled)); + } else if(type == RADIOLIB_LR11X0_PACKET_TYPE_GFSK) { + this->preambleLengthGFSK = preambleLength; + this->preambleDetLength = preambleLength >= 32 ? RADIOLIB_LR11X0_GFSK_PREAMBLE_DETECT_32_BITS : + preambleLength >= 24 ? RADIOLIB_LR11X0_GFSK_PREAMBLE_DETECT_24_BITS : + preambleLength >= 16 ? RADIOLIB_LR11X0_GFSK_PREAMBLE_DETECT_16_BITS : + preambleLength > 0 ? RADIOLIB_LR11X0_GFSK_PREAMBLE_DETECT_8_BITS : + RADIOLIB_LR11X0_GFSK_PREAMBLE_DETECT_DISABLED; + return(setPacketParamsGFSK(this->preambleLengthGFSK, this->preambleDetLength, this->syncWordLength, this->addrComp, this->packetType, RADIOLIB_LR11X0_MAX_PACKET_LENGTH, this->crcTypeGFSK, this->whitening)); + } + + return(RADIOLIB_ERR_WRONG_MODEM); +} + +int16_t LR11x0::setTCXO(float voltage, uint32_t delay) { + // check if TCXO is enabled at all + if(this->XTAL) { + return(RADIOLIB_ERR_INVALID_TCXO_VOLTAGE); + } + + // set mode to standby + standby(); + + // check RADIOLIB_LR11X0_ERROR_STAT_HF_XOSC_START_ERR flag and clear it + uint16_t errors = 0; + int16_t state = getErrors(&errors); + RADIOLIB_ASSERT(state); + if(errors & RADIOLIB_LR11X0_ERROR_STAT_HF_XOSC_START_ERR) { + clearErrors(); + } + + // check 0 V disable + if(fabsf(voltage - 0.0) <= 0.001) { + setTcxoMode(0, 0); + return(reset()); + } + + // check allowed voltage values + uint8_t tune = 0; + if(fabsf(voltage - 1.6) <= 0.001) { + tune = RADIOLIB_LR11X0_TCXO_VOLTAGE_1_6; + } else if(fabsf(voltage - 1.7) <= 0.001) { + tune = RADIOLIB_LR11X0_TCXO_VOLTAGE_1_7; + } else if(fabsf(voltage - 1.8) <= 0.001) { + tune = RADIOLIB_LR11X0_TCXO_VOLTAGE_1_8; + } else if(fabsf(voltage - 2.2) <= 0.001) { + tune = RADIOLIB_LR11X0_TCXO_VOLTAGE_2_2; + } else if(fabsf(voltage - 2.4) <= 0.001) { + tune = RADIOLIB_LR11X0_TCXO_VOLTAGE_2_4; + } else if(fabsf(voltage - 2.7) <= 0.001) { + tune = RADIOLIB_LR11X0_TCXO_VOLTAGE_2_7; + } else if(fabsf(voltage - 3.0) <= 0.001) { + tune = RADIOLIB_LR11X0_TCXO_VOLTAGE_3_0; + } else if(fabsf(voltage - 3.3) <= 0.001) { + tune = RADIOLIB_LR11X0_TCXO_VOLTAGE_3_3; + } else { + return(RADIOLIB_ERR_INVALID_TCXO_VOLTAGE); + } + + // calculate delay value + uint32_t delayValue = (uint32_t)((float)delay / 30.52f); + if(delayValue == 0) { + delayValue = 1; + } + + // enable TCXO control + return(setTcxoMode(tune, delayValue)); +} + +int16_t LR11x0::setCRC(uint8_t len, uint32_t initial, uint32_t polynomial, bool inverted) { + // check active modem + uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + int16_t state = getPacketType(&type); + RADIOLIB_ASSERT(state); + if(type == RADIOLIB_LR11X0_PACKET_TYPE_LORA) { + // LoRa CRC doesn't allow to set CRC polynomial, initial value, or inversion + this->crcTypeLoRa = len > 0 ? RADIOLIB_LR11X0_LORA_CRC_ENABLED : RADIOLIB_LR11X0_LORA_CRC_DISABLED; + state = setPacketParamsLoRa(this->preambleLengthLoRa, this->headerType, this->implicitLen, this->crcTypeLoRa, (uint8_t)this->invertIQEnabled); + + } else if(type == RADIOLIB_LR11X0_PACKET_TYPE_GFSK) { + // update packet parameters + switch(len) { + case 0: + this->crcTypeGFSK = RADIOLIB_LR11X0_GFSK_CRC_DISABLED; + break; + case 1: + if(inverted) { + this->crcTypeGFSK = RADIOLIB_LR11X0_GFSK_CRC_1_BYTE_INV; + } else { + this->crcTypeGFSK = RADIOLIB_LR11X0_GFSK_CRC_1_BYTE; + } + break; + case 2: + if(inverted) { + this->crcTypeGFSK = RADIOLIB_LR11X0_GFSK_CRC_2_BYTE_INV; + } else { + this->crcTypeGFSK = RADIOLIB_LR11X0_GFSK_CRC_2_BYTE; + } + break; + default: + return(RADIOLIB_ERR_INVALID_CRC_CONFIGURATION); + } + + state = setPacketParamsGFSK(this->preambleLengthGFSK, this->preambleDetLength, this->syncWordLength, this->addrComp, this->packetType, RADIOLIB_LR11X0_MAX_PACKET_LENGTH, this->crcTypeGFSK, this->whitening); + RADIOLIB_ASSERT(state); + + state = setGfskCrcParams(initial, polynomial); + + } + + return(state); +} + +int16_t LR11x0::invertIQ(bool enable) { + // check active modem + uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + int16_t state = getPacketType(&type); + RADIOLIB_ASSERT(state); + if(type != RADIOLIB_LR11X0_PACKET_TYPE_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + this->invertIQEnabled = enable; + return(setPacketParamsLoRa(this->preambleLengthLoRa, this->headerType, this->implicitLen, this->crcTypeLoRa, (uint8_t)this->invertIQEnabled)); +} + +float LR11x0::getRSSI() { + float val = 0; + + // check active modem + uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + (void)getPacketType(&type); + if(type == RADIOLIB_LR11X0_PACKET_TYPE_LORA) { + (void)getPacketStatusLoRa(&val, NULL, NULL); + + } else if(type == RADIOLIB_LR11X0_PACKET_TYPE_GFSK) { + (void)getPacketStatusGFSK(NULL, &val, NULL, NULL); + + } + + return(val); +} + +float LR11x0::getSNR() { + float val = 0; + + // check active modem + uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + (void)getPacketType(&type); + if(type == RADIOLIB_LR11X0_PACKET_TYPE_LORA) { + (void)getPacketStatusLoRa(NULL, &val, NULL); + } + + return(val); +} + +float LR11x0::getFrequencyError() { + // TODO implement this + return(0); +} + +size_t LR11x0::getPacketLength(bool update) { + return(this->getPacketLength(update, NULL)); +} + +size_t LR11x0::getPacketLength(bool update, uint8_t* offset) { + (void)update; + + // in implicit mode, return the cached value + uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + (void)getPacketType(&type); + if((type == RADIOLIB_LR11X0_PACKET_TYPE_LORA) && (this->headerType == RADIOLIB_LR11X0_LORA_HEADER_IMPLICIT)) { + return(this->implicitLen); + } + + uint8_t len = 0; + (void)getRxBufferStatus(&len, offset); + return((size_t)len); +} + +RadioLibTime_t LR11x0::getTimeOnAir(size_t len) { + // check active modem + uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + (void)getPacketType(&type); + if(type == RADIOLIB_LR11X0_PACKET_TYPE_LORA) { + // calculate number of symbols + float N_symbol = 0; + if(this->codingRate <= RADIOLIB_LR11X0_LORA_CR_4_8_SHORT) { + // legacy coding rate - nice and simple + + // get SF coefficients + float coeff1 = 0; + int16_t coeff2 = 0; + int16_t coeff3 = 0; + if(this->spreadingFactor < 7) { + // SF5, SF6 + coeff1 = 6.25; + coeff2 = 4*this->spreadingFactor; + coeff3 = 4*this->spreadingFactor; + } else if(this->spreadingFactor < 11) { + // SF7. SF8, SF9, SF10 + coeff1 = 4.25; + coeff2 = 4*this->spreadingFactor + 8; + coeff3 = 4*this->spreadingFactor; + } else { + // SF11, SF12 + coeff1 = 4.25; + coeff2 = 4*this->spreadingFactor + 8; + coeff3 = 4*(this->spreadingFactor - 2); + } + + // get CRC length + int16_t N_bitCRC = 16; + if(this->crcTypeLoRa == RADIOLIB_LR11X0_LORA_CRC_DISABLED) { + N_bitCRC = 0; + } + + // get header length + int16_t N_symbolHeader = 20; + if(this->headerType == RADIOLIB_LR11X0_LORA_HEADER_IMPLICIT) { + N_symbolHeader = 0; + } + + // calculate number of LoRa preamble symbols + uint32_t N_symbolPreamble = (this->preambleLengthLoRa & 0x0F) * (uint32_t(1) << ((this->preambleLengthLoRa & 0xF0) >> 4)); + + // calculate the number of symbols + N_symbol = (float)N_symbolPreamble + coeff1 + 8.0 + ceilf((float)RADIOLIB_MAX((int16_t)(8 * len + N_bitCRC - coeff2 + N_symbolHeader), (int16_t)0) / (float)coeff3) * (float)(this->codingRate + 4); + + } else { + // long interleaving - abandon hope all ye who enter here + /// \todo implement this mess - SX1280 datasheet v3.0 section 7.4.4.2 + + } + + // get time-on-air in us + return(((uint32_t(1) << this->spreadingFactor) / this->bandwidthKhz) * N_symbol * 1000.0); + + } else if(type == RADIOLIB_LR11X0_PACKET_TYPE_GFSK) { + return(((uint32_t)len * 8 * 1000000UL) / this->bitRate); + + } else if(type == RADIOLIB_LR11X0_PACKET_TYPE_LR_FHSS) { + // calculate the number of bits based on coding rate + uint16_t N_bits; + switch(this->lrFhssCr) { + case RADIOLIB_LR11X0_LR_FHSS_CR_5_6: + N_bits = ((len * 6) + 4) / 5; // this is from the official LR11xx driver, but why the extra +4? + break; + case RADIOLIB_LR11X0_LR_FHSS_CR_2_3: + N_bits = (len * 3) / 2; + break; + case RADIOLIB_LR11X0_LR_FHSS_CR_1_2: + N_bits = len * 2; + break; + case RADIOLIB_LR11X0_LR_FHSS_CR_1_3: + N_bits = len * 3; + break; + default: + return(RADIOLIB_ERR_INVALID_CODING_RATE); + } + + // calculate number of bits when accounting for unaligned last block + uint16_t N_payBits = (N_bits / RADIOLIB_LR11X0_LR_FHSS_FRAG_BITS) * RADIOLIB_LR11X0_LR_FHSS_BLOCK_BITS; + uint16_t N_lastBlockBits = N_bits % RADIOLIB_LR11X0_LR_FHSS_FRAG_BITS; + if(N_lastBlockBits) { + N_payBits += N_lastBlockBits + 2; + } + + // add header bits + uint16_t N_totalBits = (RADIOLIB_LR11X0_LR_FHSS_HEADER_BITS * this->lrFhssHdrCount) + N_payBits; + return(((uint32_t)N_totalBits * 8 * 1000000UL) / RADIOLIB_LR11X0_LR_FHSS_BIT_RATE); + + } + + return(0); +} + +RadioLibTime_t LR11x0::calculateRxTimeout(RadioLibTime_t timeoutUs) { + // the timeout value is given in units of 30.52 microseconds + // the calling function should provide some extra width, as this number of units is truncated to integer + RadioLibTime_t timeout = timeoutUs / 30.52; + return(timeout); +} + +uint32_t LR11x0::getIrqFlags() { + return((uint32_t)this->getIrqStatus()); +} + +int16_t LR11x0::setIrqFlags(uint32_t irq) { + return(this->setDioIrqParams(irq, irq)); +} + +int16_t LR11x0::clearIrqFlags(uint32_t irq) { + return(this->clearIrq(irq)); +} + +uint8_t LR11x0::randomByte() { + uint32_t num = 0; + (void)getRandomNumber(&num); + return((uint8_t)num); +} + +int16_t LR11x0::implicitHeader(size_t len) { + return(this->setHeaderType(RADIOLIB_LR11X0_LORA_HEADER_IMPLICIT, len)); +} + +int16_t LR11x0::explicitHeader() { + return(this->setHeaderType(RADIOLIB_LR11X0_LORA_HEADER_EXPLICIT)); +} + +float LR11x0::getDataRate() const { + return(this->dataRateMeasured); +} + +int16_t LR11x0::setRegulatorLDO() { + return(this->setRegMode(RADIOLIB_LR11X0_REG_MODE_LDO)); +} + +int16_t LR11x0::setRegulatorDCDC() { + return(this->setRegMode(RADIOLIB_LR11X0_REG_MODE_DC_DC)); +} + +int16_t LR11x0::setRxBoostedGainMode(bool en) { + uint8_t buff[1] = { (uint8_t)en }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_SET_RX_BOOSTED, true, buff, sizeof(buff))); +} + +void LR11x0::setRfSwitchTable(const uint32_t (&pins)[Module::RFSWITCH_MAX_PINS], const Module::RfSwitchMode_t table[]) { + // find which pins are used + uint8_t enable = 0; + for(size_t i = 0; i < Module::RFSWITCH_MAX_PINS; i++) { + // check if this pin is unused + if(pins[i] == RADIOLIB_NC) { + continue; + } + + // only keep DIO pins, there may be some GPIOs in the switch tabke + if(pins[i] & RFSWITCH_PIN_FLAG) { + enable |= 1UL << RADIOLIB_LR11X0_DIOx_VAL(pins[i]); + } + + } + + // now get the configuration + uint8_t modes[7] = { 0 }; + for(size_t i = 0; i < 7; i++) { + // check end of table + if(table[i].mode == LR11x0::MODE_END_OF_TABLE) { + break; + } + + // get the mode ID in case the modes are out-of-order + uint8_t index = table[i].mode - LR11x0::MODE_STBY; + + // iterate over the pins + for(size_t j = 0; j < Module::RFSWITCH_MAX_PINS; j++) { + // only process modes for the DIOx pins, skip GPIO pins + if(!(pins[j] & RFSWITCH_PIN_FLAG)) { + continue; + } + modes[index] |= (table[i].values[j] == this->mod->hal->GpioLevelHigh) ? (1UL << j) : 0; + } + } + + // set it + this->setDioAsRfSwitch(enable, modes[0], modes[1], modes[2], modes[3], modes[4], modes[5], modes[6]); +} + +int16_t LR11x0::forceLDRO(bool enable) { + // check packet type + uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + int16_t state = getPacketType(&type); + RADIOLIB_ASSERT(state); + if(type != RADIOLIB_LR11X0_PACKET_TYPE_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // update modulation parameters + this->ldroAuto = false; + this->ldrOptimize = (uint8_t)enable; + return(setModulationParamsLoRa(this->spreadingFactor, this->bandwidth, this->codingRate, this->ldrOptimize)); +} + +int16_t LR11x0::autoLDRO() { + uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + int16_t state = getPacketType(&type); + RADIOLIB_ASSERT(state); + if(type != RADIOLIB_LR11X0_PACKET_TYPE_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + this->ldroAuto = true; + return(RADIOLIB_ERR_NONE); +} + +int16_t LR11x0::setLrFhssConfig(uint8_t bw, uint8_t cr, uint8_t hdrCount, uint16_t hopSeed) { + // check active modem + uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + int16_t state = getPacketType(&type); + RADIOLIB_ASSERT(state); + if(type != RADIOLIB_LR11X0_PACKET_TYPE_LR_FHSS) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // check and cache all parameters + RADIOLIB_CHECK_RANGE((int8_t)cr, (int8_t)RADIOLIB_LR11X0_LR_FHSS_CR_5_6, (int8_t)RADIOLIB_LR11X0_LR_FHSS_CR_1_3, RADIOLIB_ERR_INVALID_CODING_RATE); + this->lrFhssCr = cr; + RADIOLIB_CHECK_RANGE((int8_t)bw, (int8_t)RADIOLIB_LR11X0_LR_FHSS_BW_39_06, (int8_t)RADIOLIB_LR11X0_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)hopSeed, (int16_t)0x000, (int16_t)0x1FF, RADIOLIB_ERR_INVALID_DATA_SHAPING); + this->lrFhssHopSeq = hopSeed; + return(RADIOLIB_ERR_NONE); +} + +int16_t LR11x0::startWifiScan(char wifiType, uint8_t mode, uint16_t chanMask, uint8_t numScans, uint16_t timeout) { + // LR1121 cannot do WiFi scanning + if(this->chipType == RADIOLIB_LR11X0_DEVICE_LR1121) { + return(RADIOLIB_ERR_UNSUPPORTED); + } + + uint8_t type; + switch(wifiType) { + case('b'): + type = RADIOLIB_LR11X0_WIFI_SCAN_802_11_B; + break; + case('g'): + type = RADIOLIB_LR11X0_WIFI_SCAN_802_11_G; + break; + case('n'): + type = RADIOLIB_LR11X0_WIFI_SCAN_802_11_N; + break; + case('*'): + type = RADIOLIB_LR11X0_WIFI_SCAN_ALL; + break; + default: + return(RADIOLIB_ERR_INVALID_WIFI_TYPE); + } + + // go to standby + int16_t state = standby(); + RADIOLIB_ASSERT(state); + + // reset cumulative timings + state = wifiResetCumulTimings(); + RADIOLIB_ASSERT(state); + + // set DIO mapping + state = setDioIrqParams(RADIOLIB_LR11X0_IRQ_WIFI_DONE); + RADIOLIB_ASSERT(state); + + // start scan with the maximum number of results and abort on timeout + this->wifiScanMode = mode; + state = wifiScan(type, chanMask, this->wifiScanMode, RADIOLIB_LR11X0_WIFI_MAX_NUM_RESULTS, numScans, timeout, RADIOLIB_LR11X0_WIFI_ABORT_ON_TIMEOUT_ENABLED); + return(state); +} + +void LR11x0::setWiFiScanAction(void (*func)(void)) { + this->setIrqAction(func); +} + +void LR11x0::clearWiFiScanAction() { + this->clearIrqAction(); +} + +int16_t LR11x0::getWifiScanResultsCount(uint8_t* count) { + // clear IRQ first, as this is likely to be called right after scan has finished + int16_t state = clearIrq(RADIOLIB_LR11X0_IRQ_ALL); + RADIOLIB_ASSERT(state); + + uint8_t buff[1] = { 0 }; + state = this->SPIcommand(RADIOLIB_LR11X0_CMD_WIFI_GET_NB_RESULTS, false, buff, sizeof(buff)); + + // pass the replies + if(count) { *count = buff[0]; } + + return(state); +} + +int16_t LR11x0::getWifiScanResult(LR11x0WifiResult_t* result, uint8_t index, bool brief) { + RADIOLIB_ASSERT_PTR(result); + + // read a single result + uint8_t format = brief ? RADIOLIB_LR11X0_WIFI_RESULT_TYPE_BASIC : RADIOLIB_LR11X0_WIFI_RESULT_TYPE_COMPLETE; + uint8_t raw[RADIOLIB_LR11X0_WIFI_RESULT_MAX_LEN] = { 0 }; + int16_t state = wifiReadResults(index, 1, format, raw); + RADIOLIB_ASSERT(state); + + // parse the information + switch(raw[0] & 0x03) { + case(RADIOLIB_LR11X0_WIFI_SCAN_802_11_B): + result->type = 'b'; + break; + case(RADIOLIB_LR11X0_WIFI_SCAN_802_11_G): + result->type = 'g'; + break; + case(RADIOLIB_LR11X0_WIFI_SCAN_802_11_N): + result->type = 'n'; + break; + } + result->dataRateId = (raw[0] & 0xFC) >> 2; + result->channelFreq = 2407 + (raw[1] & 0x0F)*5; + result->origin = (raw[1] & 0x30) >> 4; + result->ap = (raw[1] & 0x40) != 0; + result->rssi = (float)raw[2] / -2.0f;; + memcpy(result->mac, &raw[3], RADIOLIB_LR11X0_WIFI_RESULT_MAC_LEN); + + if(!brief) { + if(this->wifiScanMode == RADIOLIB_LR11X0_WIFI_ACQ_MODE_FULL_BEACON) { + LR11x0WifiResultExtended_t* resultExtended = reinterpret_cast(result); + resultExtended->rate = raw[3]; + resultExtended->service = (((uint16_t)raw[4] << 8) | ((uint16_t)raw[5])); + resultExtended->length = (((uint16_t)raw[6] << 8) | ((uint16_t)raw[7])); + resultExtended->frameType = raw[9] & 0x03; + resultExtended->frameSubType = (raw[9] & 0x3C) >> 2; + resultExtended->toDistributionSystem = (raw[9] & 0x40) != 0; + resultExtended->fromDistributionSystem = (raw[9] & 0x80) != 0; + memcpy(resultExtended->mac0, &raw[10], RADIOLIB_LR11X0_WIFI_RESULT_MAC_LEN); + memcpy(resultExtended->mac, &raw[16], RADIOLIB_LR11X0_WIFI_RESULT_MAC_LEN); + memcpy(resultExtended->mac2, &raw[22], RADIOLIB_LR11X0_WIFI_RESULT_MAC_LEN); + resultExtended->timestamp = (((uint64_t)raw[28] << 56) | ((uint64_t)raw[29] << 48)) | + (((uint64_t)raw[30] << 40) | ((uint64_t)raw[31] << 32)) | + (((uint64_t)raw[32] << 24) | ((uint64_t)raw[33] << 16)) | + (((uint64_t)raw[34] << 8) | (uint64_t)raw[35]); + resultExtended->periodBeacon = (((uint16_t)raw[36] << 8) | ((uint16_t)raw[37])) * 1024UL; + resultExtended->seqCtrl = (((uint16_t)raw[38] << 8) | ((uint16_t)raw[39])); + memcpy(resultExtended->ssid, &raw[40], RADIOLIB_LR11X0_WIFI_RESULT_SSID_LEN); + resultExtended->currentChannel = raw[72]; + memcpy(resultExtended->countryCode, &raw[73], 2); + resultExtended->countryCode[2] = '\0'; + resultExtended->ioReg = raw[75]; + resultExtended->fcsCheckOk = (raw[76] != 0); + resultExtended->phiOffset = (((uint16_t)raw[77] << 8) | ((uint16_t)raw[78])); + return(RADIOLIB_ERR_NONE); + } + + LR11x0WifiResultFull_t* resultFull = reinterpret_cast(result); + resultFull->frameType = raw[3] & 0x03; + resultFull->frameSubType = (raw[3] & 0x3C) >> 2; + resultFull->toDistributionSystem = (raw[3] & 0x40) != 0; + resultFull->fromDistributionSystem = (raw[3] & 0x80) != 0; + memcpy(resultFull->mac, &raw[4], RADIOLIB_LR11X0_WIFI_RESULT_MAC_LEN); + resultFull->phiOffset = (((uint16_t)raw[10] << 8) | ((uint16_t)raw[11])); + resultFull->timestamp = (((uint64_t)raw[12] << 56) | ((uint64_t)raw[13] << 48)) | + (((uint64_t)raw[14] << 40) | ((uint64_t)raw[15] << 32)) | + (((uint64_t)raw[16] << 24) | ((uint64_t)raw[17] << 16)) | + (((uint64_t)raw[18] << 8) | (uint64_t)raw[19]); + resultFull->periodBeacon = (((uint16_t)raw[20] << 8) | ((uint16_t)raw[21])) * 1024UL; + } + + return(RADIOLIB_ERR_NONE); +} + +int16_t LR11x0::wifiScan(uint8_t wifiType, uint8_t* count, uint8_t mode, uint16_t chanMask, uint8_t numScans, uint16_t timeout) { + RADIOLIB_ASSERT_PTR(count); + + // start scan + RADIOLIB_DEBUG_BASIC_PRINTLN("WiFi scan start"); + int16_t state = startWifiScan(wifiType, mode, chanMask, numScans, timeout); + RADIOLIB_ASSERT(state); + + // wait for scan finished or timeout + RadioLibTime_t softTimeout = 30UL * 1000UL; + RadioLibTime_t start = this->mod->hal->millis(); + while(!this->mod->hal->digitalRead(this->mod->getIrq())) { + this->mod->hal->yield(); + if(this->mod->hal->millis() - start > softTimeout) { + RADIOLIB_DEBUG_BASIC_PRINTLN("Timeout waiting for IRQ"); + this->standby(); + return(RADIOLIB_ERR_RX_TIMEOUT); + } + } + RADIOLIB_DEBUG_BASIC_PRINTLN("WiFi scan done in %lu ms", (long unsigned int)(this->mod->hal->millis() - start)); + + // read number of results + return(getWifiScanResultsCount(count)); +} + +int16_t LR11x0::getVersionInfo(LR11x0VersionInfo_t* info) { + RADIOLIB_ASSERT_PTR(info); + + int16_t state = this->getVersion(&info->hardware, &info->device, &info->fwMajor, &info->fwMinor); + RADIOLIB_ASSERT(state); + + // LR1121 does not have GNSS and WiFi scanning + if(this->chipType == RADIOLIB_LR11X0_DEVICE_LR1121) { + info->fwMajorWiFi = 0; + info->fwMinorWiFi = 0; + info->fwGNSS = 0; + info->almanacGNSS = 0; + return(RADIOLIB_ERR_NONE); + } + + state = this->wifiReadVersion(&info->fwMajorWiFi, &info->fwMinorWiFi); + RADIOLIB_ASSERT(state); + return(this->gnssReadVersion(&info->fwGNSS, &info->almanacGNSS)); +} + +int16_t LR11x0::updateFirmware(const uint32_t* image, size_t size, bool nonvolatile) { + RADIOLIB_ASSERT_PTR(image); + + // put the device to bootloader mode + int16_t state = this->reboot(true); + RADIOLIB_ASSERT(state); + this->mod->hal->delay(500); + + // check we're in bootloader + uint8_t device = 0xFF; + state = this->getVersion(NULL, &device, NULL, NULL); + RADIOLIB_ASSERT(state); + if(device != RADIOLIB_LR11X0_DEVICE_BOOT) { + RADIOLIB_DEBUG_BASIC_PRINTLN("Failed to put device to bootloader mode, %02x != %02x", (unsigned int)device, (unsigned int)RADIOLIB_LR11X0_DEVICE_BOOT); + return(RADIOLIB_ERR_CHIP_NOT_FOUND); + } + + // erase the image + state = this->bootEraseFlash(); + RADIOLIB_ASSERT(state); + + // wait for BUSY to go low + RadioLibTime_t start = this->mod->hal->millis(); + while(this->mod->hal->digitalRead(this->mod->getGpio())) { + this->mod->hal->yield(); + if(this->mod->hal->millis() - start >= 3000) { + RADIOLIB_DEBUG_BASIC_PRINTLN("BUSY pin timeout after erase!"); + return(RADIOLIB_ERR_SPI_CMD_TIMEOUT); + } + } + + // upload the new image + const size_t maxLen = 64; + size_t rem = size % maxLen; + size_t numWrites = (rem == 0) ? (size / maxLen) : ((size / maxLen) + 1); + RADIOLIB_DEBUG_BASIC_PRINTLN("Writing image in %lu chunks, last chunk size is %lu words", (unsigned long)numWrites, (unsigned long)rem); + for(size_t i = 0; i < numWrites; i ++) { + uint32_t offset = i * maxLen; + uint32_t len = (i == (numWrites - 1)) ? rem : maxLen; + RADIOLIB_DEBUG_BASIC_PRINTLN("Writing chunk %d at offset %08lx (%u words)", (int)i, (unsigned long)offset, (unsigned int)len); + this->bootWriteFlashEncrypted(offset*sizeof(uint32_t), (uint32_t*)&image[offset], len, nonvolatile); + } + + // kick the device from bootloader + state = this->reset(); + RADIOLIB_ASSERT(state); + + // verify we are no longer in bootloader + state = this->getVersion(NULL, &device, NULL, NULL); + RADIOLIB_ASSERT(state); + if(device == RADIOLIB_LR11X0_DEVICE_BOOT) { + RADIOLIB_DEBUG_BASIC_PRINTLN("Failed to kick device from bootloader mode, %02x == %02x", (unsigned int)device, (unsigned int)RADIOLIB_LR11X0_DEVICE_BOOT); + return(RADIOLIB_ERR_CHIP_NOT_FOUND); + } + + return(state); +} + +int16_t LR11x0::isGnssScanCapable() { + // get the version + LR11x0VersionInfo_t version; + int16_t state = this->getVersionInfo(&version); + RADIOLIB_ASSERT(state); + + // check the device firmware version is sufficient + uint16_t versionFull = ((uint16_t)version.fwMajor << 8) | (uint16_t)version.fwMinor; + state = RADIOLIB_ERR_UNSUPPORTED; + if((version.device == RADIOLIB_LR11X0_DEVICE_LR1110) && (versionFull >= 0x0401)) { + state = RADIOLIB_ERR_NONE; + } else if((version.device == RADIOLIB_LR11X0_DEVICE_LR1120) && (versionFull >= 0x0201)) { + state = RADIOLIB_ERR_NONE; + } + RADIOLIB_ASSERT(state); + + // in debug mode, dump the almanac + #if RADIOLIB_DEBUG_PROTOCOL + uint32_t addr = 0; + uint16_t sz = 0; + state = this->gnssAlmanacReadAddrSize(&addr, &sz); + RADIOLIB_ASSERT(state); + RADIOLIB_DEBUG_BASIC_PRINTLN("Almanac@%08x, %d bytes", addr, sz); + uint32_t buff[32] = { 0 }; + while(sz > 0) { + size_t len = sz > 32 ? 32 : sz/sizeof(uint32_t); + state = this->readRegMem32(addr, buff, len); + RADIOLIB_ASSERT(state); + RADIOLIB_DEBUG_HEXDUMP(NULL, (uint8_t*)buff, len*sizeof(uint32_t), addr); + addr += len*sizeof(uint32_t); + sz -= len*sizeof(uint32_t); + } + + uint8_t almanac[22] = { 0 }; + for(uint8_t i = 0; i < 128; i++) { + RADIOLIB_DEBUG_BASIC_PRINTLN("Almanac[%d]:", i); + state = this->gnssAlmanacReadSV(i, almanac); + RADIOLIB_ASSERT(state); + RADIOLIB_DEBUG_HEXDUMP(NULL, almanac, 22); + } + + #endif + + return(state); +} + +int16_t LR11x0::gnssScan(LR11x0GnssResult_t* res) { + RADIOLIB_ASSERT_PTR(res); + + // go to standby + int16_t state = standby(); + RADIOLIB_ASSERT(state); + + // set DIO mapping + state = setDioIrqParams(RADIOLIB_LR11X0_IRQ_GNSS_DONE | RADIOLIB_LR11X0_IRQ_GNSS_ABORT); + RADIOLIB_ASSERT(state); + + // set scan mode (single vs multiple) + state = this->gnssSetMode(0x03); + RADIOLIB_ASSERT(state); + + // set RF switch + this->mod->setRfSwitchState(LR11x0::MODE_GNSS); + + // start scan with high effort + RADIOLIB_DEBUG_BASIC_PRINTLN("GNSS scan start"); + state = this->gnssPerformScan(RADIOLIB_LR11X0_GNSS_EFFORT_MID, 0x3C, 16); + RADIOLIB_ASSERT(state); + + // wait for scan finished or timeout + // this can take very long if both GPS and BeiDou are enabled + RadioLibTime_t softTimeout = 300UL * 1000UL; + RadioLibTime_t start = this->mod->hal->millis(); + while(!this->mod->hal->digitalRead(this->mod->getIrq())) { + this->mod->hal->yield(); + if(this->mod->hal->millis() - start > softTimeout) { + this->gnssAbort(); + RADIOLIB_DEBUG_BASIC_PRINTLN("Timeout waiting for IRQ"); + } + } + + // restore the switch + this->mod->setRfSwitchState(Module::MODE_IDLE); + RADIOLIB_DEBUG_BASIC_PRINTLN("GNSS scan done in %lu ms", (long unsigned int)(this->mod->hal->millis() - start)); + + // distinguish between GNSS-done and GNSS-abort outcomes and clear the flags + uint32_t irq = this->getIrqStatus(); + this->clearIrq(RADIOLIB_LR11X0_IRQ_ALL); + if(irq & RADIOLIB_LR11X0_IRQ_GNSS_ABORT) { + return(RADIOLIB_ERR_RX_TIMEOUT); + } + + // retrieve the demodulator status + uint8_t info = 0; + state = this->gnssReadDemodStatus(&res->demodStat, &info); + RADIOLIB_ASSERT(state); + RADIOLIB_DEBUG_BASIC_PRINTLN("Demod status %d, info %02x", (int)res->demodStat, (unsigned int)info); + + // retrieve the number of detected satellites + state = this->gnssGetNbSvDetected(&res->numSatsDet); + RADIOLIB_ASSERT(state); + + // retrieve the result size + state = this->gnssGetResultSize(&res->resSize); + RADIOLIB_ASSERT(state); + + // check and return demodulator status + if(res->demodStat < RADIOLIB_LR11X0_GNSS_DEMOD_STATUS_TOW_FOUND) { + return(RADIOLIB_ERR_GNSS_DEMOD(res->demodStat)); + } + + return(state); +} + +int16_t LR11x0::getGnssAlmanacStatus(LR11x0GnssAlmanacStatus_t *stat) { + RADIOLIB_ASSERT_PTR(stat); + + // save the time the time until subframe is relative to + stat->start = this->mod->hal->millis(); + + // get the raw data + uint8_t raw[53] = { 0 }; + int16_t state = this->gnssReadAlmanacStatus(raw); + RADIOLIB_ASSERT(state); + + // parse the reply + stat->gps.status = (int8_t)raw[0]; + stat->gps.timeUntilSubframe = ((uint32_t)(raw[1]) << 24) | ((uint32_t)(raw[2]) << 16) | ((uint32_t)(raw[3]) << 8) | (uint32_t)raw[4]; + stat->gps.numSubframes = raw[5]; + stat->gps.nextSubframe4SvId = raw[6]; + stat->gps.nextSubframe5SvId = raw[7]; + stat->gps.nextSubframeStart = raw[8]; + stat->gps.numUpdateNeeded = raw[9]; + stat->gps.flagsUpdateNeeded[0] = ((uint32_t)(raw[10]) << 24) | ((uint32_t)(raw[11]) << 16) | ((uint32_t)(raw[12]) << 8) | (uint32_t)raw[13]; + stat->gps.flagsActive[0] = ((uint32_t)(raw[14]) << 24) | ((uint32_t)(raw[15]) << 16) | ((uint32_t)(raw[16]) << 8) | (uint32_t)raw[17]; + stat->beidou.status = (int8_t)raw[18]; + stat->beidou.timeUntilSubframe = ((uint32_t)(raw[19]) << 24) | ((uint32_t)(raw[20]) << 16) | ((uint32_t)(raw[21]) << 8) | (uint32_t)raw[22]; + stat->beidou.numSubframes = raw[23]; + stat->beidou.nextSubframe4SvId = raw[24]; + stat->beidou.nextSubframe5SvId = raw[25]; + stat->beidou.nextSubframeStart = raw[26]; + stat->beidou.numUpdateNeeded = raw[27]; + stat->beidou.flagsUpdateNeeded[0] = ((uint32_t)(raw[28]) << 24) | ((uint32_t)(raw[29]) << 16) | ((uint32_t)(raw[30]) << 8) | (uint32_t)raw[31]; + stat->beidou.flagsUpdateNeeded[1] = ((uint32_t)(raw[32]) << 24) | ((uint32_t)(raw[33]) << 16) | ((uint32_t)(raw[34]) << 8) | (uint32_t)raw[35]; + stat->beidou.flagsActive[0] = ((uint32_t)(raw[36]) << 24) | ((uint32_t)(raw[37]) << 16) | ((uint32_t)(raw[38]) << 8) | (uint32_t)raw[39]; + stat->beidou.flagsActive[1] = ((uint32_t)(raw[40]) << 24) | ((uint32_t)(raw[41]) << 16) | ((uint32_t)(raw[42]) << 8) | (uint32_t)raw[43]; + stat->beidouSvNoAlmanacFlags[0] = ((uint32_t)(raw[44]) << 24) | ((uint32_t)(raw[45]) << 16) | ((uint32_t)(raw[46]) << 8) | (uint32_t)raw[47]; + stat->beidouSvNoAlmanacFlags[1] = ((uint32_t)(raw[18]) << 24) | ((uint32_t)(raw[49]) << 16) | ((uint32_t)(raw[50]) << 8) | (uint32_t)raw[51]; + stat->nextAlmanacId = raw[52]; + + return(state); +} + +int16_t LR11x0::gnssDelayUntilSubframe(LR11x0GnssAlmanacStatus_t *stat, uint8_t constellation) { + RADIOLIB_ASSERT_PTR(stat); + + // almanac update has to be called at least 1.3 seconds before the subframe + // we use 2.3 seconds to be on the safe side + + // calculate absolute times + RadioLibTime_t window = stat->start + stat->gps.timeUntilSubframe - 2300; + if(constellation == RADIOLIB_LR11X0_GNSS_CONSTELLATION_BEIDOU) { + window = stat->start + stat->beidou.timeUntilSubframe - 2300; + } + RadioLibTime_t now = this->mod->hal->millis(); + if(now > window) { + // we missed the window + return(RADIOLIB_ERR_GNSS_SUBFRAME_NOT_AVAILABLE); + } + + RadioLibTime_t delay = window - now; + RADIOLIB_DEBUG_BASIC_PRINTLN("Time until subframe %lu ms", delay); + this->mod->hal->delay(delay); + return(RADIOLIB_ERR_NONE); +} + +// TODO fix last satellite always out of date +int16_t LR11x0::updateGnssAlmanac(uint8_t constellation) { + int16_t state = this->setDioIrqParams(RADIOLIB_LR11X0_IRQ_GNSS_DONE | RADIOLIB_LR11X0_IRQ_GNSS_ABORT); + RADIOLIB_ASSERT(state); + + state = this->gnssAlmanacUpdateFromSat(RADIOLIB_LR11X0_GNSS_EFFORT_MID, constellation); + RADIOLIB_ASSERT(state); + + // wait for scan finished or timeout, assumes 2 subframes and up to 2.3s pre-roll + uint32_t softTimeout = 16UL * 1000UL; + uint32_t start = this->mod->hal->millis(); + while (!this->mod->hal->digitalRead(this->mod->getIrq())) { + this->mod->hal->yield(); + if(this->mod->hal->millis() - start > softTimeout) { + this->gnssAbort(); + RADIOLIB_DEBUG_BASIC_PRINTLN("Timeout waiting for almanac update"); + } + } + + RADIOLIB_DEBUG_BASIC_PRINTLN("GPS almanac update done in %lu ms", (long unsigned int)(this->mod->hal->millis() - start)); + + // distinguish between GNSS-done and GNSS-abort outcomes and clear the flags + uint32_t irq = this->getIrqStatus(); + this->clearIrq(RADIOLIB_LR11X0_IRQ_ALL); + if(irq & RADIOLIB_LR11X0_IRQ_GNSS_ABORT) { + state = RADIOLIB_ERR_RX_TIMEOUT; + } + + return(state); +} + +int16_t LR11x0::getGnssPosition(LR11x0GnssPosition_t* pos, bool filtered) { + RADIOLIB_ASSERT_PTR(pos); + + uint8_t error = 0; + int16_t state; + if(filtered) { + state = this->gnssReadDopplerSolverRes(&error, &pos->numSatsUsed, NULL, NULL, NULL, NULL, &pos->latitude, &pos->longitude, &pos->accuracy, NULL); + } else { + state = this->gnssReadDopplerSolverRes(&error, &pos->numSatsUsed, &pos->latitude, &pos->longitude, &pos->accuracy, NULL, NULL, NULL, NULL, NULL); + } + RADIOLIB_ASSERT(state); + + // check the solver error + if(error != 0) { + return(RADIOLIB_ERR_GNSS_SOLVER(error)); + } + + return(state); +} + +int16_t LR11x0::getGnssSatellites(LR11x0GnssSatellite_t* sats, uint8_t numSats) { + RADIOLIB_ASSERT_PTR(sats); + if(numSats >= 32) { + return(RADIOLIB_ERR_MEMORY_ALLOCATION_FAILED); + } + + uint8_t svId[32] = { 0 }; + uint8_t snr[32] = { 0 }; + int16_t doppler[32] = { 0 }; + int16_t state = this->gnssGetSvDetected(svId, snr, doppler, numSats); + RADIOLIB_ASSERT(state); + for(size_t i = 0; i < numSats; i++) { + sats[i].svId = svId[i]; + sats[i].c_n0 = snr[i] + 31; + sats[i].doppler = doppler[i]; + } + + return(state); +} + +int16_t LR11x0::getModem(ModemType_t* modem) { + RADIOLIB_ASSERT_PTR(modem); + + uint8_t packetType = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + int16_t state = getPacketType(&packetType); + RADIOLIB_ASSERT(state); + + switch(packetType) { + case(RADIOLIB_LR11X0_PACKET_TYPE_LORA): + *modem = ModemType_t::RADIOLIB_MODEM_LORA; + return(RADIOLIB_ERR_NONE); + case(RADIOLIB_LR11X0_PACKET_TYPE_GFSK): + *modem = ModemType_t::RADIOLIB_MODEM_FSK; + return(RADIOLIB_ERR_NONE); + case(RADIOLIB_LR11X0_PACKET_TYPE_LR_FHSS): + *modem = ModemType_t::RADIOLIB_MODEM_LRFHSS; + return(RADIOLIB_ERR_NONE); + } + + return(RADIOLIB_ERR_WRONG_MODEM); +} + +int16_t LR11x0::modSetup(float tcxoVoltage, uint8_t modem) { + this->mod->init(); + this->mod->hal->pinMode(this->mod->getIrq(), this->mod->hal->GpioModeInput); + this->mod->hal->pinMode(this->mod->getGpio(), this->mod->hal->GpioModeInput); + this->mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_ADDR] = Module::BITS_32; + this->mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_CMD] = Module::BITS_16; + this->mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_STATUS] = Module::BITS_8; + this->mod->spiConfig.statusPos = 0; + this->mod->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_READ] = RADIOLIB_LR11X0_CMD_READ_REG_MEM; + this->mod->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_WRITE] = RADIOLIB_LR11X0_CMD_WRITE_REG_MEM; + this->mod->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_NOP] = RADIOLIB_LR11X0_CMD_NOP; + this->mod->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_STATUS] = RADIOLIB_LR11X0_CMD_GET_STATUS; + this->mod->spiConfig.stream = true; + this->mod->spiConfig.parseStatusCb = SPIparseStatus; + this->mod->spiConfig.checkStatusCb = SPIcheckStatus; + + // try to find the LR11x0 chip - this will also reset the module at least once + if(!LR11x0::findChip(this->chipType)) { + RADIOLIB_DEBUG_BASIC_PRINTLN("No LR11x0 found!"); + this->mod->term(); + return(RADIOLIB_ERR_CHIP_NOT_FOUND); + } + RADIOLIB_DEBUG_BASIC_PRINTLN("M\tLR11x0"); + + // set mode to standby + int16_t state = standby(); + RADIOLIB_ASSERT(state); + + // set TCXO control, if requested + if(!this->XTAL && tcxoVoltage > 0.0) { + state = setTCXO(tcxoVoltage); + RADIOLIB_ASSERT(state); + } + + // configure settings not accessible by API + return(config(modem)); +} + +int16_t LR11x0::SPIparseStatus(uint8_t in) { + if((in & 0b00001110) == RADIOLIB_LR11X0_STAT_1_CMD_PERR) { + return(RADIOLIB_ERR_SPI_CMD_INVALID); + } else if((in & 0b00001110) == RADIOLIB_LR11X0_STAT_1_CMD_FAIL) { + return(RADIOLIB_ERR_SPI_CMD_FAILED); + } else if((in == 0x00) || (in == 0xFF)) { + return(RADIOLIB_ERR_CHIP_NOT_FOUND); + } + return(RADIOLIB_ERR_NONE); +} + +int16_t LR11x0::SPIcheckStatus(Module* mod) { + // the status check command doesn't return status in the same place as other read commands, + // but only as the first byte (as with any other command), hence LR11x0::SPIcommand can't be used + // it also seems to ignore the actual command, and just sending in bunch of NOPs will work + uint8_t buff[6] = { 0 }; + mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_STATUS] = Module::BITS_0; + int16_t state = mod->SPItransferStream(NULL, 0, false, NULL, buff, sizeof(buff), true); + mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_STATUS] = Module::BITS_8; + RADIOLIB_ASSERT(state); + return(LR11x0::SPIparseStatus(buff[0])); +} + +int16_t LR11x0::SPIcommand(uint16_t cmd, bool write, uint8_t* data, size_t len, uint8_t* out, size_t outLen) { + int16_t state = RADIOLIB_ERR_UNKNOWN; + if(!write) { + // the SPI interface of LR11x0 requires two separate transactions for reading + // send the 16-bit command + state = this->mod->SPIwriteStream(cmd, out, outLen, true, false); + RADIOLIB_ASSERT(state); + + // read the result without command + this->mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_CMD] = Module::BITS_0; + state = this->mod->SPIreadStream(RADIOLIB_LR11X0_CMD_NOP, data, len, true, false); + this->mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_CMD] = Module::BITS_16; + + } else { + // write is just a single transaction + state = this->mod->SPIwriteStream(cmd, data, len, true, true); + + } + + return(state); +} + +bool LR11x0::findChip(uint8_t ver) { + uint8_t i = 0; + bool flagFound = false; + while((i < 10) && !flagFound) { + // reset the module + reset(); + + // read the version + LR11x0VersionInfo_t info; + int16_t state = getVersionInfo(&info); + RADIOLIB_ASSERT(state); + + if(info.device == ver) { + RADIOLIB_DEBUG_BASIC_PRINTLN("Found LR11x0: RADIOLIB_LR11X0_CMD_GET_VERSION = 0x%02x", info.device); + RADIOLIB_DEBUG_BASIC_PRINTLN("Base FW version: %d.%d", (int)info.fwMajor, (int)info.fwMinor); + if(this->chipType != RADIOLIB_LR11X0_DEVICE_LR1121) { + RADIOLIB_DEBUG_BASIC_PRINTLN("WiFi FW version: %d.%d", (int)info.fwMajorWiFi, (int)info.fwMinorWiFi); + RADIOLIB_DEBUG_BASIC_PRINTLN("GNSS FW version: %d.%d", (int)info.fwGNSS, (int)info.almanacGNSS); + } + flagFound = true; + } else { + RADIOLIB_DEBUG_BASIC_PRINTLN("LR11x0 not found! (%d of 10 tries) RADIOLIB_LR11X0_CMD_GET_VERSION = 0x%02x", i + 1, info.device); + RADIOLIB_DEBUG_BASIC_PRINTLN("Expected: 0x%02x", ver); + this->mod->hal->delay(10); + i++; + } + } + + return(flagFound); +} + +int16_t LR11x0::config(uint8_t modem) { + int16_t state = RADIOLIB_ERR_UNKNOWN; + + // set Rx/Tx fallback mode to STDBY_RC + state = this->setRxTxFallbackMode(RADIOLIB_LR11X0_FALLBACK_MODE_STBY_RC); + RADIOLIB_ASSERT(state); + + // clear IRQ + state = this->clearIrq(RADIOLIB_LR11X0_IRQ_ALL); + state |= this->setDioIrqParams(RADIOLIB_LR11X0_IRQ_NONE); + RADIOLIB_ASSERT(state); + + // calibrate all blocks + (void)this->calibrate(RADIOLIB_LR11X0_CALIBRATE_ALL); + + // wait for calibration completion + this->mod->hal->delay(5); + while(this->mod->hal->digitalRead(this->mod->getGpio())) { + this->mod->hal->yield(); + } + + // if something failed, show the device errors + #if RADIOLIB_DEBUG_BASIC + if(state != RADIOLIB_ERR_NONE) { + // unless mode is forced to standby, device errors will be 0 + standby(); + uint16_t errors = 0; + getErrors(&errors); + RADIOLIB_DEBUG_BASIC_PRINTLN("Calibration failed, device errors: 0x%X", errors); + } + #endif + + // set modem + state = this->setPacketType(modem); + return(state); +} + +int16_t LR11x0::setPacketMode(uint8_t mode, uint8_t len) { + // check active modem + uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + int16_t state = getPacketType(&type); + RADIOLIB_ASSERT(state); + if(type != RADIOLIB_LR11X0_PACKET_TYPE_GFSK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // set requested packet mode + state = setPacketParamsGFSK(this->preambleLengthGFSK, this->preambleDetLength, this->syncWordLength, this->addrComp, mode, len, this->crcTypeGFSK, this->whitening); + RADIOLIB_ASSERT(state); + + // update cached value + this->packetType = mode; + return(state); +} + +int16_t LR11x0::startCad(uint8_t symbolNum, uint8_t detPeak, uint8_t detMin, uint8_t exitMode, RadioLibTime_t timeout) { + // check active modem + uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + int16_t state = getPacketType(&type); + RADIOLIB_ASSERT(state); + if(type != RADIOLIB_LR11X0_PACKET_TYPE_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // select CAD parameters + // TODO the magic numbers are based on Semtech examples, this is probably suboptimal + uint8_t num = symbolNum; + if(num == RADIOLIB_LR11X0_CAD_PARAM_DEFAULT) { + num = 2; + } + + const uint8_t detPeakValues[8] = { 48, 48, 50, 55, 55, 59, 61, 65 }; + uint8_t peak = detPeak; + if(peak == RADIOLIB_LR11X0_CAD_PARAM_DEFAULT) { + peak = detPeakValues[this->spreadingFactor - 5]; + } + + uint8_t min = detMin; + if(min == RADIOLIB_LR11X0_CAD_PARAM_DEFAULT) { + min = 10; + } + + uint8_t mode = exitMode; + if(mode == RADIOLIB_LR11X0_CAD_PARAM_DEFAULT) { + mode = RADIOLIB_LR11X0_CAD_EXIT_MODE_STBY_RC; + } + + uint32_t timeout_raw = (float)timeout / 30.52f; + + // set CAD parameters + // TODO add configurable exit mode and timeout + state = setCadParams(num, peak, min, mode, timeout_raw); + RADIOLIB_ASSERT(state); + + // start CAD + return(setCad()); +} + +int16_t LR11x0::setHeaderType(uint8_t hdrType, size_t len) { + // check active modem + uint8_t type = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + int16_t state = getPacketType(&type); + RADIOLIB_ASSERT(state); + if(type != RADIOLIB_LR11X0_PACKET_TYPE_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // set requested packet mode + state = setPacketParamsLoRa(this->preambleLengthLoRa, hdrType, len, this->crcTypeLoRa, this->invertIQEnabled); + RADIOLIB_ASSERT(state); + + // update cached value + this->headerType = hdrType; + this->implicitLen = len; + + return(state); +} + +Module* LR11x0::getMod() { + return(this->mod); +} + +int16_t LR11x0::writeRegMem32(uint32_t addr, uint32_t* data, size_t len) { + // check maximum size + if(len > (RADIOLIB_LR11X0_SPI_MAX_READ_WRITE_LEN/sizeof(uint32_t))) { + return(RADIOLIB_ERR_SPI_CMD_INVALID); + } + return(this->writeCommon(RADIOLIB_LR11X0_CMD_WRITE_REG_MEM, addr, data, len, false)); +} + +int16_t LR11x0::readRegMem32(uint32_t addr, uint32_t* data, size_t len) { + // check maximum size + if(len >= (RADIOLIB_LR11X0_SPI_MAX_READ_WRITE_LEN/sizeof(uint32_t))) { + return(RADIOLIB_ERR_SPI_CMD_INVALID); + } + + // the request contains the address and length + uint8_t reqBuff[5] = { + (uint8_t)((addr >> 24) & 0xFF), (uint8_t)((addr >> 16) & 0xFF), + (uint8_t)((addr >> 8) & 0xFF), (uint8_t)(addr & 0xFF), + (uint8_t)len, + }; + + // build buffers - later we need to ensure endians are correct, + // so there is probably no way to do this without copying buffers and iterating + #if RADIOLIB_STATIC_ONLY + uint8_t rplBuff[RADIOLIB_LR11X0_SPI_MAX_READ_WRITE_LEN]; + #else + uint8_t* rplBuff = new uint8_t[len*sizeof(uint32_t)]; + #endif + + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_READ_REG_MEM, false, rplBuff, len*sizeof(uint32_t), reqBuff, sizeof(reqBuff)); + + // convert endians + if(data && (state == RADIOLIB_ERR_NONE)) { + for(size_t i = 0; i < len; i++) { + data[i] = ((uint32_t)rplBuff[2 + i*sizeof(uint32_t)] << 24) | ((uint32_t)rplBuff[3 + i*sizeof(uint32_t)] << 16) | ((uint32_t)rplBuff[4 + i*sizeof(uint32_t)] << 8) | (uint32_t)rplBuff[5 + i*sizeof(uint32_t)]; + } + } + + #if !RADIOLIB_STATIC_ONLY + delete[] rplBuff; + #endif + + return(state); +} + +int16_t LR11x0::writeBuffer8(uint8_t* data, size_t len) { + // check maximum size + if(len > RADIOLIB_LR11X0_SPI_MAX_READ_WRITE_LEN) { + return(RADIOLIB_ERR_SPI_CMD_INVALID); + } + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_WRITE_BUFFER, true, data, len)); +} + +int16_t LR11x0::readBuffer8(uint8_t* data, size_t len, size_t offset) { + // check maximum size + if(len > RADIOLIB_LR11X0_SPI_MAX_READ_WRITE_LEN) { + return(RADIOLIB_ERR_SPI_CMD_INVALID); + } + + // build buffers + size_t reqLen = 2*sizeof(uint8_t) + len; + #if RADIOLIB_STATIC_ONLY + uint8_t reqBuff[sizeof(uint32_t) + RADIOLIB_LR11X0_SPI_MAX_READ_WRITE_LEN]; + #else + uint8_t* reqBuff = new uint8_t[reqLen]; + #endif + + // set the offset and length + reqBuff[0] = (uint8_t)offset; + reqBuff[1] = (uint8_t)len; + + // send the request + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_READ_BUFFER, false, data, len, reqBuff, reqLen); + #if !RADIOLIB_STATIC_ONLY + delete[] reqBuff; + #endif + return(state); +} + +int16_t LR11x0::clearRxBuffer(void) { + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_CLEAR_RX_BUFFER, true, NULL, 0)); +} + +int16_t LR11x0::writeRegMemMask32(uint32_t addr, uint32_t mask, uint32_t data) { + uint8_t buff[12] = { + (uint8_t)((addr >> 24) & 0xFF), (uint8_t)((addr >> 16) & 0xFF), (uint8_t)((addr >> 8) & 0xFF), (uint8_t)(addr & 0xFF), + (uint8_t)((mask >> 24) & 0xFF), (uint8_t)((mask >> 16) & 0xFF), (uint8_t)((mask >> 8) & 0xFF), (uint8_t)(mask & 0xFF), + (uint8_t)((data >> 24) & 0xFF), (uint8_t)((data >> 16) & 0xFF), (uint8_t)((data >> 8) & 0xFF), (uint8_t)(data & 0xFF), + }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_WRITE_REG_MEM_MASK, true, buff, sizeof(buff))); +} + +int16_t LR11x0::getStatus(uint8_t* stat1, uint8_t* stat2, uint32_t* irq) { + uint8_t buff[6] = { 0 }; + + // the status check command doesn't return status in the same place as other read commands + // but only as the first byte (as with any other command), hence LR11x0::SPIcommand can't be used + // it also seems to ignore the actual command, and just sending in bunch of NOPs will work + int16_t state = this->mod->SPItransferStream(NULL, 0, false, NULL, buff, sizeof(buff), true); + + // pass the replies + if(stat1) { *stat1 = buff[0]; } + if(stat2) { *stat2 = buff[1]; } + if(irq) { *irq = ((uint32_t)(buff[2]) << 24) | ((uint32_t)(buff[3]) << 16) | ((uint32_t)(buff[4]) << 8) | (uint32_t)buff[5]; } + + return(state); +} + +int16_t LR11x0::getVersion(uint8_t* hw, uint8_t* device, uint8_t* major, uint8_t* minor) { + uint8_t buff[4] = { 0 }; + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GET_VERSION, false, buff, sizeof(buff)); + + // pass the replies + if(hw) { *hw = buff[0]; } + if(device) { *device = buff[1]; } + if(major) { *major = buff[2]; } + if(minor) { *minor = buff[3]; } + + return(state); +} + +int16_t LR11x0::getErrors(uint16_t* err) { + uint8_t buff[2] = { 0 }; + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GET_ERRORS, false, buff, sizeof(buff)); + + // pass the replies + if(err) { *err = ((uint16_t)(buff[0]) << 8) | (uint16_t)buff[1]; } + + return(state); +} + +int16_t LR11x0::clearErrors(void) { + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_CLEAR_ERRORS, true, NULL, 0)); +} + +int16_t LR11x0::calibrate(uint8_t params) { + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_CALIBRATE, true, ¶ms, 1)); +} + +int16_t LR11x0::setRegMode(uint8_t mode) { + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_SET_REG_MODE, true, &mode, 1)); +} + +int16_t LR11x0::calibrateImageRejection(float freqMin, float freqMax) { + uint8_t buff[2] = { + (uint8_t)floor((freqMin - 1.0f) / 4.0f), + (uint8_t)ceil((freqMax + 1.0f) / 4.0f) + }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_CALIB_IMAGE, true, buff, sizeof(buff))); +} + +int16_t LR11x0::setDioAsRfSwitch(uint8_t en, uint8_t stbyCfg, uint8_t rxCfg, uint8_t txCfg, uint8_t txHpCfg, uint8_t txHfCfg, uint8_t gnssCfg, uint8_t wifiCfg) { + uint8_t buff[8] = { en, stbyCfg, rxCfg, txCfg, txHpCfg, txHfCfg, gnssCfg, wifiCfg }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_SET_DIO_AS_RF_SWITCH, true, buff, sizeof(buff))); +} + +int16_t LR11x0::setDioIrqParams(uint32_t irq1, uint32_t irq2) { + uint8_t buff[8] = { + (uint8_t)((irq1 >> 24) & 0xFF), (uint8_t)((irq1 >> 16) & 0xFF), (uint8_t)((irq1 >> 8) & 0xFF), (uint8_t)(irq1 & 0xFF), + (uint8_t)((irq2 >> 24) & 0xFF), (uint8_t)((irq2 >> 16) & 0xFF), (uint8_t)((irq2 >> 8) & 0xFF), (uint8_t)(irq2 & 0xFF), + }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_SET_DIO_IRQ_PARAMS, true, buff, sizeof(buff))); +} + +int16_t LR11x0::setDioIrqParams(uint32_t irq) { + return(setDioIrqParams(irq, 0)); +} + +int16_t LR11x0::clearIrq(uint32_t irq) { + uint8_t buff[4] = { + (uint8_t)((irq >> 24) & 0xFF), (uint8_t)((irq >> 16) & 0xFF), (uint8_t)((irq >> 8) & 0xFF), (uint8_t)(irq & 0xFF), + }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_CLEAR_IRQ, true, buff, sizeof(buff))); +} + +int16_t LR11x0::configLfClock(uint8_t setup) { + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_CONFIG_LF_LOCK, true, &setup, 1)); +} + +int16_t LR11x0::setTcxoMode(uint8_t tune, uint32_t delay) { + uint8_t buff[4] = { + tune, (uint8_t)((delay >> 16) & 0xFF), (uint8_t)((delay >> 8) & 0xFF), (uint8_t)(delay & 0xFF), + }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_SET_TCXO_MODE, true, buff, sizeof(buff))); +} + +int16_t LR11x0::reboot(bool stay) { + uint8_t buff[1] = { (uint8_t)(stay*3) }; + return(this->mod->SPIwriteStream(RADIOLIB_LR11X0_CMD_REBOOT, buff, sizeof(buff), true, false)); +} + +int16_t LR11x0::getVbat(float* vbat) { + uint8_t buff[1] = { 0 }; + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GET_VBAT, false, buff, sizeof(buff)); + + // pass the replies + if(vbat) { *vbat = (((float)buff[0]/51.0f) - 1.0f)*1.35f; } + + return(state); +} + +int16_t LR11x0::getTemp(float* temp) { + uint8_t buff[2] = { 0 }; + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GET_TEMP, false, buff, sizeof(buff)); + + // pass the replies + if(temp) { + uint16_t raw = ((uint16_t)(buff[0]) << 8) | (uint16_t)buff[1]; + raw = raw & 0x07FF; //According LR1121 datasheet we need [0..10] bits + *temp = 25.0f - (1000.0f/1.7f)*(((float)raw/2047.0f)*1.35f - 0.7295f); //According LR1121 datasheet 1.35 + } + + return(state); +} + +int16_t LR11x0::setFs(void) { + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_SET_FS, true, NULL, 0)); +} + +int16_t LR11x0::getRandomNumber(uint32_t* rnd) { + uint8_t buff[4] = { 0 }; + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GET_RANDOM_NUMBER, false, buff, sizeof(buff)); + + // pass the replies + if(rnd) { *rnd = ((uint32_t)(buff[0]) << 24) | ((uint32_t)(buff[1]) << 16) | ((uint32_t)(buff[2]) << 8) | (uint32_t)buff[3]; } + + return(state); +} + +int16_t LR11x0::eraseInfoPage(void) { + // only page 1 can be erased + uint8_t buff[1] = { RADIOLIB_LR11X0_INFO_PAGE }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_ERASE_INFO_PAGE, true, buff, sizeof(buff))); +} + +int16_t LR11x0::writeInfoPage(uint16_t addr, const uint32_t* data, size_t len) { + // check maximum size + if(len > (RADIOLIB_LR11X0_SPI_MAX_READ_WRITE_LEN/sizeof(uint32_t))) { + return(RADIOLIB_ERR_SPI_CMD_INVALID); + } + + // build buffers - later we need to ensure endians are correct, + // so there is probably no way to do this without copying buffers and iterating + size_t buffLen = sizeof(uint8_t) + sizeof(uint16_t) + len*sizeof(uint32_t); + #if RADIOLIB_STATIC_ONLY + uint8_t dataBuff[sizeof(uint8_t) + sizeof(uint16_t) + RADIOLIB_LR11X0_SPI_MAX_READ_WRITE_LEN]; + #else + uint8_t* dataBuff = new uint8_t[buffLen]; + #endif + + // set the address + dataBuff[0] = RADIOLIB_LR11X0_INFO_PAGE; + dataBuff[1] = (uint8_t)((addr >> 8) & 0xFF); + dataBuff[2] = (uint8_t)(addr & 0xFF); + + // convert endians + for(size_t i = 0; i < len; i++) { + dataBuff[3 + i] = (uint8_t)((data[i] >> 24) & 0xFF); + dataBuff[4 + i] = (uint8_t)((data[i] >> 16) & 0xFF); + dataBuff[5 + i] = (uint8_t)((data[i] >> 8) & 0xFF); + dataBuff[6 + i] = (uint8_t)(data[i] & 0xFF); + } + + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_WRITE_INFO_PAGE, true, dataBuff, buffLen); + #if !RADIOLIB_STATIC_ONLY + delete[] dataBuff; + #endif + return(state); +} + +int16_t LR11x0::readInfoPage(uint16_t addr, uint32_t* data, size_t len) { + // check maximum size + if(len > (RADIOLIB_LR11X0_SPI_MAX_READ_WRITE_LEN/sizeof(uint32_t))) { + return(RADIOLIB_ERR_SPI_CMD_INVALID); + } + + // the request contains the address and length + uint8_t reqBuff[4] = { + RADIOLIB_LR11X0_INFO_PAGE, + (uint8_t)((addr >> 8) & 0xFF), (uint8_t)(addr & 0xFF), + (uint8_t)len, + }; + + // build buffers - later we need to ensure endians are correct, + // so there is probably no way to do this without copying buffers and iterating + #if RADIOLIB_STATIC_ONLY + uint8_t rplBuff[RADIOLIB_LR11X0_SPI_MAX_READ_WRITE_LEN]; + #else + uint8_t* rplBuff = new uint8_t[len*sizeof(uint32_t)]; + #endif + + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_READ_INFO_PAGE, false, rplBuff, len*sizeof(uint32_t), reqBuff, sizeof(reqBuff)); + + // convert endians + if(data && (state == RADIOLIB_ERR_NONE)) { + for(size_t i = 0; i < len; i++) { + data[i] = ((uint32_t)rplBuff[2 + i*sizeof(uint32_t)] << 24) | ((uint32_t)rplBuff[3 + i*sizeof(uint32_t)] << 16) | ((uint32_t)rplBuff[4 + i*sizeof(uint32_t)] << 8) | (uint32_t)rplBuff[5 + i*sizeof(uint32_t)]; + } + } + + #if !RADIOLIB_STATIC_ONLY + delete[] rplBuff; + #endif + + return(state); +} + +int16_t LR11x0::getChipEui(uint8_t* eui) { + RADIOLIB_ASSERT_PTR(eui); + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_GET_CHIP_EUI, false, eui, RADIOLIB_LR11X0_EUI_LEN)); +} + +int16_t LR11x0::getSemtechJoinEui(uint8_t* eui) { + RADIOLIB_ASSERT_PTR(eui); + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_GET_SEMTECH_JOIN_EUI, false, eui, RADIOLIB_LR11X0_EUI_LEN)); +} + +int16_t LR11x0::deriveRootKeysAndGetPin(uint8_t* pin) { + RADIOLIB_ASSERT_PTR(pin); + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_DERIVE_ROOT_KEYS_AND_GET_PIN, false, pin, RADIOLIB_LR11X0_PIN_LEN)); +} + +int16_t LR11x0::enableSpiCrc(bool en) { + // TODO implement this + (void)en; + // LR11X0 CRC is gen 0xA6 (0x65 but reflected), init 0xFF, input and result reflected + /*RadioLibCRCInstance.size = 8; + RadioLibCRCInstance.poly = 0xA6; + RadioLibCRCInstance.init = 0xFF; + RadioLibCRCInstance.out = 0x00; + RadioLibCRCInstance.refIn = true; + RadioLibCRCInstance.refOut = true;*/ + return(RADIOLIB_ERR_UNSUPPORTED); +} + +int16_t LR11x0::driveDiosInSleepMode(bool en) { + uint8_t buff[1] = { (uint8_t)en }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_DRIVE_DIOS_IN_SLEEP_MODE, true, buff, sizeof(buff))); +} + +int16_t LR11x0::resetStats(void) { + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_RESET_STATS, true, NULL, 0)); +} + +int16_t LR11x0::getStats(uint16_t* nbPktReceived, uint16_t* nbPktCrcError, uint16_t* data1, uint16_t* data2) { + uint8_t buff[8] = { 0 }; + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GET_STATS, false, buff, sizeof(buff)); + + // pass the replies + if(nbPktReceived) { *nbPktReceived = ((uint16_t)(buff[0]) << 8) | (uint16_t)buff[1]; } + if(nbPktCrcError) { *nbPktCrcError = ((uint16_t)(buff[2]) << 8) | (uint16_t)buff[3]; } + if(data1) { *data1 = ((uint16_t)(buff[4]) << 8) | (uint16_t)buff[5]; } + if(data2) { *data2 = ((uint16_t)(buff[6]) << 8) | (uint16_t)buff[7]; } + + return(state); +} + +int16_t LR11x0::getPacketType(uint8_t* type) { + uint8_t buff[1] = { 0 }; + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GET_PACKET_TYPE, false, buff, sizeof(buff)); + + // pass the replies + if(type) { *type = buff[0]; } + + return(state); +} + +int16_t LR11x0::getRxBufferStatus(uint8_t* len, uint8_t* startOffset) { + uint8_t buff[2] = { 0 }; + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GET_RX_BUFFER_STATUS, false, buff, sizeof(buff)); + + // pass the replies + if(len) { *len = buff[0]; } + if(startOffset) { *startOffset = buff[1]; } + + return(state); +} + +int16_t LR11x0::getPacketStatusLoRa(float* rssiPkt, float* snrPkt, float* signalRssiPkt) { + uint8_t buff[3] = { 0 }; + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GET_PACKET_STATUS, false, buff, sizeof(buff)); + + // pass the replies + if(rssiPkt) { *rssiPkt = (float)buff[0] / -2.0f; } + if(snrPkt) { *snrPkt = (float)((int8_t)buff[1]) / 4.0f; } + if(signalRssiPkt) { *signalRssiPkt = buff[2]; } + + return(state); +} + +int16_t LR11x0::getPacketStatusGFSK(float* rssiSync, float* rssiAvg, uint8_t* rxLen, uint8_t* stat) { + uint8_t buff[4] = { 0 }; + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GET_PACKET_STATUS, false, buff, sizeof(buff)); + + // pass the replies + if(rssiSync) { *rssiSync = (float)buff[0] / -2.0f; } + if(rssiAvg) { *rssiAvg = (float)buff[1] / -2.0f; } + if(rxLen) { *rxLen = buff[2]; } + if(stat) { *stat = buff[3]; } + + return(state); +} + +int16_t LR11x0::getRssiInst(float* rssi) { + uint8_t buff[1] = { 0 }; + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GET_RSSI_INST, false, buff, sizeof(buff)); + + // pass the replies + if(rssi) { *rssi = (float)buff[0] / -2.0f; } + + return(state); +} + +int16_t LR11x0::setGfskSyncWord(uint8_t* sync) { + RADIOLIB_ASSERT_PTR(sync); + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_SET_GFSK_SYNC_WORD, true, sync, RADIOLIB_LR11X0_GFSK_SYNC_WORD_LEN)); +} + +int16_t LR11x0::setLoRaPublicNetwork(bool pub) { + uint8_t buff[1] = { (uint8_t)pub }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_SET_LORA_PUBLIC_NETWORK, true, buff, sizeof(buff))); +} + +int16_t LR11x0::setRx(uint32_t timeout) { + uint8_t buff[3] = { + (uint8_t)((timeout >> 16) & 0xFF), (uint8_t)((timeout >> 8) & 0xFF), (uint8_t)(timeout & 0xFF), + }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_SET_RX, true, buff, sizeof(buff))); +} + +int16_t LR11x0::setTx(uint32_t timeout) { + uint8_t buff[3] = { + (uint8_t)((timeout >> 16) & 0xFF), (uint8_t)((timeout >> 8) & 0xFF), (uint8_t)(timeout & 0xFF), + }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_SET_TX, true, buff, sizeof(buff))); +} + +int16_t LR11x0::setRfFrequency(uint32_t rfFreq) { + uint8_t buff[4] = { + (uint8_t)((rfFreq >> 24) & 0xFF), (uint8_t)((rfFreq >> 16) & 0xFF), + (uint8_t)((rfFreq >> 8) & 0xFF), (uint8_t)(rfFreq & 0xFF), + }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_SET_RF_FREQUENCY, true, buff, sizeof(buff))); +} + +int16_t LR11x0::autoTxRx(uint32_t delay, uint8_t intMode, uint32_t timeout) { + uint8_t buff[7] = { + (uint8_t)((delay >> 16) & 0xFF), (uint8_t)((delay >> 8) & 0xFF), (uint8_t)(delay & 0xFF), intMode, + (uint8_t)((timeout >> 16) & 0xFF), (uint8_t)((timeout >> 8) & 0xFF), (uint8_t)(timeout & 0xFF), + }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_AUTO_TX_RX, true, buff, sizeof(buff))); +} + +int16_t LR11x0::setCadParams(uint8_t symNum, uint8_t detPeak, uint8_t detMin, uint8_t cadExitMode, uint32_t timeout) { + uint8_t buff[7] = { + symNum, detPeak, detMin, cadExitMode, + (uint8_t)((timeout >> 16) & 0xFF), (uint8_t)((timeout >> 8) & 0xFF), (uint8_t)(timeout & 0xFF), + }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_SET_CAD_PARAMS, true, buff, sizeof(buff))); +} + +int16_t LR11x0::setPacketType(uint8_t type) { + uint8_t buff[1] = { type }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_SET_PACKET_TYPE, true, buff, sizeof(buff))); +} + +int16_t LR11x0::setModulationParamsLoRa(uint8_t sf, uint8_t bw, uint8_t cr, uint8_t ldro) { + // calculate symbol length and enable low data rate optimization, if auto-configuration is enabled + if(this->ldroAuto) { + float symbolLength = (float)(uint32_t(1) << this->spreadingFactor) / (float)this->bandwidthKhz; + if(symbolLength >= 16.0) { + this->ldrOptimize = RADIOLIB_LR11X0_LORA_LDRO_ENABLED; + } else { + this->ldrOptimize = RADIOLIB_LR11X0_LORA_LDRO_DISABLED; + } + } else { + this->ldrOptimize = ldro; + } + + uint8_t buff[4] = { sf, bw, cr, this->ldrOptimize }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_SET_MODULATION_PARAMS, true, buff, sizeof(buff))); +} + +int16_t LR11x0::setModulationParamsGFSK(uint32_t br, uint8_t sh, uint8_t rxBw, uint32_t freqDev) { + uint8_t buff[10] = { + (uint8_t)((br >> 24) & 0xFF), (uint8_t)((br >> 16) & 0xFF), + (uint8_t)((br >> 8) & 0xFF), (uint8_t)(br & 0xFF), sh, rxBw, + (uint8_t)((freqDev >> 24) & 0xFF), (uint8_t)((freqDev >> 16) & 0xFF), + (uint8_t)((freqDev >> 8) & 0xFF), (uint8_t)(freqDev & 0xFF) + }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_SET_MODULATION_PARAMS, true, buff, sizeof(buff))); +} + +int16_t LR11x0::setModulationParamsLrFhss(uint32_t br, uint8_t sh) { + uint8_t buff[5] = { + (uint8_t)((br >> 24) & 0xFF), (uint8_t)((br >> 16) & 0xFF), + (uint8_t)((br >> 8) & 0xFF), (uint8_t)(br & 0xFF), sh + }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_SET_MODULATION_PARAMS, true, buff, sizeof(buff))); +} + +int16_t LR11x0::setModulationParamsSigfox(uint32_t br, uint8_t sh) { + // same as for LR-FHSS + return(this->setModulationParamsLrFhss(br, sh)); +} + +int16_t LR11x0::setPacketParamsLoRa(uint16_t preambleLen, uint8_t hdrType, uint8_t payloadLen, uint8_t crcType, uint8_t invertIQ) { + uint8_t buff[6] = { + (uint8_t)((preambleLen >> 8) & 0xFF), (uint8_t)(preambleLen & 0xFF), + hdrType, payloadLen, crcType, invertIQ + }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_SET_PACKET_PARAMS, true, buff, sizeof(buff))); +} + +int16_t LR11x0::setPacketParamsGFSK(uint16_t preambleLen, uint8_t preambleDetectorLen, uint8_t syncWordLen, uint8_t addrCmp, uint8_t packType, uint8_t payloadLen, uint8_t crcType, uint8_t whiten) { + uint8_t buff[9] = { + (uint8_t)((preambleLen >> 8) & 0xFF), (uint8_t)(preambleLen & 0xFF), + preambleDetectorLen, syncWordLen, addrCmp, packType, payloadLen, crcType, whiten + }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_SET_PACKET_PARAMS, true, buff, sizeof(buff))); +} + +int16_t LR11x0::setPacketParamsSigfox(uint8_t payloadLen, uint16_t rampUpDelay, uint16_t rampDownDelay, uint16_t bitNum) { + uint8_t buff[7] = { + payloadLen, (uint8_t)((rampUpDelay >> 8) & 0xFF), (uint8_t)(rampUpDelay & 0xFF), + (uint8_t)((rampDownDelay >> 8) & 0xFF), (uint8_t)(rampDownDelay & 0xFF), + (uint8_t)((bitNum >> 8) & 0xFF), (uint8_t)(bitNum & 0xFF), + }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_SET_PACKET_PARAMS, true, buff, sizeof(buff))); +} + +int16_t LR11x0::setTxParams(int8_t pwr, uint8_t ramp) { + uint8_t buff[2] = { (uint8_t)pwr, ramp }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_SET_TX_PARAMS, true, buff, sizeof(buff))); +} + +int16_t LR11x0::setPacketAdrs(uint8_t node, uint8_t broadcast) { + uint8_t buff[2] = { node, broadcast }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_SET_PACKET_ADRS, true, buff, sizeof(buff))); +} + +int16_t LR11x0::setRxTxFallbackMode(uint8_t mode) { + uint8_t buff[1] = { mode }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_SET_RX_TX_FALLBACK_MODE, true, buff, sizeof(buff))); +} + +int16_t LR11x0::setRxDutyCycle(uint32_t rxPeriod, uint32_t sleepPeriod, uint8_t mode) { + uint8_t buff[7] = { + (uint8_t)((rxPeriod >> 16) & 0xFF), (uint8_t)((rxPeriod >> 8) & 0xFF), (uint8_t)(rxPeriod & 0xFF), + (uint8_t)((sleepPeriod >> 16) & 0xFF), (uint8_t)((sleepPeriod >> 8) & 0xFF), (uint8_t)(sleepPeriod & 0xFF), + mode + }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_SET_RX_DUTY_CYCLE, true, buff, sizeof(buff))); +} + +int16_t LR11x0::setPaConfig(uint8_t paSel, uint8_t regPaSupply, uint8_t paDutyCycle, uint8_t paHpSel) { + uint8_t buff[4] = { paSel, regPaSupply, paDutyCycle, paHpSel }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_SET_PA_CONFIG, true, buff, sizeof(buff))); +} + +int16_t LR11x0::stopTimeoutOnPreamble(bool stop) { + uint8_t buff[1] = { (uint8_t)stop }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_STOP_TIMEOUT_ON_PREAMBLE, true, buff, sizeof(buff))); +} + +int16_t LR11x0::setCad(void) { + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_SET_CAD, true, NULL, 0)); +} + +int16_t LR11x0::setTxCw(void) { + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_SET_TX_CW, true, NULL, 0)); +} + +int16_t LR11x0::setTxInfinitePreamble(void) { + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_SET_TX_INFINITE_PREAMBLE, true, NULL, 0)); +} + +int16_t LR11x0::setLoRaSynchTimeout(uint8_t symbolNum) { + uint8_t buff[1] = { symbolNum }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_SET_LORA_SYNCH_TIMEOUT, true, buff, sizeof(buff))); +} + +int16_t LR11x0::setRangingAddr(uint32_t addr, uint8_t checkLen) { + uint8_t buff[5] = { + (uint8_t)((addr >> 24) & 0xFF), (uint8_t)((addr >> 16) & 0xFF), + (uint8_t)((addr >> 8) & 0xFF), (uint8_t)(addr & 0xFF), checkLen + }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_SET_RANGING_ADDR, true, buff, sizeof(buff))); +} + +int16_t LR11x0::setRangingReqAddr(uint32_t addr) { + uint8_t buff[4] = { + (uint8_t)((addr >> 24) & 0xFF), (uint8_t)((addr >> 16) & 0xFF), + (uint8_t)((addr >> 8) & 0xFF), (uint8_t)(addr & 0xFF) + }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_SET_RANGING_REQ_ADDR, true, buff, sizeof(buff))); +} + +int16_t LR11x0::getRangingResult(uint8_t type, float* res) { + uint8_t reqBuff[1] = { type }; + uint8_t rplBuff[4] = { 0 }; + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GET_RANGING_RESULT, false, rplBuff, sizeof(rplBuff), reqBuff, sizeof(reqBuff)); + RADIOLIB_ASSERT(state); + + if(res) { + if(type == RADIOLIB_LR11X0_RANGING_RESULT_DISTANCE) { + uint32_t raw = ((uint32_t)(rplBuff[0]) << 24) | ((uint32_t)(rplBuff[1]) << 16) | ((uint32_t)(rplBuff[2]) << 8) | (uint32_t)rplBuff[3]; + *res = ((float)(raw*3e8))/((float)(4096*this->bandwidthKhz*1000)); + } else { + *res = (float)rplBuff[3]/2.0f; + } + } + + return(state); +} + +int16_t LR11x0::setRangingTxRxDelay(uint32_t delay) { + uint8_t buff[4] = { + (uint8_t)((delay >> 24) & 0xFF), (uint8_t)((delay >> 16) & 0xFF), + (uint8_t)((delay >> 8) & 0xFF), (uint8_t)(delay & 0xFF) + }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_SET_RANGING_TX_RX_DELAY, true, buff, sizeof(buff))); +} + +int16_t LR11x0::setGfskCrcParams(uint32_t init, uint32_t poly) { + uint8_t buff[8] = { + (uint8_t)((init >> 24) & 0xFF), (uint8_t)((init >> 16) & 0xFF), + (uint8_t)((init >> 8) & 0xFF), (uint8_t)(init & 0xFF), + (uint8_t)((poly >> 24) & 0xFF), (uint8_t)((poly >> 16) & 0xFF), + (uint8_t)((poly >> 8) & 0xFF), (uint8_t)(poly & 0xFF) + }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_SET_GFSK_CRC_PARAMS, true, buff, sizeof(buff))); + +} + +int16_t LR11x0::setGfskWhitParams(uint16_t seed) { + uint8_t buff[2] = { + (uint8_t)((seed >> 8) & 0xFF), (uint8_t)(seed & 0xFF) + }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_SET_GFSK_WHIT_PARAMS, true, buff, sizeof(buff))); +} + +int16_t LR11x0::setRangingParameter(uint8_t symbolNum) { + // the first byte is reserved + uint8_t buff[2] = { 0x00, symbolNum }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_SET_RANGING_PARAMETER, true, buff, sizeof(buff))); +} + +int16_t LR11x0::setRssiCalibration(const int8_t* tune, int16_t gainOffset) { + uint8_t buff[11] = { + (uint8_t)((tune[0] & 0x0F) | (uint8_t)(tune[1] & 0x0F) << 4), + (uint8_t)((tune[2] & 0x0F) | (uint8_t)(tune[3] & 0x0F) << 4), + (uint8_t)((tune[4] & 0x0F) | (uint8_t)(tune[5] & 0x0F) << 4), + (uint8_t)((tune[6] & 0x0F) | (uint8_t)(tune[7] & 0x0F) << 4), + (uint8_t)((tune[8] & 0x0F) | (uint8_t)(tune[9] & 0x0F) << 4), + (uint8_t)((tune[10] & 0x0F) | (uint8_t)(tune[11] & 0x0F) << 4), + (uint8_t)((tune[12] & 0x0F) | (uint8_t)(tune[13] & 0x0F) << 4), + (uint8_t)((tune[14] & 0x0F) | (uint8_t)(tune[15] & 0x0F) << 4), + (uint8_t)((tune[16] & 0x0F) | (uint8_t)(tune[17] & 0x0F) << 4), + (uint8_t)(((uint16_t)gainOffset >> 8) & 0xFF), (uint8_t)(gainOffset & 0xFF), + }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_SET_RSSI_CALIBRATION, true, buff, sizeof(buff))); +} + +int16_t LR11x0::setLoRaSyncWord(uint8_t sync) { + uint8_t buff[1] = { sync }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_SET_LORA_SYNC_WORD, true, buff, sizeof(buff))); +} + +int16_t LR11x0::lrFhssBuildFrame(uint8_t hdrCount, uint8_t cr, uint8_t grid, bool hop, uint8_t bw, uint16_t hopSeq, int8_t devOffset, uint8_t* payload, size_t len) { + // check maximum size + const uint8_t maxLen[4][4] = { + { 189, 178, 167, 155, }, + { 151, 142, 133, 123, }, + { 112, 105, 99, 92, }, + { 74, 69, 65, 60, }, + }; + if((cr > RADIOLIB_LR11X0_LR_FHSS_CR_1_3) || ((hdrCount - 1) > (int)sizeof(maxLen[0])) || (len > maxLen[cr][hdrCount - 1])) { + return(RADIOLIB_ERR_SPI_CMD_INVALID); + } + + // build buffers + size_t buffLen = 9 + len; + #if RADIOLIB_STATIC_ONLY + uint8_t dataBuff[9 + 190]; + #else + uint8_t* dataBuff = new uint8_t[buffLen]; + #endif + + // set properties of the packet + dataBuff[0] = hdrCount; + dataBuff[1] = cr; + dataBuff[2] = RADIOLIB_LR11X0_LR_FHSS_MOD_TYPE_GMSK; + dataBuff[3] = grid; + dataBuff[4] = (uint8_t)hop; + dataBuff[5] = bw; + dataBuff[6] = (uint8_t)((hopSeq >> 8) & 0x01); + dataBuff[7] = (uint8_t)(hopSeq & 0xFF); + dataBuff[8] = devOffset; + memcpy(&dataBuff[9], payload, len); + + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_LR_FHSS_BUILD_FRAME, true, dataBuff, buffLen); + #if !RADIOLIB_STATIC_ONLY + delete[] dataBuff; + #endif + return(state); +} + +int16_t LR11x0::lrFhssSetSyncWord(uint32_t sync) { + uint8_t buff[4] = { + (uint8_t)((sync >> 24) & 0xFF), (uint8_t)((sync >> 16) & 0xFF), + (uint8_t)((sync >> 8) & 0xFF), (uint8_t)(sync & 0xFF) + }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_LR_FHSS_SET_SYNC_WORD, true, buff, sizeof(buff))); +} + +int16_t LR11x0::configBleBeacon(uint8_t chan, uint8_t* payload, size_t len) { + return(this->bleBeaconCommon(RADIOLIB_LR11X0_CMD_CONFIG_BLE_BEACON, chan, payload, len)); +} + +int16_t LR11x0::getLoRaRxHeaderInfos(uint8_t* info) { + uint8_t buff[1] = { 0 }; + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GET_LORA_RX_HEADER_INFOS, false, buff, sizeof(buff)); + + // pass the replies + if(info) { *info = buff[0]; } + + return(state); +} + +int16_t LR11x0::bleBeaconSend(uint8_t chan, uint8_t* payload, size_t len) { + return(this->bleBeaconCommon(RADIOLIB_LR11X0_CMD_BLE_BEACON_SEND, chan, payload, len)); +} + +int16_t LR11x0::bleBeaconCommon(uint16_t cmd, uint8_t chan, uint8_t* payload, size_t len) { + // check maximum size + // TODO what is the actual maximum? + if(len > RADIOLIB_LR11X0_SPI_MAX_READ_WRITE_LEN) { + return(RADIOLIB_ERR_SPI_CMD_INVALID); + } + + // build buffers + #if RADIOLIB_STATIC_ONLY + uint8_t dataBuff[sizeof(uint8_t) + RADIOLIB_LR11X0_SPI_MAX_READ_WRITE_LEN]; + #else + uint8_t* dataBuff = new uint8_t[sizeof(uint8_t) + len]; + #endif + + // set the channel + dataBuff[0] = chan; + memcpy(&dataBuff[1], payload, len); + + int16_t state = this->SPIcommand(cmd, true, dataBuff, sizeof(uint8_t) + len); + #if !RADIOLIB_STATIC_ONLY + delete[] dataBuff; + #endif + return(state); +} + +int16_t LR11x0::wifiScan(uint8_t type, uint16_t mask, uint8_t acqMode, uint8_t nbMaxRes, uint8_t nbScanPerChan, uint16_t timeout, uint8_t abortOnTimeout) { + uint8_t buff[9] = { + type, (uint8_t)((mask >> 8) & 0xFF), (uint8_t)(mask & 0xFF), + acqMode, nbMaxRes, nbScanPerChan, + (uint8_t)((timeout >> 8) & 0xFF), (uint8_t)(timeout & 0xFF), + abortOnTimeout + }; + + // call the SPI write stream directly to skip waiting for BUSY - it will be set to high once the scan starts + return(this->mod->SPIwriteStream(RADIOLIB_LR11X0_CMD_WIFI_SCAN, buff, sizeof(buff), false, false)); +} + +int16_t LR11x0::wifiScanTimeLimit(uint8_t type, uint16_t mask, uint8_t acqMode, uint8_t nbMaxRes, uint16_t timePerChan, uint16_t timeout) { + uint8_t buff[9] = { + type, (uint8_t)((mask >> 8) & 0xFF), (uint8_t)(mask & 0xFF), + acqMode, nbMaxRes, + (uint8_t)((timePerChan >> 8) & 0xFF), (uint8_t)(timePerChan & 0xFF), + (uint8_t)((timeout >> 8) & 0xFF), (uint8_t)(timeout & 0xFF) + }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_WIFI_SCAN_TIME_LIMIT, true, buff, sizeof(buff))); +} + +int16_t LR11x0::wifiCountryCode(uint16_t mask, uint8_t nbMaxRes, uint8_t nbScanPerChan, uint16_t timeout, uint8_t abortOnTimeout) { + uint8_t buff[7] = { + (uint8_t)((mask >> 8) & 0xFF), (uint8_t)(mask & 0xFF), + nbMaxRes, nbScanPerChan, + (uint8_t)((timeout >> 8) & 0xFF), (uint8_t)(timeout & 0xFF), + abortOnTimeout + }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_WIFI_COUNTRY_CODE, true, buff, sizeof(buff))); +} + +int16_t LR11x0::wifiCountryCodeTimeLimit(uint16_t mask, uint8_t nbMaxRes, uint16_t timePerChan, uint16_t timeout) { + uint8_t buff[7] = { + (uint8_t)((mask >> 8) & 0xFF), (uint8_t)(mask & 0xFF), + nbMaxRes, + (uint8_t)((timePerChan >> 8) & 0xFF), (uint8_t)(timePerChan & 0xFF), + (uint8_t)((timeout >> 8) & 0xFF), (uint8_t)(timeout & 0xFF) + }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_WIFI_COUNTRY_CODE_TIME_LIMIT, true, buff, sizeof(buff))); +} + +int16_t LR11x0::wifiReadResults(uint8_t index, uint8_t nbResults, uint8_t format, uint8_t* results) { + uint8_t buff[3] = { index, nbResults, format }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_WIFI_READ_RESULTS, false, results, RADIOLIB_LR11X0_WIFI_RESULT_MAX_LEN, buff, sizeof(buff))); +} + +int16_t LR11x0::wifiResetCumulTimings(void) { + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_WIFI_RESET_CUMUL_TIMINGS, true, NULL, 0)); +} + +int16_t LR11x0::wifiReadCumulTimings(uint32_t* detection, uint32_t* capture, uint32_t* demodulation) { + uint8_t buff[16] = { 0 }; + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_WIFI_READ_CUMUL_TIMINGS, false, buff, sizeof(buff)); + + // pass the replies + if(detection) { *detection = ((uint32_t)(buff[4]) << 24) | ((uint32_t)(buff[5]) << 16) | ((uint32_t)(buff[6]) << 8) | (uint32_t)buff[7]; } + if(capture) { *capture = ((uint32_t)(buff[8]) << 24) | ((uint32_t)(buff[9]) << 16) | ((uint32_t)(buff[10]) << 8) | (uint32_t)buff[11]; } + if(demodulation) { *demodulation = ((uint32_t)(buff[12]) << 24) | ((uint32_t)(buff[13]) << 16) | ((uint32_t)(buff[14]) << 8) | (uint32_t)buff[15]; } + + return(state); +} + +int16_t LR11x0::wifiGetNbCountryCodeResults(uint8_t* nbResults) { + uint8_t buff[1] = { 0 }; + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_WIFI_GET_NB_COUNTRY_CODE_RESULTS, false, buff, sizeof(buff)); + + // pass the replies + if(nbResults) { *nbResults = buff[0]; } + + return(state); +} + +int16_t LR11x0::wifiReadCountryCodeResults(uint8_t index, uint8_t nbResults, uint8_t* results) { + uint8_t reqBuff[2] = { index, nbResults }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_WIFI_READ_COUNTRY_CODE_RESULTS, false, results, nbResults, reqBuff, sizeof(reqBuff))); +} + +int16_t LR11x0::wifiCfgTimestampAPphone(uint32_t timestamp) { + uint8_t buff[4] = { + (uint8_t)((timestamp >> 24) & 0xFF), (uint8_t)((timestamp >> 16) & 0xFF), + (uint8_t)((timestamp >> 8) & 0xFF), (uint8_t)(timestamp & 0xFF) + }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_WIFI_COUNTRY_CODE_TIME_LIMIT, true, buff, sizeof(buff))); +} + +int16_t LR11x0::wifiReadVersion(uint8_t* major, uint8_t* minor) { + uint8_t buff[2] = { 0 }; + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_WIFI_READ_VERSION, false, buff, sizeof(buff)); + + // pass the replies + if(major) { *major = buff[0]; } + if(minor) { *minor = buff[1]; } + + return(state); +} + +int16_t LR11x0::gnssReadRssi(int8_t* rssi) { + uint8_t reqBuff[1] = { 0x09 }; // some undocumented magic byte, from the official driver + uint8_t rplBuff[2] = { 0 }; + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_READ_RSSI, false, rplBuff, sizeof(rplBuff), reqBuff, sizeof(reqBuff)); + RADIOLIB_ASSERT(state); + if(rssi) { *rssi = rplBuff[1]; } + return(state); +} + +int16_t LR11x0::gnssSetConstellationToUse(uint8_t mask) { + uint8_t buff[1] = { mask }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_SET_CONSTELLATION_TO_USE, true, buff, sizeof(buff))); +} + +int16_t LR11x0::gnssReadConstellationToUse(uint8_t* mask) { + uint8_t buff[1] = { 0 }; + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_READ_CONSTELLATION_TO_USE, false, buff, sizeof(buff)); + + // pass the replies + if(mask) { *mask = buff[0]; } + + return(state); +} + +int16_t LR11x0::gnssSetAlmanacUpdate(uint8_t mask) { + uint8_t buff[1] = { mask }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_SET_ALMANAC_UPDATE, true, buff, sizeof(buff))); +} + +int16_t LR11x0::gnssReadAlmanacUpdate(uint8_t* mask) { + uint8_t buff[1] = { 0 }; + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_READ_ALMANAC_UPDATE, false, buff, sizeof(buff)); + + // pass the replies + if(mask) { *mask = buff[0]; } + + return(state); +} + +int16_t LR11x0::gnssSetFreqSearchSpace(uint8_t freq) { + uint8_t buff[1] = { freq }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_SET_FREQ_SEARCH_SPACE, true, buff, sizeof(buff))); +} + +int16_t LR11x0::gnssReadFreqSearchSpace(uint8_t* freq) { + uint8_t buff[1] = { 0 }; + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_READ_FREQ_SEARCH_SPACE, false, buff, sizeof(buff)); + if(freq) { *freq = buff[0]; } + return(state); +} + +int16_t LR11x0::gnssReadVersion(uint8_t* fw, uint8_t* almanac) { + uint8_t buff[2] = { 0 }; + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_READ_VERSION, false, buff, sizeof(buff)); + + // pass the replies + if(fw) { *fw = buff[0]; } + if(almanac) { *almanac = buff[1]; } + + return(state); +} + +int16_t LR11x0::gnssReadSupportedConstellations(uint8_t* mask) { + uint8_t buff[1] = { 0 }; + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_READ_SUPPORTED_CONSTELLATIONS, false, buff, sizeof(buff)); + + // pass the replies + if(mask) { *mask = buff[0]; } + + return(state); +} + +int16_t LR11x0::gnssSetMode(uint8_t mode) { + uint8_t buff[1] = { mode }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_SET_MODE, true, buff, sizeof(buff))); +} + +int16_t LR11x0::gnssAutonomous(uint32_t gpsTime, uint8_t resMask, uint8_t nbSvMask) { + uint8_t buff[7] = { + (uint8_t)((gpsTime >> 24) & 0xFF), (uint8_t)((gpsTime >> 16) & 0xFF), + (uint8_t)((gpsTime >> 8) & 0xFF), (uint8_t)(gpsTime & 0xFF), + RADIOLIB_LR11X0_GNSS_AUTO_EFFORT_MODE, resMask, nbSvMask + }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_AUTONOMOUS, true, buff, sizeof(buff))); +} + +int16_t LR11x0::gnssAssisted(uint32_t gpsTime, uint8_t effort, uint8_t resMask, uint8_t nbSvMask) { + uint8_t buff[7] = { + (uint8_t)((gpsTime >> 24) & 0xFF), (uint8_t)((gpsTime >> 16) & 0xFF), + (uint8_t)((gpsTime >> 8) & 0xFF), (uint8_t)(gpsTime & 0xFF), + effort, resMask, nbSvMask + }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_ASSISTED, true, buff, sizeof(buff))); +} + +int16_t LR11x0::gnssSetAssistancePosition(float lat, float lon) { + uint16_t latRaw = (lat*2048.0f)/90.0f + 0.5f; + uint16_t lonRaw = (lon*2048.0f)/180.0f + 0.5f; + uint8_t buff[4] = { + (uint8_t)((latRaw >> 8) & 0xFF), (uint8_t)(latRaw & 0xFF), + (uint8_t)((lonRaw >> 8) & 0xFF), (uint8_t)(lonRaw & 0xFF), + }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_SET_ASSISTANCE_POSITION, true, buff, sizeof(buff))); +} + +int16_t LR11x0::gnssReadAssistancePosition(float* lat, float* lon) { + uint8_t buff[4] = { 0 }; + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_READ_ASSISTANCE_POSITION, false, buff, sizeof(buff)); + + // pass the replies + if(lat) { + uint16_t latRaw = ((uint16_t)(buff[0]) << 8) | (uint16_t)(buff[1]); + *lat = ((float)latRaw*90.0f)/2048.0f; + } + if(lon) { + uint16_t lonRaw = ((uint16_t)(buff[2]) << 8) | (uint16_t)(buff[3]); + *lon = ((float)lonRaw*180.0f)/2048.0f; + } + + return(state); +} + +int16_t LR11x0::gnssPushSolverMsg(uint8_t* payload, size_t len) { + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_PUSH_SOLVER_MSG, true, payload, len)); +} + +int16_t LR11x0::gnssPushDmMsg(uint8_t* payload, size_t len) { + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_PUSH_DM_MSG, true, payload, len)); +} + +int16_t LR11x0::gnssGetContextStatus(uint8_t* fwVersion, uint32_t* almanacCrc, uint8_t* errCode, uint8_t* almUpdMask, uint8_t* freqSpace) { + // send the command - datasheet here shows extra bytes being sent in the request + // but doing that fails so treat it like any other read command + uint8_t buff[9] = { 0 }; + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_GET_CONTEXT_STATUS, false, buff, sizeof(buff)); + + // pass the replies + if(fwVersion) { *fwVersion = buff[2]; } + if(almanacCrc) { *almanacCrc = ((uint32_t)(buff[3]) << 24) | ((uint32_t)(buff[4]) << 16) | ((uint32_t)(buff[5]) << 8) | (uint32_t)buff[6]; } + if(errCode) { *errCode = (buff[7] & 0xF0) >> 4; } + if(almUpdMask) { *almUpdMask = (buff[7] & 0x0E) >> 1; } + if(freqSpace) { *freqSpace = ((buff[7] & 0x01) << 1) | ((buff[8] & 0x80) >> 7); } + + return(state); +} + +int16_t LR11x0::gnssGetNbSvDetected(uint8_t* nbSv) { + uint8_t buff[1] = { 0 }; + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_GET_NB_SV_DETECTED, false, buff, sizeof(buff)); + + // pass the replies + if(nbSv) { *nbSv = buff[0]; } + + return(state); +} + +int16_t LR11x0::gnssGetSvDetected(uint8_t* svId, uint8_t* snr, int16_t* doppler, size_t nbSv) { + // TODO this is arbitrary - is there an actual maximum? + if(nbSv > RADIOLIB_LR11X0_SPI_MAX_READ_WRITE_LEN/sizeof(uint32_t)) { + return(RADIOLIB_ERR_SPI_CMD_INVALID); + } + + // build buffers + size_t buffLen = nbSv*sizeof(uint32_t); + #if RADIOLIB_STATIC_ONLY + uint8_t dataBuff[RADIOLIB_LR11X0_SPI_MAX_READ_WRITE_LEN]; + #else + uint8_t* dataBuff = new uint8_t[buffLen]; + #endif + + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_GET_SV_DETECTED, false, dataBuff, buffLen); + if(state == RADIOLIB_ERR_NONE) { + for(size_t i = 0; i < nbSv; i++) { + if(svId) { svId[i] = dataBuff[4*i]; } + if(snr) { snr[i] = dataBuff[4*i + 1]; } + if(doppler) { doppler[i] = ((uint16_t)(dataBuff[4*i + 2]) << 8) | (uint16_t)dataBuff[4*i + 3]; } + } + } + + #if !RADIOLIB_STATIC_ONLY + delete[] dataBuff; + #endif + return(state); +} + +int16_t LR11x0::gnssGetConsumption(uint32_t* cpu, uint32_t* radio) { + uint8_t buff[8] = { 0 }; + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_GET_CONSUMPTION, false, buff, sizeof(buff)); + + // pass the replies + if(cpu) { *cpu = ((uint32_t)(buff[0]) << 24) | ((uint32_t)(buff[1]) << 16) | ((uint32_t)(buff[2]) << 8) | (uint32_t)buff[3]; } + if(radio) { *radio = ((uint32_t)(buff[4]) << 24) | ((uint32_t)(buff[5]) << 16) | ((uint32_t)(buff[6]) << 8) | (uint32_t)buff[7]; } + + return(state); +} + +int16_t LR11x0::gnssGetResultSize(uint16_t* size) { + uint8_t buff[2] = { 0 }; + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_GET_RESULT_SIZE, false, buff, sizeof(buff)); + + // pass the replies + if(size) { *size = ((uint16_t)(buff[0]) << 8) | (uint16_t)buff[1]; } + + return(state); +} + +int16_t LR11x0::gnssReadResults(uint8_t* result, uint16_t size) { + RADIOLIB_ASSERT_PTR(result); + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_READ_RESULTS, false, result, size)); +} + +int16_t LR11x0::gnssAlmanacFullUpdateHeader(uint16_t date, uint32_t globalCrc) { + uint8_t buff[RADIOLIB_LR11X0_GNSS_ALMANAC_BLOCK_SIZE] = { + RADIOLIB_LR11X0_GNSS_ALMANAC_HEADER_ID, + (uint8_t)((date >> 8) & 0xFF), (uint8_t)(date & 0xFF), + (uint8_t)((globalCrc >> 24) & 0xFF), (uint8_t)((globalCrc >> 16) & 0xFF), + (uint8_t)((globalCrc >> 8) & 0xFF), (uint8_t)(globalCrc & 0xFF), + }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_ALMANAC_FULL_UPDATE, true, buff, sizeof(buff))); +} + +int16_t LR11x0::gnssAlmanacFullUpdateSV(uint8_t svn, uint8_t* svnAlmanac) { + uint8_t buff[RADIOLIB_LR11X0_GNSS_ALMANAC_BLOCK_SIZE] = { svn }; + memcpy(&buff[1], svnAlmanac, RADIOLIB_LR11X0_GNSS_ALMANAC_BLOCK_SIZE - 1); + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_ALMANAC_FULL_UPDATE, true, buff, sizeof(buff))); +} + +int16_t LR11x0::gnssAlmanacReadAddrSize(uint32_t* addr, uint16_t* size) { + uint8_t buff[6] = { 0 }; + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_ALMANAC_READ_ADDR_SIZE, false, buff, sizeof(buff)); + + if(addr) { *addr = ((uint32_t)(buff[0]) << 24) | ((uint32_t)(buff[1]) << 16) | ((uint32_t)(buff[2]) << 8) | (uint32_t)buff[3]; } + if(size) { *size = ((uint16_t)(buff[4]) << 8) | (uint16_t)buff[5]; } + + return(state); +} + +int16_t LR11x0::gnssAlmanacReadSV(uint8_t svId, uint8_t* almanac) { + uint8_t reqBuff[2] = { svId, 0x01 }; // in theory multiple SV entries can be read at the same time, but we don't need that + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_READ_ALMANAC_PER_SATELLITE, false, almanac, 22, reqBuff, sizeof(reqBuff)); + RADIOLIB_ASSERT(state); + return(state); +} + +int16_t LR11x0::gnssGetNbSvVisible(uint32_t time, float lat, float lon, uint8_t constellation, uint8_t* nbSv) { + uint16_t latRaw = (lat*2048.0f)/90.0f + 0.5f; + uint16_t lonRaw = (lon*2048.0f)/180.0f + 0.5f; + uint8_t reqBuff[9] = { + (uint8_t)((time >> 24) & 0xFF), (uint8_t)((time >> 16) & 0xFF), + (uint8_t)((time >> 8) & 0xFF), (uint8_t)(time & 0xFF), + (uint8_t)((latRaw >> 8) & 0xFF), (uint8_t)(latRaw & 0xFF), + (uint8_t)((lonRaw >> 8) & 0xFF), (uint8_t)(lonRaw & 0xFF), + constellation, + }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_GET_SV_VISIBLE, false, nbSv, 1, reqBuff, sizeof(reqBuff))); +} + +int16_t LR11x0::gnssGetSvVisible(uint8_t nbSv, uint8_t** svId, int16_t** doppler, int16_t** dopplerErr) { + // enforce a maximum of 12 SVs + if(nbSv > 12) { + return(RADIOLIB_ERR_SPI_CMD_INVALID); + } + + uint8_t buff[60] = { 0 }; + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_GET_SV_VISIBLE_DOPPLER, false, buff, sizeof(buff)); + for(uint8_t i = 0; i < nbSv; i++) { + if(svId && svId[i]) { *svId[i] = buff[i*12]; } + if(doppler && doppler[i]) { *doppler[i] = ((uint16_t)(buff[i*12 + 1]) << 8) | (uint16_t)buff[i*12 + 2]; } + if(dopplerErr && dopplerErr[i]) { *dopplerErr[i] = ((uint16_t)(buff[i*12 + 3]) << 8) | (uint16_t)buff[i*12 + 4]; } + } + + return(state); +} + +int16_t LR11x0::gnssPerformScan(uint8_t effort, uint8_t resMask, uint8_t nbSvMax) { + uint8_t buff[3] = { effort, resMask, nbSvMax }; + // call the SPI write stream directly to skip waiting for BUSY - it will be set to high once the scan starts + return(this->mod->SPIwriteStream(RADIOLIB_LR11X0_CMD_GNSS_SCAN, buff, sizeof(buff), false, false)); +} + +int16_t LR11x0::gnssReadLastScanModeLaunched(uint8_t* lastScanMode) { + uint8_t buff[1] = { 0 }; + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_READ_LAST_SCAN_MODE_LAUNCHED, false, buff, sizeof(buff)); + + // pass the replies + if(lastScanMode) { *lastScanMode = buff[0]; } + + return(state); +} + +int16_t LR11x0::gnssFetchTime(uint8_t effort, uint8_t opt) { + uint8_t buff[2] = { effort, opt }; + // call the SPI write stream directly to skip waiting for BUSY - it will be set to high once the scan starts + return(this->mod->SPIwriteStream(RADIOLIB_LR11X0_CMD_GNSS_FETCH_TIME, buff, sizeof(buff), false, false)); +} + +int16_t LR11x0::gnssReadTime(uint8_t* err, uint32_t* time, uint32_t* nbUs, uint32_t* timeAccuracy) { + uint8_t buff[12] = { 0 }; + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_READ_TIME, false, buff, sizeof(buff)); + + // pass the replies + if(err) { *err = buff[0]; } + + if(time) { + *time = ((uint32_t)(buff[1]) << 24) | ((uint32_t)(buff[2]) << 16) | ((uint32_t)(buff[3]) << 8) | (uint32_t)buff[4]; + *time += 2UL*1024UL*7UL*24UL*3600UL; // assume WN rollover is at 2, this will fail sometime in 2038 + *time += 315964800UL; // convert to UTC + } + + if(nbUs) { + *nbUs = ((uint32_t)(buff[5]) << 16) | ((uint32_t)(buff[6]) << 8) | (uint32_t)buff[7]; + *nbUs /= 16; + } + + if(timeAccuracy) { + *timeAccuracy = ((uint32_t)(buff[8]) << 24) | ((uint32_t)(buff[9]) << 16) | ((uint32_t)(buff[10]) << 8) | (uint32_t)buff[11]; + *timeAccuracy /= 16; + } + + return(state); +} + +int16_t LR11x0::gnssResetTime(void) { + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_RESET_TIME, true, NULL, 0)); +} + +int16_t LR11x0::gnssResetPosition(void) { + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_RESET_POSITION, true, NULL, 0)); +} + +int16_t LR11x0::gnssReadWeekNumberRollover(uint8_t* status, uint8_t* rollover) { + uint8_t buff[2] = { 0 }; + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_READ_WEEK_NUMBER_ROLLOWER, false, buff, sizeof(buff)); + if(status) { *status = buff[0]; } + if(rollover) { *rollover = buff[1]; } + return(state); +} + +int16_t LR11x0::gnssReadDemodStatus(int8_t* status, uint8_t* info) { + uint8_t buff[2] = { 0 }; + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_READ_DEMOD_STATUS, false, buff, sizeof(buff)); + + // pass the replies + if(status) { *status = (int8_t)buff[0]; } + if(info) { *info = buff[1]; } + + return(state); +} + +int16_t LR11x0::gnssReadCumulTiming(uint32_t* timing, uint8_t* constDemod) { + uint8_t rplBuff[125] = { 0 }; + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_READ_CUMUL_TIMING, false, rplBuff, 125); + RADIOLIB_ASSERT(state); + + // convert endians + if(timing) { + for(size_t i = 0; i < 31; i++) { + timing[i] = ((uint32_t)rplBuff[i*sizeof(uint32_t)] << 24) | ((uint32_t)rplBuff[1 + i*sizeof(uint32_t)] << 16) | ((uint32_t)rplBuff[2 + i*sizeof(uint32_t)] << 8) | (uint32_t)rplBuff[3 + i*sizeof(uint32_t)]; + } + } + + if(constDemod) { *constDemod = rplBuff[124]; } + + return(state); +} + +int16_t LR11x0::gnssSetTime(uint32_t time, uint16_t accuracy) { + uint8_t buff[6] = { + (uint8_t)((time >> 24) & 0xFF), (uint8_t)((time >> 16) & 0xFF), + (uint8_t)((time >> 8) & 0xFF), (uint8_t)(time & 0xFF), + (uint8_t)((accuracy >> 8) & 0xFF), (uint8_t)(accuracy & 0xFF), + }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_SET_TIME, true, buff, sizeof(buff))); +} + +int16_t LR11x0::gnssReadDopplerSolverRes(uint8_t* error, uint8_t* nbSvUsed, float* lat, float* lon, uint16_t* accuracy, uint16_t* xtal, float* latFilt, float* lonFilt, uint16_t* accuracyFilt, uint16_t* xtalFilt) { + uint8_t buff[18] = { 0 }; + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_READ_DOPPLER_SOLVER_RES, false, buff, sizeof(buff)); + + // pass the replies + if(error) { *error = buff[0]; } + if(nbSvUsed) { *nbSvUsed = buff[1]; } + if(lat) { + uint16_t latRaw = ((uint16_t)(buff[2]) << 8) | (uint16_t)buff[3]; + *lat = ((float)latRaw * 90.0f)/2048.0f; + } + if(lon) { + uint16_t lonRaw = ((uint16_t)(buff[4]) << 8) | (uint16_t)buff[5]; + *lon = ((float)lonRaw * 180.0f)/2048.0f; + } + if(accuracy) { *accuracy = ((uint16_t)(buff[6]) << 8) | (uint16_t)buff[7]; } + if(xtal) { *xtal = ((uint16_t)(buff[8]) << 8) | (uint16_t)buff[9]; } + if(latFilt) { + uint16_t latRaw = ((uint16_t)(buff[10]) << 8) | (uint16_t)buff[11]; + *latFilt = ((float)latRaw * 90.0f)/2048.0f; + } + if(lonFilt) { + uint16_t lonRaw = ((uint16_t)(buff[12]) << 8) | (uint16_t)buff[13]; + *lonFilt = ((float)lonRaw * 180.0f)/2048.0f; + } + if(accuracyFilt) { *accuracyFilt = ((uint16_t)(buff[14]) << 8) | (uint16_t)buff[15]; } + if(xtalFilt) { *xtalFilt = ((uint16_t)(buff[16]) << 8) | (uint16_t)buff[17]; } + + return(state); +} + +int16_t LR11x0::gnssReadDelayResetAP(uint32_t* delay) { + uint8_t buff[3] = { 0 }; + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_READ_DELAY_RESET_AP, false, buff, sizeof(buff)); + + if(delay) { *delay = ((uint32_t)(buff[0]) << 16) | ((uint32_t)(buff[1]) << 8) | (uint32_t)buff[2]; } + + return(state); +} + +int16_t LR11x0::gnssAlmanacUpdateFromSat(uint8_t effort, uint8_t bitMask) { + uint8_t buff[2] = { effort, bitMask }; + // call the SPI write stream directly to skip waiting for BUSY - it will be set to high once the scan starts + return(this->mod->SPIwriteStream(RADIOLIB_LR11X0_CMD_GNSS_ALMANAC_UPDATE_FROM_SAT, buff, sizeof(buff), false, false)); +} + +int16_t LR11x0::gnssReadKeepSyncStatus(uint8_t mask, uint8_t* nbSvVisible, uint32_t* elapsed) { + uint8_t reqBuff[1] = { mask }; + uint8_t rplBuff[5] = { 0 }; + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_READ_KEEP_SYNC_STATUS, false, rplBuff, sizeof(rplBuff), reqBuff, sizeof(reqBuff)); + RADIOLIB_ASSERT(state); + if(nbSvVisible) { *nbSvVisible = rplBuff[0]; } + if(elapsed) { *elapsed = ((uint32_t)(rplBuff[1]) << 24) | ((uint32_t)(rplBuff[2]) << 16) | ((uint32_t)(rplBuff[3]) << 8) | (uint32_t)rplBuff[4]; } + return(state); +} + +int16_t LR11x0::gnssReadAlmanacStatus(uint8_t* status) { + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_READ_ALMANAC_STATUS, false, status, 53)); +} + +int16_t LR11x0::gnssConfigAlmanacUpdatePeriod(uint8_t bitMask, uint8_t svType, uint16_t period) { + uint8_t buff[4] = { bitMask, svType, (uint8_t)((period >> 8) & 0xFF), (uint8_t)(period & 0xFF) }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_CONFIG_ALMANAC_UPDATE_PERIOD, true, buff, sizeof(buff))); +} + +int16_t LR11x0::gnssReadAlmanacUpdatePeriod(uint8_t bitMask, uint8_t svType, uint16_t* period) { + uint8_t reqBuff[2] = { bitMask, svType }; + uint8_t rplBuff[2] = { 0 }; + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_READ_ALMANAC_UPDATE_PERIOD, false, rplBuff, sizeof(rplBuff), reqBuff, sizeof(reqBuff)); + RADIOLIB_ASSERT(state); + + if(period) { *period = ((uint16_t)(rplBuff[0]) << 8) | (uint16_t)rplBuff[1]; } + + return(state); +} + +int16_t LR11x0::gnssConfigDelayResetAP(uint32_t delay) { + uint8_t buff[3] = { (uint8_t)((delay >> 16) & 0xFF), (uint8_t)((delay >> 8) & 0xFF), (uint8_t)(delay & 0xFF) }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_CONFIG_DELAY_RESET_AP, true, buff, sizeof(buff))); +} + +int16_t LR11x0::gnssGetSvWarmStart(uint8_t bitMask, uint8_t* sv, uint8_t nbVisSat) { + uint8_t reqBuff[1] = { bitMask }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_GET_SV_WARM_START, false, sv, nbVisSat, reqBuff, sizeof(reqBuff))); +} + +int16_t LR11x0::gnssGetSvSync(uint8_t mask, uint8_t nbSv, uint8_t* syncList) { + uint8_t reqBuff[2] = { mask, nbSv }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_GET_SV_SYNC, false, syncList, nbSv, reqBuff, sizeof(reqBuff))); +} + +int16_t LR11x0::gnssReadWarmStartStatus(uint8_t bitMask, uint8_t* nbVisSat, uint32_t* timeElapsed) { + uint8_t reqBuff[1] = { bitMask }; + uint8_t rplBuff[5] = { 0 }; + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_READ_WARM_START_STATUS, false, rplBuff, sizeof(rplBuff), reqBuff, sizeof(reqBuff)); + RADIOLIB_ASSERT(state); + + if(nbVisSat) { *nbVisSat = rplBuff[0]; } + if(timeElapsed) { *timeElapsed = ((uint32_t)(rplBuff[1]) << 24) | ((uint32_t)(rplBuff[2]) << 16) | ((uint32_t)(rplBuff[3]) << 8) | (uint32_t)rplBuff[4]; } + + return(state); +} + +int16_t LR11x0::gnssWriteBitMaskSatActivated(uint8_t bitMask, uint32_t* bitMaskActivated0, uint32_t* bitMaskActivated1) { + uint8_t reqBuff[1] = { bitMask }; + uint8_t rplBuff[8] = { 0 }; + size_t rplLen = (bitMask & 0x01) ? 8 : 4; // GPS only has the first bit mask + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_GNSS_WRITE_BIT_MASK_SAT_ACTIVATED, false, rplBuff, rplLen, reqBuff, sizeof(reqBuff)); + RADIOLIB_ASSERT(state); + + if(bitMaskActivated0) { *bitMaskActivated0 = ((uint32_t)(rplBuff[0]) << 24) | ((uint32_t)(rplBuff[1]) << 16) | ((uint32_t)(rplBuff[2]) << 8) | (uint32_t)rplBuff[3]; } + if(bitMaskActivated1) { *bitMaskActivated1 = ((uint32_t)(rplBuff[4]) << 24) | ((uint32_t)(rplBuff[5]) << 16) | ((uint32_t)(rplBuff[6]) << 8) | (uint32_t)rplBuff[7]; } + + return(state); +} + +void LR11x0::gnssAbort() { + // send the abort signal (single NOP) + this->mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_CMD] = Module::BITS_8; + // we need to call the most basic overload of the SPI write method otherwise the call will be ambiguous + uint8_t cmd[2] = { 0, 0 }; + this->mod->SPIwriteStream(cmd, 2, NULL, 0, false, false); + this->mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_CMD] = Module::BITS_16; + + // wait for at least 2.9 seconds as specified by the user manual + this->mod->hal->delay(3000); +} + +int16_t LR11x0::cryptoSetKey(uint8_t keyId, uint8_t* key) { + RADIOLIB_ASSERT_PTR(key); + uint8_t buff[1 + RADIOLIB_AES128_KEY_SIZE] = { 0 }; + buff[0] = keyId; + memcpy(&buff[1], key, RADIOLIB_AES128_KEY_SIZE); + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_CRYPTO_SET_KEY, false, buff, sizeof(buff))); +} + +int16_t LR11x0::cryptoDeriveKey(uint8_t srcKeyId, uint8_t dstKeyId, uint8_t* key) { + RADIOLIB_ASSERT_PTR(key); + uint8_t buff[2 + RADIOLIB_AES128_KEY_SIZE] = { 0 }; + buff[0] = srcKeyId; + buff[1] = dstKeyId; + memcpy(&buff[2], key, RADIOLIB_AES128_KEY_SIZE); + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_CRYPTO_DERIVE_KEY, false, buff, sizeof(buff))); +} + +int16_t LR11x0::cryptoProcessJoinAccept(uint8_t decKeyId, uint8_t verKeyId, uint8_t lwVer, uint8_t* header, uint8_t* dataIn, size_t len, uint8_t* dataOut) { + // calculate buffer sizes + size_t headerLen = 1; + if(lwVer) { + headerLen += 11; // LoRaWAN 1.1 header is 11 bytes longer than 1.0 + } + size_t reqLen = 3*sizeof(uint8_t) + headerLen + len; + size_t rplLen = sizeof(uint8_t) + len; + + // build buffers + #if RADIOLIB_STATIC_ONLY + uint8_t reqBuff[RADIOLIB_LR11X0_SPI_MAX_READ_WRITE_LEN]; + uint8_t rplBuff[RADIOLIB_LR11X0_SPI_MAX_READ_WRITE_LEN]; + #else + uint8_t* reqBuff = new uint8_t[reqLen]; + uint8_t* rplBuff = new uint8_t[rplLen]; + #endif + + // set the request fields + reqBuff[0] = decKeyId; + reqBuff[1] = verKeyId; + reqBuff[2] = lwVer; + memcpy(&reqBuff[3], header, headerLen); + memcpy(&reqBuff[3 + headerLen], dataIn, len); + + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_CRYPTO_PROCESS_JOIN_ACCEPT, false, rplBuff, rplLen, reqBuff, reqLen); + #if !RADIOLIB_STATIC_ONLY + delete[] reqBuff; + #endif + if(state != RADIOLIB_ERR_NONE) { + #if !RADIOLIB_STATIC_ONLY + delete[] rplBuff; + #endif + return(state); + } + + // check the crypto engine state + if(rplBuff[0] != RADIOLIB_LR11X0_CRYPTO_STATUS_SUCCESS) { + RADIOLIB_DEBUG_BASIC_PRINTLN("Crypto Engine error: %02x", rplBuff[0]); + #if !RADIOLIB_STATIC_ONLY + delete[] rplBuff; + #endif + return(RADIOLIB_ERR_SPI_CMD_FAILED); + } + + // pass the data + memcpy(dataOut, &rplBuff[1], len); + #if !RADIOLIB_STATIC_ONLY + delete[] rplBuff; + #endif + return(state); +} + +int16_t LR11x0::cryptoComputeAesCmac(uint8_t keyId, uint8_t* data, size_t len, uint32_t* mic) { + size_t reqLen = sizeof(uint8_t) + len; + #if RADIOLIB_STATIC_ONLY + uint8_t reqBuff[sizeof(uint8_t) + RADIOLIB_LR11X0_SPI_MAX_READ_WRITE_LEN]; + #else + uint8_t* reqBuff = new uint8_t[reqLen]; + #endif + uint8_t rplBuff[5] = { 0 }; + + reqBuff[0] = keyId; + memcpy(&reqBuff[1], data, len); + + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_CRYPTO_COMPUTE_AES_CMAC, false, rplBuff, sizeof(rplBuff), reqBuff, reqLen); + #if !RADIOLIB_STATIC_ONLY + delete[] reqBuff; + #endif + + // check the crypto engine state + if(rplBuff[0] != RADIOLIB_LR11X0_CRYPTO_STATUS_SUCCESS) { + RADIOLIB_DEBUG_BASIC_PRINTLN("Crypto Engine error: %02x", rplBuff[0]); + return(RADIOLIB_ERR_SPI_CMD_FAILED); + } + + if(mic) { *mic = ((uint32_t)(rplBuff[1]) << 24) | ((uint32_t)(rplBuff[2]) << 16) | ((uint32_t)(rplBuff[3]) << 8) | (uint32_t)rplBuff[4]; } + return(state); +} + +int16_t LR11x0::cryptoVerifyAesCmac(uint8_t keyId, uint32_t micExp, uint8_t* data, size_t len, bool* result) { + size_t reqLen = sizeof(uint8_t) + sizeof(uint32_t) + len; + #if RADIOLIB_STATIC_ONLY + uint8_t reqBuff[sizeof(uint8_t) + sizeof(uint32_t) + RADIOLIB_LR11X0_SPI_MAX_READ_WRITE_LEN]; + #else + uint8_t* reqBuff = new uint8_t[reqLen]; + #endif + uint8_t rplBuff[1] = { 0 }; + + reqBuff[0] = keyId; + reqBuff[1] = (uint8_t)((micExp >> 24) & 0xFF); + reqBuff[2] = (uint8_t)((micExp >> 16) & 0xFF); + reqBuff[3] = (uint8_t)((micExp >> 8) & 0xFF); + reqBuff[4] = (uint8_t)(micExp & 0xFF); + memcpy(&reqBuff[5], data, len); + + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_CRYPTO_VERIFY_AES_CMAC, false, rplBuff, sizeof(rplBuff), reqBuff, reqLen); + #if !RADIOLIB_STATIC_ONLY + delete[] reqBuff; + #endif + + // check the crypto engine state + if(rplBuff[0] != RADIOLIB_LR11X0_CRYPTO_STATUS_SUCCESS) { + RADIOLIB_DEBUG_BASIC_PRINTLN("Crypto Engine error: %02x", rplBuff[0]); + return(RADIOLIB_ERR_SPI_CMD_FAILED); + } + + if(result) { *result = (rplBuff[0] == RADIOLIB_LR11X0_CRYPTO_STATUS_SUCCESS); } + return(state); +} + +int16_t LR11x0::cryptoAesEncrypt01(uint8_t keyId, uint8_t* dataIn, size_t len, uint8_t* dataOut) { + return(this->cryptoCommon(RADIOLIB_LR11X0_CMD_CRYPTO_AES_ENCRYPT_01, keyId, dataIn, len, dataOut)); +} + +int16_t LR11x0::cryptoAesEncrypt(uint8_t keyId, uint8_t* dataIn, size_t len, uint8_t* dataOut) { + return(this->cryptoCommon(RADIOLIB_LR11X0_CMD_CRYPTO_AES_ENCRYPT, keyId, dataIn, len, dataOut)); +} + +int16_t LR11x0::cryptoAesDecrypt(uint8_t keyId, uint8_t* dataIn, size_t len, uint8_t* dataOut) { + return(this->cryptoCommon(RADIOLIB_LR11X0_CMD_CRYPTO_AES_DECRYPT, keyId, dataIn, len, dataOut)); +} + +int16_t LR11x0::cryptoStoreToFlash(void) { + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_CRYPTO_STORE_TO_FLASH, true, NULL, 0)); +} + +int16_t LR11x0::cryptoRestoreFromFlash(void) { + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_CRYPTO_RESTORE_FROM_FLASH, true, NULL, 0)); +} + +int16_t LR11x0::cryptoSetParam(uint8_t id, uint32_t value) { + uint8_t buff[5] = { + id, + (uint8_t)((value >> 24) & 0xFF), (uint8_t)((value >> 16) & 0xFF), + (uint8_t)((value >> 8) & 0xFF), (uint8_t)(value & 0xFF) + }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_CRYPTO_SET_PARAM, true, buff, sizeof(buff))); +} + +int16_t LR11x0::cryptoGetParam(uint8_t id, uint32_t* value) { + uint8_t reqBuff[1] = { id }; + uint8_t rplBuff[4] = { 0 }; + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_CRYPTO_GET_PARAM, false, rplBuff, sizeof(rplBuff), reqBuff, sizeof(reqBuff)); + RADIOLIB_ASSERT(state); + if(value) { *value = ((uint32_t)(rplBuff[0]) << 24) | ((uint32_t)(rplBuff[1]) << 16) | ((uint32_t)(rplBuff[2]) << 8) | (uint32_t)rplBuff[3]; } + return(state); +} + +int16_t LR11x0::cryptoCheckEncryptedFirmwareImage(uint32_t offset, uint32_t* data, size_t len, bool nonvolatile) { + // check maximum size + if(len > (RADIOLIB_LR11X0_SPI_MAX_READ_WRITE_LEN/sizeof(uint32_t))) { + return(RADIOLIB_ERR_SPI_CMD_INVALID); + } + return(this->writeCommon(RADIOLIB_LR11X0_CMD_CRYPTO_CHECK_ENCRYPTED_FIRMWARE_IMAGE, offset, data, len, nonvolatile)); +} + +int16_t LR11x0::cryptoCheckEncryptedFirmwareImageResult(bool* result) { + uint8_t buff[1] = { 0 }; + int16_t state = this->SPIcommand(RADIOLIB_LR11X0_CMD_CRYPTO_CHECK_ENCRYPTED_FIRMWARE_IMAGE_RESULT, false, buff, sizeof(buff)); + + // pass the replies + if(result) { *result = (bool)buff[0]; } + + return(state); +} + +int16_t LR11x0::bootEraseFlash(void) { + // erasing flash takes about 2.5 seconds, temporarily tset SPI timeout to 3 seconds + RadioLibTime_t timeout = this->mod->spiConfig.timeout; + this->mod->spiConfig.timeout = 3000; + int16_t state = this->mod->SPIwriteStream(RADIOLIB_LR11X0_CMD_BOOT_ERASE_FLASH, NULL, 0, false, false); + this->mod->spiConfig.timeout = timeout; + return(state); +} + +int16_t LR11x0::bootWriteFlashEncrypted(uint32_t offset, uint32_t* data, size_t len, bool nonvolatile) { + // check maximum size + if(len > (RADIOLIB_LR11X0_SPI_MAX_READ_WRITE_LEN/sizeof(uint32_t))) { + return(RADIOLIB_ERR_SPI_CMD_INVALID); + } + return(this->writeCommon(RADIOLIB_LR11X0_CMD_BOOT_WRITE_FLASH_ENCRYPTED, offset, data, len, nonvolatile)); +} + +int16_t LR11x0::bootReboot(bool stay) { + uint8_t buff[1] = { (uint8_t)stay }; + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_BOOT_REBOOT, true, buff, sizeof(buff))); +} + +int16_t LR11x0::bootGetPin(uint8_t* pin) { + RADIOLIB_ASSERT_PTR(pin); + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_BOOT_GET_PIN, false, pin, RADIOLIB_LR11X0_PIN_LEN)); +} + +int16_t LR11x0::bootGetChipEui(uint8_t* eui) { + RADIOLIB_ASSERT_PTR(eui); + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_BOOT_GET_CHIP_EUI, false, eui, RADIOLIB_LR11X0_EUI_LEN)); +} + +int16_t LR11x0::bootGetJoinEui(uint8_t* eui) { + RADIOLIB_ASSERT_PTR(eui); + return(this->SPIcommand(RADIOLIB_LR11X0_CMD_BOOT_GET_JOIN_EUI, false, eui, RADIOLIB_LR11X0_EUI_LEN)); +} + +int16_t LR11x0::writeCommon(uint16_t cmd, uint32_t addrOffset, const uint32_t* data, size_t len, bool nonvolatile) { + // build buffers - later we need to ensure endians are correct, + // so there is probably no way to do this without copying buffers and iterating + size_t buffLen = sizeof(uint32_t) + len*sizeof(uint32_t); + #if RADIOLIB_STATIC_ONLY + uint8_t dataBuff[sizeof(uint32_t) + RADIOLIB_LR11X0_SPI_MAX_READ_WRITE_LEN]; + #else + uint8_t* dataBuff = new uint8_t[buffLen]; + #endif + + // set the address or offset + dataBuff[0] = (uint8_t)((addrOffset >> 24) & 0xFF); + dataBuff[1] = (uint8_t)((addrOffset >> 16) & 0xFF); + dataBuff[2] = (uint8_t)((addrOffset >> 8) & 0xFF); + dataBuff[3] = (uint8_t)(addrOffset & 0xFF); + + // convert endians + for(size_t i = 0; i < len; i++) { + uint32_t bin = 0; + if(nonvolatile) { + bin = RADIOLIB_NONVOLATILE_READ_DWORD(data + i); + } else { + bin = data[i]; + } + dataBuff[4 + i*sizeof(uint32_t)] = (uint8_t)((bin >> 24) & 0xFF); + dataBuff[5 + i*sizeof(uint32_t)] = (uint8_t)((bin >> 16) & 0xFF); + dataBuff[6 + i*sizeof(uint32_t)] = (uint8_t)((bin >> 8) & 0xFF); + dataBuff[7 + i*sizeof(uint32_t)] = (uint8_t)(bin & 0xFF); + } + + int16_t state = this->mod->SPIwriteStream(cmd, dataBuff, buffLen, true, false); + #if !RADIOLIB_STATIC_ONLY + delete[] dataBuff; + #endif + return(state); +} + +int16_t LR11x0::cryptoCommon(uint16_t cmd, uint8_t keyId, uint8_t* dataIn, size_t len, uint8_t* dataOut) { + // build buffers + #if RADIOLIB_STATIC_ONLY + uint8_t reqBuff[RADIOLIB_LR11X0_SPI_MAX_READ_WRITE_LEN]; + uint8_t rplBuff[RADIOLIB_LR11X0_SPI_MAX_READ_WRITE_LEN]; + #else + uint8_t* reqBuff = new uint8_t[sizeof(uint8_t) + len]; + uint8_t* rplBuff = new uint8_t[sizeof(uint8_t) + len]; + #endif + + // set the request fields + reqBuff[0] = keyId; + memcpy(&reqBuff[1], dataIn, len); + + int16_t state = this->SPIcommand(cmd, false, rplBuff, sizeof(uint8_t) + len, reqBuff, sizeof(uint8_t) + len); + #if !RADIOLIB_STATIC_ONLY + delete[] reqBuff; + #endif + if(state != RADIOLIB_ERR_NONE) { + #if !RADIOLIB_STATIC_ONLY + delete[] rplBuff; + #endif + return(state); + } + + // check the crypto engine state + if(rplBuff[0] != RADIOLIB_LR11X0_CRYPTO_STATUS_SUCCESS) { + RADIOLIB_DEBUG_BASIC_PRINTLN("Crypto Engine error: %02x", rplBuff[0]); + return(RADIOLIB_ERR_SPI_CMD_FAILED); + } + + // pass the data + memcpy(dataOut, &rplBuff[1], len); + #if !RADIOLIB_STATIC_ONLY + delete[] rplBuff; + #endif + return(state); +} + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/LR11x0/LR11x0.h b/software/firmware/source/libraries/RadioLib/src/modules/LR11x0/LR11x0.h new file mode 100644 index 000000000..9ee985d5b --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/LR11x0/LR11x0.h @@ -0,0 +1,1849 @@ +#if !defined(_RADIOLIB_LR11X0_H) +#define _RADIOLIB_LR11X0_H + +#include "../../TypeDef.h" + +#if !RADIOLIB_EXCLUDE_LR11X0 + +#include "../../Module.h" + +#include "../../protocols/PhysicalLayer/PhysicalLayer.h" + +// LR11X0 physical layer properties +#define RADIOLIB_LR11X0_FREQUENCY_STEP_SIZE 1.0 +#define RADIOLIB_LR11X0_MAX_PACKET_LENGTH 255 +#define RADIOLIB_LR11X0_CRYSTAL_FREQ 32.0 +#define RADIOLIB_LR11X0_DIV_EXPONENT 25 + +// LR11X0 SPI commands +#define RADIOLIB_LR11X0_CMD_NOP (0x0000) +#define RADIOLIB_LR11X0_CMD_WRITE_REG_MEM (0x0105) +#define RADIOLIB_LR11X0_CMD_READ_REG_MEM (0x0106) +#define RADIOLIB_LR11X0_CMD_WRITE_BUFFER (0x0109) +#define RADIOLIB_LR11X0_CMD_READ_BUFFER (0x010A) +#define RADIOLIB_LR11X0_CMD_CLEAR_RX_BUFFER (0x010B) +#define RADIOLIB_LR11X0_CMD_WRITE_REG_MEM_MASK (0x010C) +#define RADIOLIB_LR11X0_CMD_GET_STATUS (0x0100) +#define RADIOLIB_LR11X0_CMD_GET_VERSION (0x0101) +#define RADIOLIB_LR11X0_CMD_GET_ERRORS (0x010D) +#define RADIOLIB_LR11X0_CMD_CLEAR_ERRORS (0x010E) +#define RADIOLIB_LR11X0_CMD_CALIBRATE (0x010F) +#define RADIOLIB_LR11X0_CMD_SET_REG_MODE (0x0110) +#define RADIOLIB_LR11X0_CMD_CALIB_IMAGE (0x0111) +#define RADIOLIB_LR11X0_CMD_SET_DIO_AS_RF_SWITCH (0x0112) +#define RADIOLIB_LR11X0_CMD_SET_DIO_IRQ_PARAMS (0x0113) +#define RADIOLIB_LR11X0_CMD_CLEAR_IRQ (0x0114) +#define RADIOLIB_LR11X0_CMD_CONFIG_LF_LOCK (0x0116) +#define RADIOLIB_LR11X0_CMD_SET_TCXO_MODE (0x0117) +#define RADIOLIB_LR11X0_CMD_REBOOT (0x0118) +#define RADIOLIB_LR11X0_CMD_GET_VBAT (0x0119) +#define RADIOLIB_LR11X0_CMD_GET_TEMP (0x011A) +#define RADIOLIB_LR11X0_CMD_SET_SLEEP (0x011B) +#define RADIOLIB_LR11X0_CMD_SET_STANDBY (0x011C) +#define RADIOLIB_LR11X0_CMD_SET_FS (0x011D) +#define RADIOLIB_LR11X0_CMD_GET_RANDOM_NUMBER (0x0120) +#define RADIOLIB_LR11X0_CMD_ERASE_INFO_PAGE (0x0121) +#define RADIOLIB_LR11X0_CMD_WRITE_INFO_PAGE (0x0122) +#define RADIOLIB_LR11X0_CMD_READ_INFO_PAGE (0x0123) +#define RADIOLIB_LR11X0_CMD_GET_CHIP_EUI (0x0125) +#define RADIOLIB_LR11X0_CMD_GET_SEMTECH_JOIN_EUI (0x0126) +#define RADIOLIB_LR11X0_CMD_DERIVE_ROOT_KEYS_AND_GET_PIN (0x0127) +#define RADIOLIB_LR11X0_CMD_ENABLE_SPI_CRC (0x0128) +#define RADIOLIB_LR11X0_CMD_DRIVE_DIOS_IN_SLEEP_MODE (0x012A) +#define RADIOLIB_LR11X0_CMD_RESET_STATS (0x0200) +#define RADIOLIB_LR11X0_CMD_GET_STATS (0x0201) +#define RADIOLIB_LR11X0_CMD_GET_PACKET_TYPE (0x0202) +#define RADIOLIB_LR11X0_CMD_GET_RX_BUFFER_STATUS (0x0203) +#define RADIOLIB_LR11X0_CMD_GET_PACKET_STATUS (0x0204) +#define RADIOLIB_LR11X0_CMD_GET_RSSI_INST (0x0205) +#define RADIOLIB_LR11X0_CMD_SET_GFSK_SYNC_WORD (0x0206) +#define RADIOLIB_LR11X0_CMD_SET_LORA_PUBLIC_NETWORK (0x0208) +#define RADIOLIB_LR11X0_CMD_SET_RX (0x0209) +#define RADIOLIB_LR11X0_CMD_SET_TX (0x020A) +#define RADIOLIB_LR11X0_CMD_SET_RF_FREQUENCY (0x020B) +#define RADIOLIB_LR11X0_CMD_AUTO_TX_RX (0x020C) +#define RADIOLIB_LR11X0_CMD_SET_CAD_PARAMS (0x020D) +#define RADIOLIB_LR11X0_CMD_SET_PACKET_TYPE (0x020E) +#define RADIOLIB_LR11X0_CMD_SET_MODULATION_PARAMS (0x020F) +#define RADIOLIB_LR11X0_CMD_SET_PACKET_PARAMS (0x0210) +#define RADIOLIB_LR11X0_CMD_SET_TX_PARAMS (0x0211) +#define RADIOLIB_LR11X0_CMD_SET_PACKET_ADRS (0x0212) +#define RADIOLIB_LR11X0_CMD_SET_RX_TX_FALLBACK_MODE (0x0213) +#define RADIOLIB_LR11X0_CMD_SET_RX_DUTY_CYCLE (0x0214) +#define RADIOLIB_LR11X0_CMD_SET_PA_CONFIG (0x0215) +#define RADIOLIB_LR11X0_CMD_STOP_TIMEOUT_ON_PREAMBLE (0x0217) +#define RADIOLIB_LR11X0_CMD_SET_CAD (0x0218) +#define RADIOLIB_LR11X0_CMD_SET_TX_CW (0x0219) +#define RADIOLIB_LR11X0_CMD_SET_TX_INFINITE_PREAMBLE (0x021A) +#define RADIOLIB_LR11X0_CMD_SET_LORA_SYNCH_TIMEOUT (0x021B) +#define RADIOLIB_LR11X0_CMD_SET_RANGING_ADDR (0x021C) +#define RADIOLIB_LR11X0_CMD_SET_RANGING_REQ_ADDR (0x021D) +#define RADIOLIB_LR11X0_CMD_GET_RANGING_RESULT (0x021E) +#define RADIOLIB_LR11X0_CMD_SET_RANGING_TX_RX_DELAY (0x021F) +#define RADIOLIB_LR11X0_CMD_SET_GFSK_CRC_PARAMS (0x0224) +#define RADIOLIB_LR11X0_CMD_SET_GFSK_WHIT_PARAMS (0x0225) +#define RADIOLIB_LR11X0_CMD_SET_RX_BOOSTED (0x0227) +#define RADIOLIB_LR11X0_CMD_SET_RANGING_PARAMETER (0x0228) +#define RADIOLIB_LR11X0_CMD_SET_RSSI_CALIBRATION (0x0229) +#define RADIOLIB_LR11X0_CMD_SET_LORA_SYNC_WORD (0x022B) +#define RADIOLIB_LR11X0_CMD_LR_FHSS_BUILD_FRAME (0x022C) +#define RADIOLIB_LR11X0_CMD_LR_FHSS_SET_SYNC_WORD (0x022D) +#define RADIOLIB_LR11X0_CMD_CONFIG_BLE_BEACON (0x022E) +#define RADIOLIB_LR11X0_CMD_GET_LORA_RX_HEADER_INFOS (0x0230) +#define RADIOLIB_LR11X0_CMD_BLE_BEACON_SEND (0x0231) +#define RADIOLIB_LR11X0_CMD_WIFI_SCAN (0x0300) +#define RADIOLIB_LR11X0_CMD_WIFI_SCAN_TIME_LIMIT (0x0301) +#define RADIOLIB_LR11X0_CMD_WIFI_COUNTRY_CODE (0x0302) +#define RADIOLIB_LR11X0_CMD_WIFI_COUNTRY_CODE_TIME_LIMIT (0x0303) +#define RADIOLIB_LR11X0_CMD_WIFI_GET_NB_RESULTS (0x0305) +#define RADIOLIB_LR11X0_CMD_WIFI_READ_RESULTS (0x0306) +#define RADIOLIB_LR11X0_CMD_WIFI_RESET_CUMUL_TIMINGS (0x0307) +#define RADIOLIB_LR11X0_CMD_WIFI_READ_CUMUL_TIMINGS (0x0308) +#define RADIOLIB_LR11X0_CMD_WIFI_GET_NB_COUNTRY_CODE_RESULTS (0x0309) +#define RADIOLIB_LR11X0_CMD_WIFI_READ_COUNTRY_CODE_RESULTS (0x030A) +#define RADIOLIB_LR11X0_CMD_WIFI_CFG_TIMESTAMP_AP_PHONE (0x030B) +#define RADIOLIB_LR11X0_CMD_WIFI_READ_VERSION (0x0320) +#define RADIOLIB_LR11X0_CMD_GNSS_READ_RSSI (0x0222) +#define RADIOLIB_LR11X0_CMD_GNSS_SET_CONSTELLATION_TO_USE (0x0400) +#define RADIOLIB_LR11X0_CMD_GNSS_READ_CONSTELLATION_TO_USE (0x0401) +#define RADIOLIB_LR11X0_CMD_GNSS_SET_ALMANAC_UPDATE (0x0402) +#define RADIOLIB_LR11X0_CMD_GNSS_READ_ALMANAC_UPDATE (0x0403) +#define RADIOLIB_LR11X0_CMD_GNSS_SET_FREQ_SEARCH_SPACE (0x0404) +#define RADIOLIB_LR11X0_CMD_GNSS_READ_FREQ_SEARCH_SPACE (0x0405) +#define RADIOLIB_LR11X0_CMD_GNSS_READ_VERSION (0x0406) +#define RADIOLIB_LR11X0_CMD_GNSS_READ_SUPPORTED_CONSTELLATIONS (0x0407) +#define RADIOLIB_LR11X0_CMD_GNSS_SET_MODE (0x0408) +#define RADIOLIB_LR11X0_CMD_GNSS_AUTONOMOUS (0x0409) +#define RADIOLIB_LR11X0_CMD_GNSS_ASSISTED (0x040A) +#define RADIOLIB_LR11X0_CMD_GNSS_SCAN (0x040B) +#define RADIOLIB_LR11X0_CMD_GNSS_GET_RESULT_SIZE (0x040C) +#define RADIOLIB_LR11X0_CMD_GNSS_READ_RESULTS (0x040D) +#define RADIOLIB_LR11X0_CMD_GNSS_ALMANAC_FULL_UPDATE (0x040E) +#define RADIOLIB_LR11X0_CMD_GNSS_ALMANAC_READ_ADDR_SIZE (0x040F) +#define RADIOLIB_LR11X0_CMD_GNSS_SET_ASSISTANCE_POSITION (0x0410) +#define RADIOLIB_LR11X0_CMD_GNSS_READ_ASSISTANCE_POSITION (0x0411) +#define RADIOLIB_LR11X0_CMD_GNSS_PUSH_SOLVER_MSG (0x0414) +#define RADIOLIB_LR11X0_CMD_GNSS_PUSH_DM_MSG (0x0415) +#define RADIOLIB_LR11X0_CMD_GNSS_GET_CONTEXT_STATUS (0x0416) +#define RADIOLIB_LR11X0_CMD_GNSS_GET_NB_SV_DETECTED (0x0417) +#define RADIOLIB_LR11X0_CMD_GNSS_GET_SV_DETECTED (0x0418) +#define RADIOLIB_LR11X0_CMD_GNSS_GET_CONSUMPTION (0x0419) +#define RADIOLIB_LR11X0_CMD_GNSS_READ_ALMANAC_PER_SATELLITE (0x041A) +#define RADIOLIB_LR11X0_CMD_GNSS_GET_SV_VISIBLE (0x041F) +#define RADIOLIB_LR11X0_CMD_GNSS_GET_SV_VISIBLE_DOPPLER (0x0420) +#define RADIOLIB_LR11X0_CMD_GNSS_READ_LAST_SCAN_MODE_LAUNCHED (0x0426) +#define RADIOLIB_LR11X0_CMD_GNSS_FETCH_TIME (0x0432) +#define RADIOLIB_LR11X0_CMD_GNSS_READ_TIME (0x0434) +#define RADIOLIB_LR11X0_CMD_GNSS_RESET_TIME (0x0435) +#define RADIOLIB_LR11X0_CMD_GNSS_RESET_POSITION (0x0437) +#define RADIOLIB_LR11X0_CMD_GNSS_READ_WEEK_NUMBER_ROLLOWER (0x0438) +#define RADIOLIB_LR11X0_CMD_GNSS_READ_DEMOD_STATUS (0x0439) +#define RADIOLIB_LR11X0_CMD_GNSS_READ_CUMUL_TIMING (0x044A) +#define RADIOLIB_LR11X0_CMD_GNSS_SET_TIME (0x044B) +#define RADIOLIB_LR11X0_CMD_GNSS_CONFIG_DELAY_RESET_AP (0x044D) +#define RADIOLIB_LR11X0_CMD_GNSS_READ_DOPPLER_SOLVER_RES (0x044F) +#define RADIOLIB_LR11X0_CMD_GNSS_READ_DELAY_RESET_AP (0x0453) +#define RADIOLIB_LR11X0_CMD_GNSS_ALMANAC_UPDATE_FROM_SAT (0x0454) +#define RADIOLIB_LR11X0_CMD_GNSS_READ_KEEP_SYNC_STATUS (0x0456) +#define RADIOLIB_LR11X0_CMD_GNSS_READ_ALMANAC_STATUS (0x0457) +#define RADIOLIB_LR11X0_CMD_GNSS_CONFIG_ALMANAC_UPDATE_PERIOD (0x0463) +#define RADIOLIB_LR11X0_CMD_GNSS_READ_ALMANAC_UPDATE_PERIOD (0x0464) +#define RADIOLIB_LR11X0_CMD_GNSS_GET_SV_WARM_START (0x0466) +#define RADIOLIB_LR11X0_CMD_GNSS_GET_SV_SYNC (0x0466) +#define RADIOLIB_LR11X0_CMD_GNSS_READ_WARM_START_STATUS (0x0469) +#define RADIOLIB_LR11X0_CMD_GNSS_WRITE_BIT_MASK_SAT_ACTIVATED (0x0472) +#define RADIOLIB_LR11X0_CMD_CRYPTO_SET_KEY (0x0502) +#define RADIOLIB_LR11X0_CMD_CRYPTO_DERIVE_KEY (0x0503) +#define RADIOLIB_LR11X0_CMD_CRYPTO_PROCESS_JOIN_ACCEPT (0x0504) +#define RADIOLIB_LR11X0_CMD_CRYPTO_COMPUTE_AES_CMAC (0x0505) +#define RADIOLIB_LR11X0_CMD_CRYPTO_VERIFY_AES_CMAC (0x0506) +#define RADIOLIB_LR11X0_CMD_CRYPTO_AES_ENCRYPT_01 (0x0507) +#define RADIOLIB_LR11X0_CMD_CRYPTO_AES_ENCRYPT (0x0508) +#define RADIOLIB_LR11X0_CMD_CRYPTO_AES_DECRYPT (0x0509) +#define RADIOLIB_LR11X0_CMD_CRYPTO_STORE_TO_FLASH (0x050A) +#define RADIOLIB_LR11X0_CMD_CRYPTO_RESTORE_FROM_FLASH (0x050B) +#define RADIOLIB_LR11X0_CMD_CRYPTO_SET_PARAM (0x050D) +#define RADIOLIB_LR11X0_CMD_CRYPTO_GET_PARAM (0x050E) +#define RADIOLIB_LR11X0_CMD_CRYPTO_CHECK_ENCRYPTED_FIRMWARE_IMAGE (0x050F) +#define RADIOLIB_LR11X0_CMD_CRYPTO_CHECK_ENCRYPTED_FIRMWARE_IMAGE_RESULT (0x0510) +#define RADIOLIB_LR11X0_CMD_BOOT_ERASE_FLASH (0x8000) +#define RADIOLIB_LR11X0_CMD_BOOT_WRITE_FLASH_ENCRYPTED (0x8003) +#define RADIOLIB_LR11X0_CMD_BOOT_REBOOT (0x8005) +#define RADIOLIB_LR11X0_CMD_BOOT_GET_PIN (0x800B) +#define RADIOLIB_LR11X0_CMD_BOOT_GET_CHIP_EUI (0x800C) +#define RADIOLIB_LR11X0_CMD_BOOT_GET_JOIN_EUI (0x800D) + +// LR11X0 register map +#define RADIOLIB_LR11X0_REG_SF6_SX127X_COMPAT (0x00F20414) +#define RADIOLIB_LR11X0_REG_LORA_HIGH_POWER_FIX (0x00F30054) +#define RADIOLIB_LR11X0_REG_LNA_MODE (0x00F3008C) +// TODO add fix for br 600/1200 bps + +// LR11X0 SPI command variables + +// RADIOLIB_LR11X0_CMD_GET_STATUS MSB LSB DESCRIPTION +#define RADIOLIB_LR11X0_STAT_1_CMD_FAIL (0x00UL << 1) // 3 1 command status: last command could not be executed +#define RADIOLIB_LR11X0_STAT_1_CMD_PERR (0x01UL << 1) // 3 1 processing error +#define RADIOLIB_LR11X0_STAT_1_CMD_OK (0x02UL << 1) // 3 1 successfully processed +#define RADIOLIB_LR11X0_STAT_1_CMD_DAT (0x03UL << 1) // 3 1 successfully processed, data is being transmitted +#define RADIOLIB_LR11X0_STAT_1_IRQ_INACTIVE (0x00UL << 0) // 0 0 interrupt status: inactive +#define RADIOLIB_LR11X0_STAT_1_IRQ_ACTIVE (0x01UL << 0) // 0 0 at least 1 interrupt active +#define RADIOLIB_LR11X0_STAT_2_CMD_RST_CLEARED (0x00UL << 4) // 7 4 reset status: cleared +#define RADIOLIB_LR11X0_STAT_2_CMD_RST_ANALOG (0x01UL << 4) // 7 4 analog (power on, brown-out) +#define RADIOLIB_LR11X0_STAT_2_CMD_RST_EXTERNAL (0x02UL << 4) // 7 4 NRESET pin +#define RADIOLIB_LR11X0_STAT_2_CMD_RST_SYSTEM (0x03UL << 4) // 7 4 system +#define RADIOLIB_LR11X0_STAT_2_CMD_RST_WATCHDOG (0x04UL << 4) // 7 4 watchdog +#define RADIOLIB_LR11X0_STAT_2_CMD_RST_WAKEUP (0x05UL << 4) // 7 4 NSS toggling wake-up +#define RADIOLIB_LR11X0_STAT_2_CMD_RST_RTC (0x06UL << 4) // 7 4 realtime clock +#define RADIOLIB_LR11X0_STAT_2_MODE_SLEEP (0x00UL << 1) // 3 1 chip mode: sleep +#define RADIOLIB_LR11X0_STAT_2_MODE_STBY_RC (0x01UL << 1) // 3 1 standby with RC oscillator +#define RADIOLIB_LR11X0_STAT_2_MODE_STBY_OSC (0x02UL << 1) // 3 1 standby with external oscillator +#define RADIOLIB_LR11X0_STAT_2_MODE_FS (0x03UL << 1) // 3 1 frequency synthesis +#define RADIOLIB_LR11X0_STAT_2_MODE_RX (0x04UL << 1) // 3 1 receive +#define RADIOLIB_LR11X0_STAT_2_MODE_TX (0x05UL << 1) // 3 1 transmit +#define RADIOLIB_LR11X0_STAT_2_MODE_WIFI_GNSS (0x06UL << 1) // 3 1 WiFi or GNSS geolocation +#define RADIOLIB_LR11X0_STAT_2_BOOT (0x00UL << 0) // 0 0 code executed from: bootloader +#define RADIOLIB_LR11X0_STAT_2_FLASH (0x01UL << 0) // 0 0 flash + +// RADIOLIB_LR11X0_CMD_WRITE_REG_MEM +#define RADIOLIB_LR11X0_SPI_MAX_READ_WRITE_LEN (256) // 7 0 maximum length of read/write SPI payload in bytes + +// RADIOLIB_LR11X0_CMD_GET_VERSION +#define RADIOLIB_LR11X0_DEVICE_LR1110 (0x01UL << 0) // 7 0 HW device: LR1110 +#define RADIOLIB_LR11X0_DEVICE_LR1120 (0x02UL << 0) // 7 0 LR1120 +#define RADIOLIB_LR11X0_DEVICE_LR1121 (0x03UL << 0) // 7 0 LR1121 +#define RADIOLIB_LR11X0_DEVICE_BOOT (0xDFUL << 0) // 7 0 bootloader mode + +// RADIOLIB_LR11X0_CMD_GET_ERRORS +#define RADIOLIB_LR11X0_ERROR_STAT_LF_RC_CALIB_ERR (0x01UL << 0) // 15 0 error: low frequency RC not calibrated +#define RADIOLIB_LR11X0_ERROR_STAT_HF_RC_CALIB_ERR (0x01UL << 1) // 15 0 high frequency RC not calibrated +#define RADIOLIB_LR11X0_ERROR_STAT_ADC_CALIB_ERR (0x01UL << 2) // 15 0 ADC not calibrated +#define RADIOLIB_LR11X0_ERROR_STAT_PLL_CALIB_ERR (0x01UL << 3) // 15 0 PLL not calibrated +#define RADIOLIB_LR11X0_ERROR_STAT_IMG_CALIB_ERR (0x01UL << 4) // 15 0 image rejection not calibrated +#define RADIOLIB_LR11X0_ERROR_STAT_HF_XOSC_START_ERR (0x01UL << 5) // 15 0 high frequency oscillator failed to start +#define RADIOLIB_LR11X0_ERROR_STAT_LF_XOSC_START_ERR (0x01UL << 6) // 15 0 low frequency oscillator failed to start +#define RADIOLIB_LR11X0_ERROR_STAT_PLL_LOCK_ERR (0x01UL << 7) // 15 0 PLL failed to lock +#define RADIOLIB_LR11X0_ERROR_STAT_RX_ADC_OFFSET_ERR (0x01UL << 8) // 15 0 ADC offset not calibrated + +// RADIOLIB_LR11X0_CMD_CALIBRATE +#define RADIOLIB_LR11X0_CALIBRATE_PLL_TX (0x01UL << 5) // 5 5 calibrate: Tx PLL +#define RADIOLIB_LR11X0_CALIBRATE_IMG (0x01UL << 4) // 4 4 image rejection +#define RADIOLIB_LR11X0_CALIBRATE_ADC (0x01UL << 3) // 3 3 A/D converter +#define RADIOLIB_LR11X0_CALIBRATE_PLL (0x01UL << 2) // 2 2 PLL +#define RADIOLIB_LR11X0_CALIBRATE_HF_RC (0x01UL << 1) // 1 1 high frequency RC +#define RADIOLIB_LR11X0_CALIBRATE_LF_RC (0x01UL << 0) // 0 0 low frequency RC +#define RADIOLIB_LR11X0_CALIBRATE_ALL (0x3FUL << 0) // 5 0 everything +#define RADIOLIB_LR11X0_CAL_IMG_FREQ_TRIG_MHZ (20.0) + +// RADIOLIB_LR11X0_CMD_SET_REG_MODE +#define RADIOLIB_LR11X0_REG_MODE_LDO (0x00UL << 0) // 0 0 regulator mode: LDO in all modes +#define RADIOLIB_LR11X0_REG_MODE_DC_DC (0x01UL << 0) // 0 0 DC-DC and LDO + +// RADIOLIB_LR11X0_CMD_SET_DIO_AS_RF_SWITCH +#define RADIOLIB_LR11X0_RFSW_DIO5_ENABLED (0x01UL << 0) // 4 0 RF switch: DIO5 enabled +#define RADIOLIB_LR11X0_RFSW_DIO5_DISABLED (0x00UL << 0) // 4 0 DIO5 disabled (default) +#define RADIOLIB_LR11X0_RFSW_DIO6_ENABLED (0x01UL << 1) // 4 0 RF switch: DIO6 enabled +#define RADIOLIB_LR11X0_RFSW_DIO6_DISABLED (0x00UL << 1) // 4 0 DIO6 disabled (default) +#define RADIOLIB_LR11X0_RFSW_DIO7_ENABLED (0x01UL << 2) // 4 0 RF switch: DIO7 enabled +#define RADIOLIB_LR11X0_RFSW_DIO7_DISABLED (0x00UL << 2) // 4 0 DIO7 disabled (default) +#define RADIOLIB_LR11X0_RFSW_DIO8_ENABLED (0x01UL << 3) // 4 0 RF switch: DIO8 enabled +#define RADIOLIB_LR11X0_RFSW_DIO8_DISABLED (0x00UL << 3) // 4 0 DIO8 disabled (default) +#define RADIOLIB_LR11X0_RFSW_DIO10_ENABLED (0x01UL << 4) // 4 0 RF switch: DIO10 enabled +#define RADIOLIB_LR11X0_RFSW_DIO10_DISABLED (0x00UL << 4) // 4 0 DIO10 disabled (default) +#define RADIOLIB_LR11X0_DIOx(X) ((X) | RFSWITCH_PIN_FLAG) +#define RADIOLIB_LR11X0_DIOx_VAL(X) ((X) & ~RFSWITCH_PIN_FLAG) +#define RADIOLIB_LR11X0_DIO5 (RADIOLIB_LR11X0_DIOx(0)) +#define RADIOLIB_LR11X0_DIO6 (RADIOLIB_LR11X0_DIOx(1)) +#define RADIOLIB_LR11X0_DIO7 (RADIOLIB_LR11X0_DIOx(2)) +#define RADIOLIB_LR11X0_DIO8 (RADIOLIB_LR11X0_DIOx(3)) +#define RADIOLIB_LR11X0_DIO10 (RADIOLIB_LR11X0_DIOx(4)) + +// RADIOLIB_LR11X0_CMD_SET_DIO_IRQ_PARAMS +#define RADIOLIB_LR11X0_IRQ_TX_DONE (0x01UL << 2) // 31 0 interrupt: packet transmitted +#define RADIOLIB_LR11X0_IRQ_RX_DONE (0x01UL << 3) // 31 0 packet received +#define RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED (0x01UL << 4) // 31 0 preamble detected +#define RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID (0x01UL << 5) // 31 0 sync word or LoRa header valid +#define RADIOLIB_LR11X0_IRQ_HEADER_ERR (0x01UL << 6) // 31 0 LoRa header CRC error +#define RADIOLIB_LR11X0_IRQ_CRC_ERR (0x01UL << 7) // 31 0 packet CRC error +#define RADIOLIB_LR11X0_IRQ_CAD_DONE (0x01UL << 8) // 31 0 CAD completed +#define RADIOLIB_LR11X0_IRQ_CAD_DETECTED (0x01UL << 9) // 31 0 CAD detected +#define RADIOLIB_LR11X0_IRQ_TIMEOUT (0x01UL << 10) // 31 0 Rx or Tx timeout +#define RADIOLIB_LR11X0_IRQ_LR_FHSS_HOP (0x01UL << 11) // 31 0 FHSS hop +#define RADIOLIB_LR11X0_IRQ_GNSS_DONE (0x01UL << 19) // 31 0 GNSS scan finished +#define RADIOLIB_LR11X0_IRQ_WIFI_DONE (0x01UL << 20) // 31 0 WiFi scan finished +#define RADIOLIB_LR11X0_IRQ_LBD (0x01UL << 21) // 31 0 low battery detected +#define RADIOLIB_LR11X0_IRQ_CMD_ERROR (0x01UL << 22) // 31 0 command error +#define RADIOLIB_LR11X0_IRQ_ERROR (0x01UL << 23) // 31 0 some other error than CMD_ERR +#define RADIOLIB_LR11X0_IRQ_FSK_LEN_ERROR (0x01UL << 24) // 31 0 FSK packet received with length error +#define RADIOLIB_LR11X0_IRQ_FSK_ADDR_ERROR (0x01UL << 25) // 31 0 FSK packet received with address error +#define RADIOLIB_LR11X0_IRQ_LORA_RX_TIMESTAMP (0x01UL << 27) // 31 0 last LoRa symbol was received (timestamp source) +#define RADIOLIB_LR11X0_IRQ_GNSS_ABORT (0x01UL << 28) // 31 0 GNSS scan aborted +#define RADIOLIB_LR11X0_IRQ_ALL (0x1BF80FFCUL) // 31 0 all interrupts +#define RADIOLIB_LR11X0_IRQ_NONE (0x00UL << 0) // 31 0 no interrupts + +// RADIOLIB_LR11X0_CMD_CONFIG_LF_LOCK +#define RADIOLIB_LR11X0_LF_CLK_RC (0x00UL << 0) // 1 0 32.768 kHz source: RC oscillator +#define RADIOLIB_LR11X0_LF_CLK_XOSC (0x01UL << 0) // 1 0 crystal oscillator +#define RADIOLIB_LR11X0_LF_CLK_EXT (0x02UL << 0) // 1 0 external signal on DIO11 +#define RADIOLIB_LR11X0_LF_BUSY_RELEASE_DISABLED (0x00UL << 2) // 2 2 +#define RADIOLIB_LR11X0_LF_BUSY_RELEASE_ENABLED (0x01UL << 2) // 2 2 + +// RADIOLIB_LR11X0_CMD_SET_TCXO_MODE +#define RADIOLIB_LR11X0_TCXO_VOLTAGE_1_6 (0x00UL << 0) // 2 0 TCXO supply voltage: 1.6V +#define RADIOLIB_LR11X0_TCXO_VOLTAGE_1_7 (0x01UL << 0) // 2 0 1.7V +#define RADIOLIB_LR11X0_TCXO_VOLTAGE_1_8 (0x02UL << 0) // 2 0 1.8V +#define RADIOLIB_LR11X0_TCXO_VOLTAGE_2_2 (0x03UL << 0) // 2 0 2.2V +#define RADIOLIB_LR11X0_TCXO_VOLTAGE_2_4 (0x04UL << 0) // 2 0 2.4V +#define RADIOLIB_LR11X0_TCXO_VOLTAGE_2_7 (0x05UL << 0) // 2 0 2.7V +#define RADIOLIB_LR11X0_TCXO_VOLTAGE_3_0 (0x06UL << 0) // 2 0 3.0V +#define RADIOLIB_LR11X0_TCXO_VOLTAGE_3_3 (0x07UL << 0) // 2 0 3.3V + +// RADIOLIB_LR11X0_CMD_SET_SLEEP +#define RADIOLIB_LR11X0_SLEEP_RETENTION_DISABLED (0x00UL << 0) // 0 0 configuration retention in sleep mode: disabled +#define RADIOLIB_LR11X0_SLEEP_RETENTION_ENABLED (0x01UL << 0) // 0 0 enabled +#define RADIOLIB_LR11X0_SLEEP_WAKEUP_DISABLED (0x00UL << 0) // 1 1 automated wakeup: disabled +#define RADIOLIB_LR11X0_SLEEP_WAKEUP_ENABLED (0x01UL << 0) // 1 1 enabled + +// RADIOLIB_LR11X0_CMD_SET_STANDBY +#define RADIOLIB_LR11X0_STANDBY_RC (0x00UL << 0) // 7 0 standby mode: RC oscillator +#define RADIOLIB_LR11X0_STANDBY_XOSC (0x00UL << 0) // 7 0 XTAL/TCXO oscillator + +// RADIOLIB_LR11X0_CMD_ERASE_INFO_PAGE +#define RADIOLIB_LR11X0_INFO_PAGE (1) + +// RADIOLIB_LR11X0_CMD_GET_CHIP_EUI +#define RADIOLIB_LR11X0_EUI_LEN (8) + +// RADIOLIB_LR11X0_CMD_DERIVE_ROOT_KEYS_AND_GET_PIN +#define RADIOLIB_LR11X0_PIN_LEN (4) + +// RADIOLIB_LR11X0_CMD_GET_PACKET_STATUS +#define RADIOLIB_LR11X0_RX_STATUS_ADDR_ERR (0x01UL << 5) // 7 0 Rx status: address filtering error +#define RADIOLIB_LR11X0_RX_STATUS_CRC_ERR (0x01UL << 4) // 7 0 CRC error +#define RADIOLIB_LR11X0_RX_STATUS_LEN_ERR (0x01UL << 3) // 7 0 length filtering error +#define RADIOLIB_LR11X0_RX_STATUS_ABORTED (0x01UL << 2) // 7 0 packet reception aborted +#define RADIOLIB_LR11X0_RX_STATUS_PACKET_RECEIVED (0x01UL << 1) // 7 0 packet received +#define RADIOLIB_LR11X0_RX_STATUS_PACKET_SENT (0x01UL << 0) // 7 0 packet sent + +// RADIOLIB_LR11X0_CMD_SET_GFSK_SYNC_WORD +#define RADIOLIB_LR11X0_GFSK_SYNC_WORD_LEN (8) + +// RADIOLIB_LR11X0_CMD_SET_LORA_PUBLIC_NETWORK +#define RADIOLIB_LR11X0_LORA_PRIVATE_NETWORK (0x00UL << 0) // 7 0 LoRa sync word: private network +#define RADIOLIB_LR11X0_LORA_PUBLIC_NETWORK (0x01UL << 0) // 7 0 public network + +// RADIOLIB_LR11X0_CMD_SET_RX +#define RADIOLIB_LR11X0_RX_TIMEOUT_NONE (0x000000UL) // 23 0 Rx timeout duration: no timeout (Rx single mode) +#define RADIOLIB_LR11X0_RX_TIMEOUT_INF (0xFFFFFFUL) // 23 0 infinite (Rx continuous mode) + +// RADIOLIB_LR11X0_CMD_SET_TX +#define RADIOLIB_LR11X0_TX_TIMEOUT_NONE (0x000000UL) // 23 0 disable Tx timeout + +// RADIOLIB_LR11X0_CMD_AUTO_TX_RX +#define RADIOLIB_LR11X0_AUTO_TX_RX_DISABLED (0xFFFFFFUL) // 23 0 disable auto Tx/Rx mode +#define RADIOLIB_LR11X0_AUTO_TX_RX_SKIP_INT (0x000000UL) // 23 0 skip intermediary mode +#define RADIOLIB_LR11X0_AUTO_INTERMEDIARY_MODE_SLEEP (0x00UL << 0) // 1 0 intermediary mode: sleep +#define RADIOLIB_LR11X0_AUTO_INTERMEDIARY_MODE_STBY_RC (0x01UL << 0) // 1 0 standby with RC +#define RADIOLIB_LR11X0_AUTO_INTERMEDIARY_MODE_STBY_XOSC (0x02UL << 0) // 1 0 standby with XOSC +#define RADIOLIB_LR11X0_AUTO_INTERMEDIARY_MODE_FS (0x03UL << 0) // 1 0 frequency synthesis +#define RADIOLIB_LR11X0_AUTO_TX_RX_TIMEOUT_DISABLED (0x000000UL) // 23 0 disable timeout of the second mode + +// RADIOLIB_LR11X0_CMD_SET_CAD_PARAMS +#define RADIOLIB_LR11X0_CAD_EXIT_MODE_STBY_RC (0x00UL << 0) // 7 0 mode to set after CAD: standby with RC +#define RADIOLIB_LR11X0_CAD_EXIT_MODE_RX (0x01UL << 0) // 7 0 receive if activity detected +#define RADIOLIB_LR11X0_CAD_EXIT_MODE_LBT (0x10UL << 0) // 7 0 transmit if no activity detected +#define RADIOLIB_LR11X0_CAD_PARAM_DEFAULT (0xFFUL << 0) // 7 0 used by the CAD methods to specify default parameter value + +// RADIOLIB_LR11X0_CMD_SET_PACKET_TYPE +#define RADIOLIB_LR11X0_PACKET_TYPE_NONE (0x00UL << 0) // 2 0 packet type: none +#define RADIOLIB_LR11X0_PACKET_TYPE_GFSK (0x01UL << 0) // 2 0 (G)FSK +#define RADIOLIB_LR11X0_PACKET_TYPE_LORA (0x02UL << 0) // 2 0 LoRa +#define RADIOLIB_LR11X0_PACKET_TYPE_SIGFOX (0x03UL << 0) // 2 0 Sigfox +#define RADIOLIB_LR11X0_PACKET_TYPE_LR_FHSS (0x04UL << 0) // 2 0 GMSK/LR-FHSS +#define RADIOLIB_LR11X0_PACKET_TYPE_RANGING (0x05UL << 0) // 2 0 ranging +#define RADIOLIB_LR11X0_PACKET_TYPE_BLE (0x06UL << 0) // 2 0 BLE beacon + +// RADIOLIB_LR11X0_CMD_SET_MODULATION_PARAMS +#define RADIOLIB_LR11X0_LORA_BW_62_5 (0x03UL << 0) // 7 0 LoRa bandwidth: 62.5 kHz +#define RADIOLIB_LR11X0_LORA_BW_125_0 (0x04UL << 0) // 7 0 125.0 kHz +#define RADIOLIB_LR11X0_LORA_BW_250_0 (0x05UL << 0) // 7 0 250.0 kHz +#define RADIOLIB_LR11X0_LORA_BW_500_0 (0x06UL << 0) // 7 0 500.0 kHz +#define RADIOLIB_LR11X0_LORA_BW_203_125 (0x0DUL << 0) // 7 0 203.0 kHz (2.4GHz only) +#define RADIOLIB_LR11X0_LORA_BW_406_25 (0x0EUL << 0) // 7 0 406.0 kHz (2.4GHz only) +#define RADIOLIB_LR11X0_LORA_BW_812_50 (0x0FUL << 0) // 7 0 812.0 kHz (2.4GHz only) +#define RADIOLIB_LR11X0_LORA_CR_4_5_SHORT (0x01UL << 0) // 7 0 coding rate: 4/5 with short interleaver +#define RADIOLIB_LR11X0_LORA_CR_4_6_SHORT (0x02UL << 0) // 7 0 4/6 with short interleaver +#define RADIOLIB_LR11X0_LORA_CR_4_7_SHORT (0x03UL << 0) // 7 0 4/7 with short interleaver +#define RADIOLIB_LR11X0_LORA_CR_4_8_SHORT (0x04UL << 0) // 7 0 4/8 with short interleaver +#define RADIOLIB_LR11X0_LORA_CR_4_5_LONG (0x05UL << 0) // 7 0 4/5 with long interleaver +#define RADIOLIB_LR11X0_LORA_CR_4_6_LONG (0x06UL << 0) // 7 0 4/6 with long interleaver +#define RADIOLIB_LR11X0_LORA_CR_4_8_LONG (0x07UL << 0) // 7 0 4/8 with long interleaver +#define RADIOLIB_LR11X0_LORA_LDRO_DISABLED (0x00UL << 0) // 7 0 low data rate optimize: disabled +#define RADIOLIB_LR11X0_LORA_LDRO_ENABLED (0x01UL << 0) // 7 0 enabled +#define RADIOLIB_LR11X0_GFSK_BIT_RATE_DIV_DISABLED (0x00UL << 31) // 31 0 divide bit rate value by 256: disabled +#define RADIOLIB_LR11X0_GFSK_BIT_RATE_DIV_ENABLED (0x01UL << 31) // 31 0 enabled +#define RADIOLIB_LR11X0_GFSK_SHAPING_NONE (0x00UL << 0) // 7 0 shaping filter: none +#define RADIOLIB_LR11X0_GFSK_SHAPING_GAUSSIAN_BT_0_3 (0x08UL << 0) // 7 0 Gaussian, BT = 0.3 +#define RADIOLIB_LR11X0_GFSK_SHAPING_GAUSSIAN_BT_0_5 (0x09UL << 0) // 7 0 Gaussian, BT = 0.5 +#define RADIOLIB_LR11X0_GFSK_SHAPING_GAUSSIAN_BT_0_7 (0x0AUL << 0) // 7 0 Gaussian, BT = 0.7 +#define RADIOLIB_LR11X0_GFSK_SHAPING_GAUSSIAN_BT_1_0 (0x0BUL << 0) // 7 0 Gaussian, BT = 1.0 +#define RADIOLIB_LR11X0_GFSK_SHAPING_RAISED_COSINE_BT_0_7 (0x16UL << 0) // 7 0 raised cosine, BT = 0.7 +#define RADIOLIB_LR11X0_GFSK_RX_BW_4_8 (0x1FUL << 0) // 7 0 GFSK Rx bandwidth: 4.8 kHz +#define RADIOLIB_LR11X0_GFSK_RX_BW_5_8 (0x17UL << 0) // 7 0 5.8 kHz +#define RADIOLIB_LR11X0_GFSK_RX_BW_7_3 (0x0FUL << 0) // 7 0 7.3 kHz +#define RADIOLIB_LR11X0_GFSK_RX_BW_9_7 (0x1EUL << 0) // 7 0 9.7 kHz +#define RADIOLIB_LR11X0_GFSK_RX_BW_11_7 (0x16UL << 0) // 7 0 11.7 kHz +#define RADIOLIB_LR11X0_GFSK_RX_BW_14_6 (0x0EUL << 0) // 7 0 14.6 kHz +#define RADIOLIB_LR11X0_GFSK_RX_BW_19_5 (0x1DUL << 0) // 7 0 19.5 kHz +#define RADIOLIB_LR11X0_GFSK_RX_BW_23_4 (0x15UL << 0) // 7 0 23.4 kHz +#define RADIOLIB_LR11X0_GFSK_RX_BW_29_3 (0x0DUL << 0) // 7 0 29.3 kHz +#define RADIOLIB_LR11X0_GFSK_RX_BW_39_0 (0x1CUL << 0) // 7 0 39.0 kHz +#define RADIOLIB_LR11X0_GFSK_RX_BW_46_9 (0x14UL << 0) // 7 0 46.9 kHz +#define RADIOLIB_LR11X0_GFSK_RX_BW_58_6 (0x0CUL << 0) // 7 0 58.6 kHz +#define RADIOLIB_LR11X0_GFSK_RX_BW_78_2 (0x1BUL << 0) // 7 0 78.2 kHz +#define RADIOLIB_LR11X0_GFSK_RX_BW_93_8 (0x13UL << 0) // 7 0 93.8 kHz +#define RADIOLIB_LR11X0_GFSK_RX_BW_117_3 (0x0BUL << 0) // 7 0 117.3 kHz +#define RADIOLIB_LR11X0_GFSK_RX_BW_156_2 (0x1AUL << 0) // 7 0 156.2 kHz +#define RADIOLIB_LR11X0_GFSK_RX_BW_187_2 (0x12UL << 0) // 7 0 187.2 kHz +#define RADIOLIB_LR11X0_GFSK_RX_BW_234_3 (0x0AUL << 0) // 7 0 234.3 kHz +#define RADIOLIB_LR11X0_GFSK_RX_BW_312_0 (0x19UL << 0) // 7 0 312.0 kHz +#define RADIOLIB_LR11X0_GFSK_RX_BW_373_6 (0x11UL << 0) // 7 0 373.6 kHz +#define RADIOLIB_LR11X0_GFSK_RX_BW_467_0 (0x09UL << 0) // 7 0 467.0 kHz +#define RADIOLIB_LR11X0_LR_FHSS_BIT_RATE (488.28215) // 31 0 LR FHSS bit rate: 488.28215 bps +#define RADIOLIB_LR11X0_LR_FHSS_BIT_RATE_RAW (0x8001E848UL) // 31 0 488.28215 bps in raw +#define RADIOLIB_LR11X0_LR_FHSS_SHAPING_GAUSSIAN_BT_1_0 (0x0BUL << 0) // 7 0 shaping filter: Gaussian, BT = 1.0 +#define RADIOLIB_LR11X0_SIGFOX_SHAPING_GAUSSIAN_BT_0_7 (0x16UL << 0) // 7 0 shaping filter: Gaussian, BT = 0.7 + +// RADIOLIB_LR11X0_CMD_SET_PACKET_PARAMS +#define RADIOLIB_LR11X0_LORA_HEADER_EXPLICIT (0x00UL << 0) // 7 0 LoRa header mode: explicit +#define RADIOLIB_LR11X0_LORA_HEADER_IMPLICIT (0x01UL << 0) // 7 0 implicit +#define RADIOLIB_LR11X0_LORA_PAYLOAD_LEN_ANY (0x00UL << 0) // 7 0 accept any payload length +#define RADIOLIB_LR11X0_LORA_CRC_ENABLED (0x01UL << 0) // 7 0 CRC: enabled +#define RADIOLIB_LR11X0_LORA_CRC_DISABLED (0x00UL << 0) // 7 0 disabled +#define RADIOLIB_LR11X0_LORA_IQ_STANDARD (0x00UL << 0) // 7 0 IQ setup: standard +#define RADIOLIB_LR11X0_LORA_IQ_INVERTED (0x01UL << 0) // 7 0 inverted +#define RADIOLIB_LR11X0_GFSK_PREAMBLE_DETECT_DISABLED (0x00UL << 0) // 7 0 preamble detector: disabled +#define RADIOLIB_LR11X0_GFSK_PREAMBLE_DETECT_8_BITS (0x04UL << 0) // 7 0 8 bits +#define RADIOLIB_LR11X0_GFSK_PREAMBLE_DETECT_16_BITS (0x05UL << 0) // 7 0 16 bits +#define RADIOLIB_LR11X0_GFSK_PREAMBLE_DETECT_24_BITS (0x06UL << 0) // 7 0 24 bits +#define RADIOLIB_LR11X0_GFSK_PREAMBLE_DETECT_32_BITS (0x07UL << 0) // 7 0 32 bits +#define RADIOLIB_LR11X0_GFSK_ADDR_FILTER_DISABLED (0x00UL << 0) // 7 0 address filtering: disabled +#define RADIOLIB_LR11X0_GFSK_ADDR_FILTER_NODE (0x01UL << 0) // 7 0 node address +#define RADIOLIB_LR11X0_GFSK_ADDR_FILTER_NODE_BROADCAST (0x02UL << 0) // 7 0 node and broadcast address +#define RADIOLIB_LR11X0_GFSK_PACKET_LENGTH_FIXED (0x00UL << 0) // 7 0 packet length: fixed +#define RADIOLIB_LR11X0_GFSK_PACKET_LENGTH_VARIABLE (0x01UL << 0) // 7 0 variable +#define RADIOLIB_LR11X0_GFSK_PACKET_LENGTH_VARIABLE_SX128X (0x02UL << 0) // 7 0 variable, SX128x 9-bit length encoding +#define RADIOLIB_LR11X0_GFSK_PAYLOAD_LEN_ANY (0x00UL << 0) // 7 0 accept any payload length +#define RADIOLIB_LR11X0_GFSK_CRC_DISABLED (0x01UL << 0) // 7 0 CRC: disabled +#define RADIOLIB_LR11X0_GFSK_CRC_1_BYTE (0x00UL << 0) // 7 0 1-byte +#define RADIOLIB_LR11X0_GFSK_CRC_2_BYTE (0x02UL << 0) // 7 0 2-byte +#define RADIOLIB_LR11X0_GFSK_CRC_1_BYTE_INV (0x04UL << 0) // 7 0 1-byte, inverted +#define RADIOLIB_LR11X0_GFSK_CRC_2_BYTE_INV (0x06UL << 0) // 7 0 2-byte, inverted +#define RADIOLIB_LR11X0_GFSK_WHITENING_DISABLED (0x00UL << 0) // 7 0 whitening: disabled +#define RADIOLIB_LR11X0_GFSK_WHITENING_ENABLED (0x01UL << 0) // 7 0 enabled + +// RADIOLIB_LR11X0_CMD_SET_TX_PARAMS +#define RADIOLIB_LR11X0_PA_RAMP_48U (0x02UL << 0) // 7 0 PA ramp time: 48 us + +// RADIOLIB_LR11X0_CMD_SET_RX_TX_FALLBACK_MODE +#define RADIOLIB_LR11X0_FALLBACK_MODE_STBY_RC (0x01UL << 0) // 1 0 fallback mode after Rx/Tx: standby with RC +#define RADIOLIB_LR11X0_FALLBACK_MODE_STBY_XOSC (0x02UL << 0) // 1 0 standby with XOSC +#define RADIOLIB_LR11X0_FALLBACK_MODE_FS (0x03UL << 0) // 1 0 frequency synthesis + +// RADIOLIB_LR11X0_CMD_SET_RX_DUTY_CYCLE +#define RADIOLIB_LR11X0_RX_DUTY_CYCLE_MODE_RX (0x00UL << 0) // 0 0 mode in Rx windows: Rx (default) +#define RADIOLIB_LR11X0_RX_DUTY_CYCLE_MODE_CAD (0x01UL << 0) // 0 0 CAD +#define RADIOLIB_LR11X0_TIMING_STEP (1.0f/32768.0f) // 23 0 timing step fo delays + +// RADIOLIB_LR11X0_CMD_SET_PA_CONFIG +#define RADIOLIB_LR11X0_PA_SEL_LP (0x00UL << 0) // 7 0 PA select: low power PA +#define RADIOLIB_LR11X0_PA_SEL_HP (0x01UL << 0) // 7 0 high power PA +#define RADIOLIB_LR11X0_PA_SEL_HF (0x02UL << 0) // 7 0 high frequency PA +#define RADIOLIB_LR11X0_PA_SUPPLY_INTERNAL (0x00UL << 0) // 7 0 PA power source: internal +#define RADIOLIB_LR11X0_PA_SUPPLY_VBAT (0x01UL << 0) // 7 0 VBAT (required for >= 14 dBm) + +// RADIOLIB_LR11X0_CMD_STOP_TIMEOUT_ON_PREAMBLE +#define RADIOLIB_LR11X0_STOP_ON_SYNC_HEADER (0x00UL << 0) // 0 0 stop timeout on: sync word or header (default) +#define RADIOLIB_LR11X0_STOP_ON_PREAMBLE (0x01UL << 0) // 0 0 preamble + +// RADIOLIB_LR11X0_CMD_GET_RANGING_RESULT +#define RADIOLIB_LR11X0_RANGING_RESULT_DISTANCE (0) // 7 0 ranging result type: distance +#define RADIOLIB_LR11X0_RANGING_RESULT_RSSI (1) // 7 0 RSSI + +// RADIOLIB_LR11X0_CMD_SET_RX_BOOSTED +#define RADIOLIB_LR11X0_RX_BOOSTED_ENABLED (0x01UL << 0) // 0 0 Rx boosted mode: enabled +#define RADIOLIB_LR11X0_RX_BOOSTED_DISABLED (0x00UL << 0) // 0 0 disabled + +// RADIOLIB_LR11X0_CMD_SET_LORA_SYNC_WORD +#define RADIOLIB_LR11X0_LORA_SYNC_WORD_PRIVATE (0x12) +#define RADIOLIB_LR11X0_LORA_SYNC_WORD_PUBLIC (0x34) + +// RADIOLIB_LR11X0_CMD_LR_FHSS_BUILD_FRAME +#define RADIOLIB_LR11X0_LR_FHSS_CR_5_6 (0x00UL << 0) // 7 0 LR FHSS coding rate: 5/6 +#define RADIOLIB_LR11X0_LR_FHSS_CR_2_3 (0x01UL << 0) // 7 0 2/3 +#define RADIOLIB_LR11X0_LR_FHSS_CR_1_2 (0x02UL << 0) // 7 0 1/2 +#define RADIOLIB_LR11X0_LR_FHSS_CR_1_3 (0x03UL << 0) // 7 0 1/3 +#define RADIOLIB_LR11X0_LR_FHSS_MOD_TYPE_GMSK (0x00UL << 0) // 7 0 LR FHSS modulation: GMSK +#define RADIOLIB_LR11X0_LR_FHSS_GRID_STEP_FCC (0x00UL << 0) // 7 0 LR FHSS step size: 25.390625 kHz (FCC) +#define RADIOLIB_LR11X0_LR_FHSS_GRID_STEP_NON_FCC (0x01UL << 0) // 7 0 3.90625 kHz (non-FCC) +#define RADIOLIB_LR11X0_LR_FHSS_HOPPING_DISABLED (0x00UL << 0) // 7 0 LR FHSS hopping: disabled +#define RADIOLIB_LR11X0_LR_FHSS_HOPPING_ENABLED (0x01UL << 0) // 7 0 enabled +#define RADIOLIB_LR11X0_LR_FHSS_BW_39_06 (0x00UL << 0) // 7 0 LR FHSS bandwidth: 39.06 kHz +#define RADIOLIB_LR11X0_LR_FHSS_BW_85_94 (0x01UL << 0) // 7 0 85.94 kHz +#define RADIOLIB_LR11X0_LR_FHSS_BW_136_72 (0x02UL << 0) // 7 0 136.72 kHz +#define RADIOLIB_LR11X0_LR_FHSS_BW_183_59 (0x03UL << 0) // 7 0 183.59 kHz +#define RADIOLIB_LR11X0_LR_FHSS_BW_335_94 (0x04UL << 0) // 7 0 335.94 kHz +#define RADIOLIB_LR11X0_LR_FHSS_BW_386_72 (0x05UL << 0) // 7 0 386.72 kHz +#define RADIOLIB_LR11X0_LR_FHSS_BW_722_66 (0x06UL << 0) // 7 0 722.66 kHz +#define RADIOLIB_LR11X0_LR_FHSS_BW_773_44 (0x07UL << 0) // 7 0 773.44 kHz +#define RADIOLIB_LR11X0_LR_FHSS_BW_1523_4 (0x08UL << 0) // 7 0 1523.4 kHz +#define RADIOLIB_LR11X0_LR_FHSS_BW_1574_2 (0x09UL << 0) // 7 0 1574.2 kHz +#define RADIOLIB_LR11X0_LR_FHSS_HEADER_BITS (114) // 7 0 LR FHSS packet bit widths: header +#define RADIOLIB_LR11X0_LR_FHSS_FRAG_BITS (48) // 7 0 payload fragment +#define RADIOLIB_LR11X0_LR_FHSS_BLOCK_PREAMBLE_BITS (2) // 7 0 block preamble +#define RADIOLIB_LR11X0_LR_FHSS_BLOCK_BITS (RADIOLIB_LR11X0_LR_FHSS_FRAG_BITS + RADIOLIB_LR11X0_LR_FHSS_BLOCK_PREAMBLE_BITS) + +// RADIOLIB_LR11X0_CMD_GET_LORA_RX_HEADER_INFOS +#define RADIOLIB_LR11X0_LAST_HEADER_CRC_ENABLED (0x01UL << 4) // 4 4 last header CRC: enabled +#define RADIOLIB_LR11X0_LAST_HEADER_CRC_DISABLED (0x00UL << 4) // 4 4 disabled + +// RADIOLIB_LR11X0_CMD_WIFI_SCAN +#define RADIOLIB_LR11X0_WIFI_SCAN_802_11_B (0x01UL << 0) // 7 0 Wi-Fi type to scan: 802.11b +#define RADIOLIB_LR11X0_WIFI_SCAN_802_11_G (0x02UL << 0) // 7 0 802.11g +#define RADIOLIB_LR11X0_WIFI_SCAN_802_11_N (0x03UL << 0) // 7 0 802.11n +#define RADIOLIB_LR11X0_WIFI_SCAN_ALL (0x04UL << 0) // 7 0 all (802.11b first) +#define RADIOLIB_LR11X0_WIFI_ACQ_MODE_BEACON_ONLY (0x01UL << 0) // 7 0 Wi-Fi acquisition mode: beacon only +#define RADIOLIB_LR11X0_WIFI_ACQ_MODE_BEACON_PACKET (0x02UL << 0) // 7 0 beacon and packet +#define RADIOLIB_LR11X0_WIFI_ACQ_MODE_FULL_TRAFFIC (0x03UL << 0) // 7 0 full traffic +#define RADIOLIB_LR11X0_WIFI_ACQ_MODE_FULL_BEACON (0x04UL << 0) // 7 0 full beacon +#define RADIOLIB_LR11X0_WIFI_ACQ_MODE_SSID_BEACON (0x05UL << 0) // 7 0 SSID beacon +#define RADIOLIB_LR11X0_WIFI_ABORT_ON_TIMEOUT_ENABLED (0x01UL << 0) // 7 0 abort scanning on preamble timeout: enabled +#define RADIOLIB_LR11X0_WIFI_ABORT_ON_TIMEOUT_DISABLED (0x00UL << 0) // 7 0 disabled +#define RADIOLIB_LR11X0_WIFI_MAX_NUM_RESULTS (32) // 7 0 maximum possible number of Wi-Fi scan results +#define RADIOLIB_LR11X0_WIFI_ALL_CHANNELS (0x3FFFUL) // 16 0 scan all channels + +// RADIOLIB_LR11X0_CMD_WIFI_READ_RESULTS +#define RADIOLIB_LR11X0_WIFI_RESULT_TYPE_COMPLETE (0x01UL << 0) // 7 0 Wi-Fi scan result type: complete +#define RADIOLIB_LR11X0_WIFI_RESULT_TYPE_BASIC (0x04UL << 0) // 7 0 basic +#define RADIOLIB_LR11X0_WIFI_RESULT_MAX_LEN (79) // 7 0 maximum possible Wi-Fi scan size +#define RADIOLIB_LR11X0_WIFI_RESULT_MAC_LEN (6) // 7 0 MAC address length in bytes +#define RADIOLIB_LR11X0_WIFI_RESULT_SSID_LEN (32) // 7 0 SSID length in bytes + +// RADIOLIB_LR11X0_CMD_GNSS_SET_CONSTELLATION_TO_USE +#define RADIOLIB_LR11X0_GNSS_CONSTELLATION_GPS (0x01UL << 0) // 7 0 GNSS constellation to use: GPS +#define RADIOLIB_LR11X0_GNSS_CONSTELLATION_BEIDOU (0x01UL << 1) // 7 0 BeiDou + +// RADIOLIB_LR11X0_CMD_GNSS_SET_MODE +#define RADIOLIB_LR11X0_GNSS_MODE_SINGLE_SCAN (0x00UL << 0) // 7 0 GNSS scanning mode: single/legacy +#define RADIOLIB_LR11X0_GNSS_MODE_SINGLE_MULTIPLE (0x03UL << 1) // 7 0 multiple/advanced + +// RADIOLIB_LR11X0_CMD_GNSS_AUTONOMOUS +#define RADIOLIB_LR11X0_GNSS_RES_PSEUDO_DOPPLER_ENABLED (0x01UL << 0) // 0 0 GNSS results in NAV message: pseudo-range (in single scan mode) or Doppler information (in multiple scan mode) +#define RADIOLIB_LR11X0_GNSS_RES_PSEUDO_DOPPLER_DISABLED (0x00UL << 0) // 0 0 not included +#define RADIOLIB_LR11X0_GNSS_RES_DOPPLER_ENABLED (0x01UL << 1) // 1 1 Doppler information +#define RADIOLIB_LR11X0_GNSS_RES_DOPPLER_DISABLED (0x00UL << 1) // 1 1 not included +#define RADIOLIB_LR11X0_GNSS_NB_SV_ALL (0x00UL << 0) // 7 0 include all detected satellites +#define RADIOLIB_LR11X0_GNSS_AUTO_EFFORT_MODE (0x00UL << 0) // 7 0 reserved, always 0 + +// RADIOLIB_LR11X0_CMD_GNSS_ASSISTED +#define RADIOLIB_LR11X0_GNSS_ASSIST_LOW_POWER (0x00UL << 0) // 7 0 effort mode: low power +#define RADIOLIB_LR11X0_GNSS_ASSIST_BEST_EFFORT (0x01UL << 0) // 7 0 best effort + +// RADIOLIB_LR11X0_CMD_GNSS_GET_CONTEXT_STATUS +#define RADIOLIB_LR11X0_GNSS_CONTEXT_ERR_NONE (0x00UL << 0) // 7 4 error code: none +#define RADIOLIB_LR11X0_GNSS_CONTEXT_ERR_ALMANAC_OLD (0x01UL << 0) // 7 4 almanac too old +#define RADIOLIB_LR11X0_GNSS_CONTEXT_ERR_ALMANAC_CRC (0x02UL << 0) // 7 4 almanac CRC mismatch +#define RADIOLIB_LR11X0_GNSS_CONTEXT_ERR_FLASH (0x03UL << 0) // 7 4 flash integrity error +#define RADIOLIB_LR11X0_GNSS_CONTEXT_ERR_ALMANAC_UPD (0x04UL << 0) // 7 4 almanac update not allowed +#define RADIOLIB_LR11X0_GNSS_CONTEXT_FREQ_SPACE_250_HZ (0x00UL << 0) // 8 7 frequency search space: 250 Hz +#define RADIOLIB_LR11X0_GNSS_CONTEXT_FREQ_SPACE_500_HZ (0x01UL << 0) // 8 7 500 Hz +#define RADIOLIB_LR11X0_GNSS_CONTEXT_FREQ_SPACE_1000_HZ (0x02UL << 0) // 8 7 1000 Hz +#define RADIOLIB_LR11X0_GNSS_CONTEXT_FREQ_SPACE_2000_HZ (0x03UL << 0) // 8 7 2000 Hz + +// RADIOLIB_LR11X0_CMD_GNSS_GET_SV_VISIBLE +#define RADIOLIB_LR11X0_SV_CONSTELLATION_GPS (0x00UL << 0) // 7 0 GNSS constellation: GPS +#define RADIOLIB_LR11X0_SV_CONSTELLATION_BEIDOU (0x01UL << 0) // 7 0 BeiDou + +// RADIOLIB_LR11X0_CMD_GNSS_ALMANAC_FULL_UPDATE +#define RADIOLIB_LR11X0_GNSS_ALMANAC_HEADER_ID (0x80UL << 0) // 7 0 starting byte of GNSS almanac header +#define RADIOLIB_LR11X0_GNSS_ALMANAC_BLOCK_SIZE (20) + +// RADIOLIB_LR11X0_CMD_GNSS_FETCH_TIME +#define RADIOLIB_LR11X0_GNSS_EFFORT_LOW (0x00UL << 0) // 7 0 GNSS effort mode: low sensitivity +#define RADIOLIB_LR11X0_GNSS_EFFORT_MID (0x01UL << 0) // 7 0 medium sensitivity +#define RADIOLIB_LR11X0_GNSS_FETCH_TIME_OPT_TOW (0x00UL << 0) // 7 0 time fetch options: ToW only, requires WN to demodulated beforehand +#define RADIOLIB_LR11X0_GNSS_FETCH_TIME_OPT_TOW_WN (0x01UL << 0) // 7 0 ToW and WN +#define RADIOLIB_LR11X0_GNSS_FETCH_TIME_OPT_TOW_WN_ROLL (0x02UL << 0) // 7 0 ToW, WN and rollover + +// RADIOLIB_LR11X0_CMD_GNSS_READ_DEMOD_STATUS +#define RADIOLIB_LR11X0_GNSS_DEMOD_STATUS_NOT_POSSIBLE (-21) // 7 0 GNSS demodulation status: not possible to demodulate +#define RADIOLIB_LR11X0_GNSS_DEMOD_STATUS_SAT_LOST (-20) // 7 0 satellite lost +#define RADIOLIB_LR11X0_GNSS_DEMOD_STATUS_ALMANAC_DEMOD_ERROR (-19) // 7 0 almanac demodulation error +#define RADIOLIB_LR11X0_GNSS_DEMOD_STATUS_TOO_LATE (-18) // 7 0 woke up after preamble (demodulation started too late) +#define RADIOLIB_LR11X0_GNSS_DEMOD_STATUS_20_MS_FAIL (-17) // 7 0 20ms real-time clock failed +#define RADIOLIB_LR11X0_GNSS_DEMOD_STATUS_WAKE_UP_FAIL (-16) // 7 0 wake up sync failed +#define RADIOLIB_LR11X0_GNSS_DEMOD_STATUS_WN_INVALID (-15) // 7 0 week number not validated +#define RADIOLIB_LR11X0_GNSS_DEMOD_STATUS_NO_ACTIVE_SAT (-14) // 7 0 no active satellite selected in satellite list +#define RADIOLIB_LR11X0_GNSS_DEMOD_STATUS_SLEEP_TOO_LONG (-13) // 7 0 sleep time too long +#define RADIOLIB_LR11X0_GNSS_DEMOD_STATUS_TOW_INVALID (-12) // 7 0 wrong time-of-week demodulated +#define RADIOLIB_LR11X0_GNSS_DEMOD_STATUS_PREAMBLE_INVALID (-11) // 7 0 preamble not validated +#define RADIOLIB_LR11X0_GNSS_DEMOD_STATUS_DISABLED (-10) // 7 0 demodulator disabled +#define RADIOLIB_LR11X0_GNSS_DEMOD_STATUS_EXTR_FAILED (-9) // 7 0 demodulator extraction failed +#define RADIOLIB_LR11X0_GNSS_DEMOD_STATUS_NO_BIT_CHANGE (-8) // 7 0 no bit change found during demodulation start +#define RADIOLIB_LR11X0_GNSS_DEMOD_STATUS_NO_BIT_CHANGE_ADV (-7) // 7 0 no bit change found during advanced scan +#define RADIOLIB_LR11X0_GNSS_DEMOD_STATUS_NO_SAT_FOUND (-6) // 7 0 no satellites found +#define RADIOLIB_LR11X0_GNSS_DEMOD_STATUS_SYNC_LOST (-5) // 7 0 word sync lost +#define RADIOLIB_LR11X0_GNSS_DEMOD_STATUS_PARITY_NOT_ENOUGH (-3) // 7 0 parity check fail (not enough) +#define RADIOLIB_LR11X0_GNSS_DEMOD_STATUS_PARITY_TOO_MANY (-2) // 7 0 parity check fail (too many) +#define RADIOLIB_LR11X0_GNSS_DEMOD_STATUS_NO_PARITY (-1) // 7 0 parity check fail (no parity found) +#define RADIOLIB_LR11X0_GNSS_DEMOD_STATUS_WORD_SYNC_NONE (0) // 7 0 word sync search not started +#define RADIOLIB_LR11X0_GNSS_DEMOD_STATUS_WORD_SYNC_POT (1) // 7 0 potential word sync found +#define RADIOLIB_LR11X0_GNSS_DEMOD_STATUS_WORD_SYNC_OK (2) // 7 0 word sync found +#define RADIOLIB_LR11X0_GNSS_DEMOD_STATUS_TOW_FOUND (3) // 7 0 time-of-week found +#define RADIOLIB_LR11X0_GNSS_DEMOD_STATUS_WN_FOUND (4) // 7 0 week number and time-of-week found +#define RADIOLIB_LR11X0_GNSS_DEMOD_STATUS_ALM_FOUND_UNSAVED (5) // 7 0 almanac found but not saved +#define RADIOLIB_LR11X0_GNSS_DEMOD_STATUS_HALF_ALM_SAVED (6) // 7 0 half of almanac found and saved +#define RADIOLIB_LR11X0_GNSS_DEMOD_STATUS_FULL_ALM_SAVED (7) // 7 0 full almanac found and saved +#define RADIOLIB_LR11X0_GNSS_DEMOD_INFO_WORD_SYNC_FOUND (0x01UL << 0) // 7 0 GNSS demodulation info: word synchronization found +#define RADIOLIB_LR11X0_GNSS_DEMOD_INFO_TOW_FOUND (0x01UL << 1) // 7 0 time-of-week found +#define RADIOLIB_LR11X0_GNSS_DEMOD_INFO_WN_DEMODED (0x01UL << 2) // 7 0 week number demodulated +#define RADIOLIB_LR11X0_GNSS_DEMOD_INFO_WN_FOUND (0x01UL << 3) // 7 0 week number found +#define RADIOLIB_LR11X0_GNSS_DEMOD_INFO_SUBFRAME_1_FOUND (0x01UL << 4) // 7 0 subframe 1 found +#define RADIOLIB_LR11X0_GNSS_DEMOD_INFO_SUBFRAME_4_FOUND (0x01UL << 5) // 7 0 subframe 4 found +#define RADIOLIB_LR11X0_GNSS_DEMOD_INFO_SUBFRAME_5_FOUND (0x01UL << 6) // 7 0 subframe 5 found + +// RADIOLIB_LR11X0_CMD_GNSS_READ_ALMANAC_STATUS +#define RADIOLIB_LR11X0_GNSS_ALMANAC_STATUS_UP_TO_DATE (0) // 7 0 GPS/BeiDou almanac status: all satellites up-to-date +#define RADIOLIB_LR11X0_GNSS_ALMANAC_STATUS_OUTDATED (1) // 7 0 at least one satellite needs update + +// RADIOLIB_LR11X0_CMD_GNSS_READ_DOPPLER_SOLVER_RES +#define RADIOLIB_LR11X0_GNSS_SOLVER_ERR_NONE (0) // 7 0 internal 2D solver error: no error +#define RADIOLIB_LR11X0_GNSS_SOLVER_ERR_RES_HIGH (1) // 7 0 residue too high +#define RADIOLIB_LR11X0_GNSS_SOLVER_ERR_NOT_CONVERGED (2) // 7 0 not converged on solution +#define RADIOLIB_LR11X0_GNSS_SOLVER_ERR_NOT_ENOUGH_SV (3) // 7 0 not enough satellites +#define RADIOLIB_LR11X0_GNSS_SOLVER_ERR_ILL_MATRIX (4) // 7 0 matrix error (?) +#define RADIOLIB_LR11X0_GNSS_SOLVER_ERR_TIME (5) // 7 0 time error +#define RADIOLIB_LR11X0_GNSS_SOLVER_ERR_ALM_PART_OLD (6) // 7 0 part of almanac too old or not available +#define RADIOLIB_LR11X0_GNSS_SOLVER_ERR_INCONSISTENT (7) // 7 0 not consistent with history (?) +#define RADIOLIB_LR11X0_GNSS_SOLVER_ERR_ALM_OLD (8) // 7 0 all of almanac too old + +// RADIOLIB_LR11X0_CMD_CRYPTO_SET_KEY +#define RADIOLIB_LR11X0_CRYPTO_STATUS_SUCCESS (0x00UL << 0) // 7 0 crypto engine status: success +#define RADIOLIB_LR11X0_CRYPTO_STATUS_FAIL_CMAC (0x01UL << 0) // 7 0 MIC check failed +#define RADIOLIB_LR11X0_CRYPTO_STATUS_INV_KEY_ID (0x03UL << 0) // 7 0 key/parameter source or destination ID error +#define RADIOLIB_LR11X0_CRYPTO_STATUS_BUF_SIZE (0x05UL << 0) // 7 0 data buffer size invalid +#define RADIOLIB_LR11X0_CRYPTO_STATUS_ERROR (0x06UL << 0) // 7 0 generic error + +// RADIOLIB_LR11X0_CMD_CRYPTO_PROCESS_JOIN_ACCEPT +#define RADIOLIB_LR11X0_CRYPTO_LORAWAN_VERSION_1_0 (0x00UL << 0) // 7 0 LoRaWAN version: 1.0.x +#define RADIOLIB_LR11X0_CRYPTO_LORAWAN_VERSION_1_1 (0x01UL << 0) // 7 0 1.1 + +// LR11X0 SPI register variables + +// RADIOLIB_LR11X0_REG_SF6_SX127X_COMPAT +#define RADIOLIB_LR11X0_SF6_SX126X (0x00UL << 18) // 18 18 SF6 mode: SX126x series +#define RADIOLIB_LR11X0_SF6_SX127X (0x01UL << 18) // 18 18 SX127x series + +// RADIOLIB_LR11X0_REG_LORA_HIGH_POWER_FIX +#define RADIOLIB_LR11X0_LORA_HIGH_POWER_FIX (0x00UL << 30) // 30 30 fix for errata + +// RADIOLIB_LR11X0_REG_LNA_MODE +#define RADIOLIB_LR11X0_LNA_MODE_SINGLE_RFI_N (0x01UL << 4) // 7 4 LNA mode: single-ended RFI_N +#define RADIOLIB_LR11X0_LNA_MODE_SINGLE_RFI_P (0x02UL << 4) // 7 4 single-ended RFI_P +#define RADIOLIB_LR11X0_LNA_MODE_DIFFERENTIAL (0x03UL << 4) // 7 4 differential (default) + +/*! + \struct LR11x0WifiResult_t + \brief Structure to save result of passive WiFi scan. + This result only saves the basic information. +*/ +struct LR11x0WifiResult_t { + /*! \brief WiFi (802.11) signal type, 'b', 'n' or 'g' */ + char type; + + /*! \brief Data rate ID holding information about modulation and coding rate. See LR11x0 user manual for details. */ + uint8_t dataRateId; + + /*! \brief Channel frequency in MHz */ + uint16_t channelFreq; + + /*! \brief MAC address origin: from gateway (1), phone (2) or undetermined (3) */ + uint8_t origin; + + /*! \brief Whether this signal was sent by an access point (true) or end device (false) */ + bool ap; + + /*! \brief RSSI in dBm */ + float rssi; + + /*! \brief MAC address */ + uint8_t mac[RADIOLIB_LR11X0_WIFI_RESULT_MAC_LEN]; +}; + +/*! + \struct LR11x0WifiResultFull_t + \brief Structure to save result of passive WiFi scan. + This result saves additional information alongside that in LR11x0WifiResult_t. +*/ +struct LR11x0WifiResultFull_t: public LR11x0WifiResult_t { + /*! \brief Frame type. See LR11x0 user manual for details. */ + uint8_t frameType; + + /*! \brief Frame sub type. See LR11x0 user manual for details. */ + uint8_t frameSubType; + + /*! \brief Frame sent from client station to distribution system. */ + bool toDistributionSystem; + + /*! \brief Frame sent from distribution system to client station. */ + bool fromDistributionSystem; + + /*! \brief See LR11x0 user manual for details. */ + uint16_t phiOffset; + + /*! \brief Number of microseconds the AP has been active. */ + uint64_t timestamp; + + /*! \brief Beacon period in microseconds. */ + uint32_t periodBeacon; +}; + +/*! + \struct LR11x0WifiResultExtended_t + \brief Structure to save result of passive WiFi scan. + This result saves additional information alongside that in LR11x0WifiResultFull_t. + Only scans performed with RADIOLIB_LR11X0_WIFI_ACQ_MODE_FULL_BEACON acquisition mode + can yield this result! +*/ +struct LR11x0WifiResultExtended_t: public LR11x0WifiResultFull_t { + /*! \brief Data rate. See LR11x0 user manual for details. */ + uint8_t rate; + + /*! \brief Refer to IEEE Std 802.11, 2016, Part 11: Wireless LAN MAC and PHY Spec. */ + uint16_t service; + + /*! \brief Refer to IEEE Std 802.11, 2016, Part 11: Wireless LAN MAC and PHY Spec. */ + uint16_t length; + + /*! \brief MAC address 0 */ + uint8_t mac0[RADIOLIB_LR11X0_WIFI_RESULT_MAC_LEN]; + + /*! \brief MAC address 2 */ + uint8_t mac2[RADIOLIB_LR11X0_WIFI_RESULT_MAC_LEN]; + + /*! \brief Refer to IEEE Std 802.11, 2016, Part 11: Wireless LAN MAC and PHY Spec. */ + uint16_t seqCtrl; + + /*! \brief SSID */ + uint8_t ssid[RADIOLIB_LR11X0_WIFI_RESULT_SSID_LEN]; + + /*! \brief WiFi channel number */ + uint8_t currentChannel; + + /*! \brief Two-letter country code (null-terminated string). */ + char countryCode[3]; + + /*! \brief Refer to IEEE Std 802.11, 2016, Part 11: Wireless LAN MAC and PHY Spec. */ + uint8_t ioReg; + + /*! \brief True if frame check sequences is valid, false otherwise. */ + bool fcsCheckOk; +}; + +/*! + \struct LR11x0VersionInfo_t + \brief Structure to report information about versions of the LR11x0 hardware and firmware. +*/ +struct LR11x0VersionInfo_t { + /*! \brief Hardware revision. */ + uint8_t hardware; + + /*! \brief Which device this is - one of RADIOLIB_LR11X0_DEVICE_* macros. */ + uint8_t device; + + /*! \brief Major revision of the base firmware. */ + uint8_t fwMajor; + + /*! \brief Minor revision of the base firmware. */ + uint8_t fwMinor; + + /*! \brief Major revision of the WiFi firmware. */ + uint8_t fwMajorWiFi; + + /*! \brief Minor revision of the WiFi firmware. */ + uint8_t fwMinorWiFi; + + /*! \brief Revision of the GNSS firmware. */ + uint8_t fwGNSS; + + /*! \brief Almanac revision of the GNSS firmware. */ + uint8_t almanacGNSS; +}; + +/*! + \struct LR11x0GnssResult_t + \brief Structure to report information results of a GNSS scan. +*/ +struct LR11x0GnssResult_t { + /*! \brief Demodulator status. One of RADIOLIB_LR11X0_GNSS_DEMOD_STATUS_* */ + int8_t demodStat; + + /*! \brief Number of satellites detected during the scan. */ + uint8_t numSatsDet; + + /*! \brief Result size, used when passing data to LoRa cloud. */ + uint16_t resSize; +}; + +/*! + \struct LR11x0GnssPosition_t + \brief Structure to report position from LR11x0 internal solver. +*/ +struct LR11x0GnssPosition_t { + /*! \brief Latitude in degrees. */ + float latitude; + + /*! \brief Longitude in degrees. */ + float longitude; + + /*! \brief Accuracy of this result. */ + uint16_t accuracy; + + /*! \brief Number of satellites used to solve this position. */ + uint8_t numSatsUsed; +}; + +/*! + \struct LR11x0GnssSatellite_t + \brief Structure to save information about a satellite found during GNSS scan. +*/ +struct LR11x0GnssSatellite_t { + /*! \brief Satellite vehicle (SV) identifier. */ + uint8_t svId; + + /*! \brief C/N0 in dB. */ + uint8_t c_n0; + + /*! \brief Doppler shift of the signal in Hz. */ + int16_t doppler; +}; + +/*! + \struct LR11x0GnssAlmanacStatusPart_t + \brief Structure to save information about one constellation of the GNSS almanac. +*/ +struct LR11x0GnssAlmanacStatusPart_t { + int8_t status; + uint32_t timeUntilSubframe; + uint8_t numSubframes; + uint8_t nextSubframe4SvId; + uint8_t nextSubframe5SvId; + uint8_t nextSubframeStart; + uint8_t numUpdateNeeded; + uint32_t flagsUpdateNeeded[2]; + uint32_t flagsActive[2]; +}; + +/*! + \struct LR11x0GnssAlmanacStatus_t + \brief Structure to save information about the GNSS almanac. + This is not the actual almanac, just some context information about it. +*/ +struct LR11x0GnssAlmanacStatus_t { + /*! \brief GPS part of the almanac */ + LR11x0GnssAlmanacStatusPart_t gps; + + /*! \brief BeiDou part of the almanac */ + LR11x0GnssAlmanacStatusPart_t beidou; + + /*! \brief Extra flags present for BeiDou only */ + uint32_t beidouSvNoAlmanacFlags[2]; + + /*! \brief Next almanac ID */ + uint8_t nextAlmanacId; + + /*! \brief Timestamp of when almanac status was retrieved - timeUntilSubframe is relative to this value. */ + RadioLibTime_t start; +}; + +/*! + \class LR11x0 + \brief Base class for %LR11x0 series. All derived classes for %LR11x0 (e.g. LR1110 or LR1120) inherit from this base class. + This class should not be instantiated directly from user code, only from its derived classes. +*/ +class LR11x0: public PhysicalLayer { + public: + // introduce PhysicalLayer overloads + using PhysicalLayer::transmit; + using PhysicalLayer::receive; + using PhysicalLayer::startTransmit; + using PhysicalLayer::readData; + + /*! + \brief Default constructor. + \param mod Instance of Module that will be used to communicate with the radio. + */ + explicit LR11x0(Module* mod); + + /*! + \brief Custom operation modes for LR11x0. + Needed because LR11x0 has several modems (sub-GHz, 2.4 GHz etc.) in one package + */ + enum OpMode_t { + /*! End of table marker, use \ref END_OF_MODE_TABLE constant instead */ + MODE_END_OF_TABLE = Module::MODE_END_OF_TABLE, + /*! Standby/idle mode */ + MODE_STBY = Module::MODE_IDLE, + /*! Receive mode */ + MODE_RX = Module::MODE_RX, + /*! Low power transmission mode */ + MODE_TX = Module::MODE_TX, + /*! High power transmission mode */ + MODE_TX_HP, + /*! High frequency transmission mode */ + MODE_TX_HF, + /*! GNSS scanning mode */ + MODE_GNSS, + /*! WiFi scanning mode */ + MODE_WIFI, + }; + + /*! + \brief Whether the module has an XTAL (true) or TCXO (false). Defaults to false. + */ + bool XTAL; + + /*! + \brief Initialization method for LoRa modem. + \param bw LoRa bandwidth in kHz. + \param sf LoRa spreading factor. + \param cr LoRa coding rate denominator. + \param syncWord 1-byte LoRa sync word. + \param preambleLength LoRa preamble length in symbols + \param tcxoVoltage TCXO reference voltage to be set. + \param high defaults to false for Sub-GHz band, true for frequencies above 1GHz + \returns \ref status_codes + */ + int16_t begin(float bw, uint8_t sf, uint8_t cr, uint8_t syncWord, uint16_t preambleLength, float tcxoVoltage, bool high = false); + + /*! + \brief Initialization method for FSK modem. + \param br FSK bit rate in kbps. + \param freqDev Frequency deviation from carrier frequency in kHz. + \param rxBw Receiver bandwidth in kHz. + \param preambleLength FSK preamble length in bits. + \param tcxoVoltage TCXO reference voltage to be set. + \returns \ref status_codes + */ + int16_t beginGFSK(float br, float freqDev, float rxBw, uint16_t preambleLength, float tcxoVoltage); + + /*! + \brief Initialization method for LR-FHSS modem. + \param bw LR-FHSS bandwidth, one of RADIOLIB_LR11X0_LR_FHSS_BW_* values. + \param cr LR-FHSS coding rate, one of RADIOLIB_LR11X0_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. + \returns \ref status_codes + */ + int16_t beginLRFHSS(uint8_t bw, uint8_t cr, bool narrowGrid, float tcxoVoltage); + + /*! + \brief Initialization method for GNSS scanning. + \param constellations GNSS constellations to use (GPS, BeiDou or both). Defaults to both. + \param tcxoVoltage TCXO reference voltage to be set. + \returns \ref status_codes + */ + int16_t beginGNSS(uint8_t constellations = RADIOLIB_LR11X0_GNSS_CONSTELLATION_GPS | RADIOLIB_LR11X0_GNSS_CONSTELLATION_BEIDOU, float tcxoVoltage = 1.6); + + /*! + \brief Reset method. Will reset the chip to the default state using RST pin. + \returns \ref status_codes + */ + int16_t reset(); + + /*! + \brief Blocking binary transmit method. + Overloads for string-based transmissions are implemented in PhysicalLayer. + \param data Binary data to be sent. + \param len Number of bytes to send. + \param addr Address to send the data to. Will only be added if address filtering was enabled. + \returns \ref status_codes + */ + int16_t transmit(const uint8_t* data, size_t len, uint8_t addr = 0) override; + + /*! + \brief Blocking binary receive method. + Overloads for string-based transmissions are implemented in PhysicalLayer. + \param data Binary data to be sent. + \param len Number of bytes to send. + \returns \ref status_codes + */ + int16_t receive(uint8_t* data, size_t len) override; + + /*! + \brief Starts direct mode transmission. + \param frf Raw RF frequency value. Defaults to 0, required for quick frequency shifts in RTTY. + \returns \ref status_codes + */ + int16_t transmitDirect(uint32_t frf = 0) override; + + /*! + \brief Starts direct mode reception. Only implemented for PhysicalLayer compatibility, as %SX126x series does not support direct mode reception. + Will always return RADIOLIB_ERR_UNKNOWN. + \returns \ref status_codes + */ + int16_t receiveDirect() override; + + /*! + \brief Performs scan for LoRa transmission in the current channel. Detects both preamble and payload. + \returns \ref status_codes + */ + int16_t scanChannel() override; + + /*! + \brief Performs scan for LoRa transmission in the current channel. Detects both preamble and payload. + \param config CAD configuration structure. + \returns \ref status_codes + */ + int16_t scanChannel(const ChannelScanConfig_t &config) override; + + /*! + \brief Sets the module to standby mode (overload for PhysicalLayer compatibility, uses 13 MHz RC oscillator). + \returns \ref status_codes + */ + int16_t standby() override; + + /*! + \brief Sets the module to standby mode. + \param mode Oscillator to be used in standby mode. Can be set to RADIOLIB_LR11X0_STANDBY_RC (13 MHz RC oscillator) + or RADIOLIB_LR11X0_STANDBY_XOSC (32 MHz external crystal oscillator). + \param wakeup Whether to force the module to wake up. Setting to true will immediately attempt to wake up the module. + \returns \ref status_codes + */ + int16_t standby(uint8_t mode, bool wakeup = true); + + /*! + \brief Sets the module to sleep mode. To wake the device up, call standby(). + Overload with warm start enabled for PhysicalLayer compatibility. + \returns \ref status_codes + */ + int16_t sleep() override; + + /*! + \brief Sets the module to sleep mode. To wake the device up, call standby(). + \param retainConfig Set to true to retain configuration of the currently active modem ("warm start") + or to false to discard current configuration ("cold start"). Defaults to true. + \param sleepTime Sleep duration (enables automatic wakeup), in multiples of 30.52 us. Ignored if set to 0. + \returns \ref status_codes + */ + int16_t sleep(bool retainConfig, uint32_t sleepTime); + + // interrupt methods + + /*! + \brief Sets interrupt service routine to call when IRQ1 activates. + \param func ISR to call. + */ + void setIrqAction(void (*func)(void)); + + /*! + \brief Clears interrupt service routine to call when IRQ1 activates. + */ + void clearIrqAction(); + + /*! + \brief Sets interrupt service routine to call when a packet is received. + \param func ISR to call. + */ + void setPacketReceivedAction(void (*func)(void)) override; + + /*! + \brief Clears interrupt service routine to call when a packet is received. + */ + void clearPacketReceivedAction() override; + + /*! + \brief Sets interrupt service routine to call when a packet is sent. + \param func ISR to call. + */ + void setPacketSentAction(void (*func)(void)) override; + + /*! + \brief Clears interrupt service routine to call when a packet is sent. + */ + void clearPacketSentAction() override; + + /*! + \brief Interrupt-driven binary transmit method. + Overloads for string-based transmissions are implemented in PhysicalLayer. + \param data Binary data to be sent. + \param len Number of bytes to send. + \param addr Address to send the data to. Will only be added if address filtering was enabled. + \returns \ref status_codes + */ + int16_t startTransmit(const uint8_t* data, size_t len, uint8_t addr = 0) override; + + /*! + \brief Clean up after transmission is done. + \returns \ref status_codes + */ + int16_t finishTransmit() override; + + /*! + \brief Interrupt-driven receive method with default parameters. + Implemented for compatibility with PhysicalLayer. + + \returns \ref status_codes + */ + int16_t startReceive() override; + + /*! + \brief Interrupt-driven receive method. IRQ1 will be activated when full packet is received. + \param timeout Raw timeout value, expressed as multiples of 1/32.768 kHz (approximately 30.52 us). + Defaults to RADIOLIB_LR11X0_RX_TIMEOUT_INF for infinite timeout (Rx continuous mode), + set to RADIOLIB_LR11X0_RX_TIMEOUT_NONE for no timeout (Rx single mode). + If timeout other than infinite is set, signal will be generated on IRQ1. + + \param irqFlags Sets the IRQ flags that will trigger IRQ1, defaults to RADIOLIB_LR11X0_IRQ_RX_DONE. + \param irqMask Only for PhysicalLayer compatibility, not used. + \param len Only for PhysicalLayer compatibility, not used. + \returns \ref status_codes + */ + int16_t startReceive(uint32_t timeout, uint32_t irqFlags = RADIOLIB_LR11X0_IRQ_RX_DONE, uint32_t irqMask = 0, size_t len = 0); + + /*! + \brief Reads the current IRQ status. + \returns IRQ status bits + */ + uint32_t getIrqStatus(); + + /*! + \brief Reads data received after calling startReceive method. When the packet length is not known in advance, + getPacketLength method must be called BEFORE calling readData! + \param data Pointer to array to save the received binary data. + \param len Number of bytes that will be read. When set to 0, the packet length will be retrieved automatically. + When more bytes than received are requested, only the number of bytes requested will be returned. + \returns \ref status_codes + */ + int16_t readData(uint8_t* data, size_t len) override; + + /*! + \brief Interrupt-driven channel activity detection method. IRQ1 will be activated + when LoRa preamble is detected, or upon timeout. Defaults to CAD parameter values recommended by AN1200.48. + \returns \ref status_codes + */ + int16_t startChannelScan() override; + + /*! + \brief Interrupt-driven channel activity detection method. IRQ pin will be activated + when LoRa preamble is detected, or upon timeout. + \param config CAD configuration structure. + \returns \ref status_codes + */ + int16_t startChannelScan(const ChannelScanConfig_t &config) override; + + /*! + \brief Read the channel scan result + \returns \ref status_codes + */ + int16_t getChannelScanResult() override; + + // configuration methods + + /*! + \brief Sets LoRa bandwidth. Allowed values are 62.5, 125.0, 250.0 and 500.0 kHz. (default, high = false) + \param bw LoRa bandwidth to be set in kHz. + \param high if set to true, allowed bandwidth is 203.125, 406.25 and 812.5 kHz, frequency must be above 1GHz + \returns \ref status_codes + */ + int16_t setBandwidth(float bw, bool high = false); + + /*! + \brief Sets LoRa spreading factor. Allowed values range from 5 to 12. + \param sf LoRa spreading factor to be set. + \param legacy Enable legacy mode for SF6 - this allows to communicate with SX127x at SF6. + \returns \ref status_codes + */ + int16_t setSpreadingFactor(uint8_t sf, bool legacy = false); + + /*! + \brief Sets LoRa coding rate denominator. Allowed values range from 5 to 8. + \param cr LoRa coding rate denominator to be set. + \param longInterleave Enable long interleaver when set to true. + Note that CR 4/7 is not possible with long interleaver enabled! + \returns \ref status_codes + */ + int16_t setCodingRate(uint8_t cr, bool longInterleave = false); + + /*! + \brief Sets LoRa sync word. + \param syncWord LoRa sync word to be set. + \returns \ref status_codes + */ + int16_t setSyncWord(uint8_t syncWord); + + /*! + \brief Sets GFSK bit rate. Allowed values range from 0.6 to 300.0 kbps. + \param br FSK bit rate to be set in kbps. + \returns \ref status_codes + */ + int16_t setBitRate(float br) override; + + /*! + \brief Sets GFSK frequency deviation. Allowed values range from 0.0 to 200.0 kHz. + \param freqDev GFSK frequency deviation to be set in kHz. + \returns \ref status_codes + */ + int16_t setFrequencyDeviation(float freqDev) override; + + /*! + \brief Sets GFSK receiver bandwidth. Allowed values are 4.8, 5.8, 7.3, 9.7, 11.7, 14.6, 19.5, + 23.4, 29.3, 39.0, 46.9, 58.6, 78.2, 93.8, 117.3, 156.2, 187.2, 234.3, 312.0, 373.6 and 467.0 kHz. + \param rxBw GFSK receiver bandwidth to be set in kHz. + \returns \ref status_codes + */ + int16_t setRxBandwidth(float rxBw); + + /*! + \brief Sets GFSK sync word in the form of array of up to 8 bytes. + \param syncWord GFSK sync word to be set. + \param len GFSK sync word length in bytes. + \returns \ref status_codes + */ + int16_t setSyncWord(uint8_t* syncWord, size_t len) override; + + /*! + \brief Sets GFSK sync word in the form of array of up to 8 bytes. + \param syncWord GFSK sync word to be set. + \param bitsLen GFSK sync word length in bits. If length is not divisible by 8, + least significant bits of syncWord will be ignored. + \returns \ref status_codes + */ + int16_t setSyncBits(uint8_t *syncWord, uint8_t bitsLen); + + /*! + \brief Sets node address. Calling this method will also enable address filtering for node address only. + \param nodeAddr Node address to be set. + \returns \ref status_codes + */ + int16_t setNodeAddress(uint8_t nodeAddr); + + /*! + \brief Sets broadcast address. Calling this method will also enable address + filtering for node and broadcast address. + \param broadAddr Node address to be set. + \returns \ref status_codes + */ + int16_t setBroadcastAddress(uint8_t broadAddr); + + /*! + \brief Disables address filtering. Calling this method will also erase previously set addresses. + \returns \ref status_codes + */ + int16_t disableAddressFiltering(); + + /*! + \brief Sets time-bandwidth product of Gaussian filter applied for shaping. + Allowed values are RADIOLIB_SHAPING_0_3, RADIOLIB_SHAPING_0_5, RADIOLIB_SHAPING_0_7 or RADIOLIB_SHAPING_1_0. + Set to RADIOLIB_SHAPING_NONE to disable data shaping. + \param sh Time-bandwidth product of Gaussian filter to be set. + \returns \ref status_codes + */ + int16_t setDataShaping(uint8_t sh) override; + + /*! + \brief Sets transmission encoding. Available in GFSK mode only. Serves only as alias for PhysicalLayer compatibility. + \param encoding Encoding to be used. Set to 0 for NRZ, and 2 for whitening. + \returns \ref status_codes + */ + int16_t setEncoding(uint8_t encoding) override; + + /*! + \brief Set modem in fixed packet length mode. Available in GFSK mode only. + \param len Packet length. + \returns \ref status_codes + */ + int16_t fixedPacketLengthMode(uint8_t len = RADIOLIB_LR11X0_MAX_PACKET_LENGTH); + + /*! + \brief Set modem in variable packet length mode. Available in GFSK mode only. + \param maxLen Maximum packet length. + \returns \ref status_codes + */ + int16_t variablePacketLengthMode(uint8_t maxLen = RADIOLIB_LR11X0_MAX_PACKET_LENGTH); + + /*! + \brief Sets GFSK whitening parameters. + \param enabled True = Whitening enabled + \param initial Initial value used for the whitening LFSR in GFSK mode. + By default set to 0x01FF for compatibility with SX127x and LoRaWAN. + \returns \ref status_codes + */ + int16_t setWhitening(bool enabled, uint16_t initial = 0x01FF); + + /*! + \brief Set data. + \param dr Data rate struct. Interpretation depends on currently active modem (GFSK or LoRa). + \returns \ref status_codes + */ + int16_t setDataRate(DataRate_t dr) override; + + /*! + \brief Check the data rate can be configured by this module. + \param dr Data rate struct. Interpretation depends on currently active modem (GFSK or LoRa). + \returns \ref status_codes + */ + int16_t checkDataRate(DataRate_t dr) override; + + /*! + \brief Sets preamble length for LoRa or GFSK modem. Allowed values range from 1 to 65535. + \param preambleLength Preamble length to be set in symbols (LoRa) or bits (GFSK). + \returns \ref status_codes + */ + int16_t setPreambleLength(size_t preambleLength) override; + + /*! + \brief Sets TCXO (Temperature Compensated Crystal Oscillator) configuration. + \param voltage TCXO reference voltage in volts. Allowed values are 1.6, 1.7, 1.8, 2.2. 2.4, 2.7, 3.0 and 3.3 V. + Set to 0 to disable TCXO. + NOTE: After setting this parameter to 0, the module will be reset (since there's no other way to disable TCXO). + \param delay TCXO timeout in us. Defaults to 5000 us. + \returns \ref status_codes + */ + int16_t setTCXO(float voltage, uint32_t delay = 5000); + + /*! + \brief Sets CRC configuration. + \param len CRC length in bytes, Allowed values are 1 or 2, set to 0 to disable CRC. + \param initial Initial CRC value. GFSK only. Defaults to 0x1D0F (CCIT CRC). + \param polynomial Polynomial for CRC calculation. GFSK only. Defaults to 0x1021 (CCIT CRC). + \param inverted Invert CRC bytes. GFSK only. Defaults to true (CCIT CRC). + \returns \ref status_codes + */ + int16_t setCRC(uint8_t len, uint32_t initial = 0x00001D0FUL, uint32_t polynomial = 0x00001021UL, bool inverted = true); + + /*! + \brief Enable/disable inversion of the I and Q signals + \param enable QI inversion enabled (true) or disabled (false); + \returns \ref status_codes + */ + int16_t invertIQ(bool enable) override; + + /*! + \brief Gets RSSI (Recorded Signal Strength Indicator) of the last received packet. Only available for LoRa or GFSK modem. + \returns RSSI of the last received packet in dBm. + */ + float getRSSI() override; + + /*! + \brief Gets SNR (Signal to Noise Ratio) of the last received packet. Only available for LoRa modem. + \returns SNR of the last received packet in dB. + */ + float getSNR() override; + + /*! + \brief Gets frequency error of the latest received packet. + \returns Frequency error in Hz. + */ + float getFrequencyError(); + + /*! + \brief Query modem for the packet length of received payload. + \param update Update received packet length. Will return cached value when set to false. + \returns Length of last received packet in bytes. + */ + size_t getPacketLength(bool update = true) override; + + /*! + \brief Query modem for the packet length of received payload. + \param update Update received packet length. Will return cached value when set to false. + \returns Length of last received packet in bytes. + */ + size_t getPacketLength(bool update, uint8_t* offset); + + /*! + \brief Get expected time-on-air for a given size of payload + \param len Payload length in bytes. + \returns Expected time-on-air in microseconds. + */ + RadioLibTime_t getTimeOnAir(size_t len) override; + + /*! + \brief Calculate the timeout value for this specific module / series (in number of symbols or units of time) + \param timeoutUs Timeout in microseconds to listen for + \returns Timeout value in a unit that is specific for the used module + */ + RadioLibTime_t calculateRxTimeout(RadioLibTime_t timeoutUs) override; + + /*! + \brief Read currently active IRQ flags. + \returns IRQ flags. + */ + uint32_t getIrqFlags() override; + + /*! + \brief Set interrupt on IRQ pin to be sent on a specific IRQ bit (e.g. RxTimeout, CadDone). + \param irq Module-specific IRQ flags. + \returns \ref status_codes + */ + int16_t setIrqFlags(uint32_t irq) override; + + /*! + \brief Clear interrupt on a specific IRQ bit (e.g. RxTimeout, CadDone). + \param irq Module-specific IRQ flags. + \returns \ref status_codes + */ + int16_t clearIrqFlags(uint32_t irq) override; + + /*! + \brief Get one truly random byte from RSSI noise. + \returns TRNG byte. + */ + uint8_t randomByte() override; + + /*! + \brief Set implicit header mode for future reception/transmission. + \param len Payload length in bytes. + \returns \ref status_codes + */ + int16_t implicitHeader(size_t len); + + /*! + \brief Set explicit header mode for future reception/transmission. + \returns \ref status_codes + */ + int16_t explicitHeader(); + + /*! + \brief Gets effective data rate for the last transmitted packet. The value is calculated only for payload bytes. + \returns Effective data rate in bps. + */ + float getDataRate() const; + + /*! + \brief Set regulator mode to LDO. + \returns \ref status_codes + */ + int16_t setRegulatorLDO(); + + /*! + \brief Set regulator mode to DC-DC. + \returns \ref status_codes + */ + int16_t setRegulatorDCDC(); + + /*! + \brief Enables or disables Rx Boosted Gain mode (additional Rx gain for increased power consumption). + \param en True for Rx Boosted Gain, false for Rx Power Saving Gain + \returns \ref status_codes + */ + int16_t setRxBoostedGainMode(bool en); + + /*! \copydoc Module::setRfSwitchTable */ + void setRfSwitchTable(const uint32_t (&pins)[Module::RFSWITCH_MAX_PINS], const Module::RfSwitchMode_t table[]); + + /*! + \brief Forces LoRa low data rate optimization. Only available in LoRa mode. After calling this method, LDRO will always be set to + the provided value, regardless of symbol length. To re-enable automatic LDRO configuration, call LR11x0::autoLDRO() + \param enable Force LDRO to be always enabled (true) or disabled (false). + \returns \ref status_codes + */ + int16_t forceLDRO(bool enable); + + /*! + \brief Re-enables automatic LDRO configuration. Only available in LoRa mode. After calling this method, LDRO will be enabled automatically + when symbol length exceeds 16 ms. + \returns \ref status_codes + */ + int16_t autoLDRO(); + + /*! + \brief Sets LR-FHSS configuration. + \param bw LR-FHSS bandwidth, one of RADIOLIB_LR11X0_LR_FHSS_BW_* values. + \param cr LR-FHSS coding rate, one of RADIOLIB_LR11X0_LR_FHSS_CR_* values. + \param hdrCount Header packet count, 1 - 4. Defaults to 3. + \param hopSeed 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 hopSeed = 0x13A); + + /*! + \brief Start passive WiFi scan. BUSY pin will be de-activated when the scan is finished. + \param wifiType Type of WiFi (802.11) signals to scan, 'b', 'n', 'g' or '*' for all signals. + \param mode Scan acquisition mode, one of RADIOLIB_LR11X0_WIFI_ACQ_MODE_*. + The type of results available after the scan depends on this mode. + Defaults to RADIOLIB_LR11X0_WIFI_ACQ_MODE_FULL_BEACON, which provides the most information. + \param chanMask Bit mask of WiFi channels to scan, defaults to all channels. + More channels leads to longer overall scan duration. + \param numScans Number of scans to perform per each enabled channel. Defaults to 16 scans. + More scans leads to longer overall scan duration. + \param timeout Timeout of each scan in milliseconds. Defaults to 100 ms + Longer timeout leads to longer overall scan duration. + \returns \ref status_codes + */ + int16_t startWifiScan(char wifiType, uint8_t mode = RADIOLIB_LR11X0_WIFI_ACQ_MODE_FULL_BEACON, uint16_t chanMask = RADIOLIB_LR11X0_WIFI_ALL_CHANNELS, uint8_t numScans = 16, uint16_t timeout = 100); + + /*! + \brief Sets interrupt service routine to call when a WiFi scan is completed. + \param func ISR to call. + */ + void setWiFiScanAction(void (*func)(void)); + + /*! + \brief Clears interrupt service routine to call when a WiFi scan is completed. + */ + void clearWiFiScanAction(); + + /*! + \brief Get number of WiFi scan results after the scan is finished. + \param count Pointer to a variable that will hold the number of scan results. + \returns \ref status_codes + */ + int16_t getWifiScanResultsCount(uint8_t* count); + + /*! + \brief Retrieve passive WiFi scan result. + \param result Pointer to structure to hold the result data. + \param index Result index, starting from 0. The number of scan results can be retrieved by calling getWifiScanResultsCount. + \param brief Whether to only retrieve the results in brief format. If set to false, only information in LR11x0WifiResult_t + will be retrieved. If set to true, information in LR11x0WifiResultFull_t will be retrieved. In addition, if WiFi scan mode + was set to RADIOLIB_LR11X0_WIFI_ACQ_MODE_FULL_BEACON, all information in LR11x0WifiResultExtended_t will be retrieved. + \returns \ref status_codes + */ + int16_t getWifiScanResult(LR11x0WifiResult_t* result, uint8_t index, bool brief = false); + + /*! + \brief Blocking WiFi scan method. Performs a full passive WiFi scan. + This method may block for several seconds! + \param wifiType Type of WiFi (802.11) signals to scan, 'b', 'n', 'g' or '*' for all signals. + \param count Pointer to a variable that will hold the number of scan results. + \param mode Scan acquisition mode, one of RADIOLIB_LR11X0_WIFI_ACQ_MODE_*. + The type of results available after the scan depends on this mode. + Defaults to RADIOLIB_LR11X0_WIFI_ACQ_MODE_FULL_BEACON, which provides the most information. + \param chanMask Bit mask of WiFi channels to scan, defaults to all channels. + More channels leads to longer overall scan duration. + \param numScans Number of scans to perform per each enabled channel. Defaults to 16 scans. + More scans leads to longer overall scan duration. + \param timeout Timeout of each scan in milliseconds. Defaults to 100 ms + Longer timeout leads to longer overall scan duration. + \returns \ref status_codes + */ + int16_t wifiScan(uint8_t wifiType, uint8_t* count, uint8_t mode = RADIOLIB_LR11X0_WIFI_ACQ_MODE_FULL_BEACON, uint16_t chanMask = RADIOLIB_LR11X0_WIFI_ALL_CHANNELS, uint8_t numScans = 16, uint16_t timeout = 100); + + /*! + \brief Retrieve LR11x0 hardware, device and firmware version information. + \param info Pointer to LR11x0VersionInfo_t structure to populate. + \returns \ref status_codes + */ + int16_t getVersionInfo(LR11x0VersionInfo_t* info); + + /*! + \brief Method to upload new firmware image to the device. + The device will be automatically erased, a new firmware will be uploaded, + written to flash and executed. + \param image Pointer to the image to upload. + \param size Size of the image in 32-bit words. + \param nonvolatile Set to true when the image is saved in non-volatile memory of the host processor, + or to false when the patch is in its RAM. Defaults to true. + \returns \ref status_codes + */ + int16_t updateFirmware(const uint32_t* image, size_t size, bool nonvolatile = true); + + /*! + \brief Method to check whether the device is capable of performing a GNSS scan. + \returns \ref status_codes + */ + int16_t isGnssScanCapable(); + + /*! + \brief Performs GNSS scan. + \param res Pointer to LR11x0GnssPosition_t structure to populate. + Will not be saved if set to NULL, defaults to NULL. + \returns \ref status_codes + */ + int16_t gnssScan(LR11x0GnssResult_t* res = NULL); + + /*! + \brief Read information about the almanac. + \param stat Pointer to structure to save the almanac status into. + This is not the actual almanac, just a structure providing information about it. + \returns \ref status_codes + */ + int16_t getGnssAlmanacStatus(LR11x0GnssAlmanacStatus_t *stat); + + /*! + \brief Blocking wait until the next subframe with almanac data is available. + Used to control timing during almanac update from satellite. + \param stat Pointer to structure containing the almanac status read by getGnssAlmanacStatus. + This is not the actual almanac, just a structure providing information about it. + \param constellation Constellation to wait for, one of RADIOLIB_LR11X0_GNSS_CONSTELLATION_*. + Constellations cannot be updated at the same time, but rather must be updated sequentially! + \returns \ref status_codes + */ + int16_t gnssDelayUntilSubframe(LR11x0GnssAlmanacStatus_t *stat, uint8_t constellation); + + /*! + \brief Perform almanac update. Must be called immediately after gnssDelayUntilSubframe. + \param constellation Constellation to update, one of RADIOLIB_LR11X0_GNSS_CONSTELLATION_*. + Constellations cannot be updated at the same time, but rather must be updated sequentially! + \returns \ref status_codes + */ + int16_t updateGnssAlmanac(uint8_t constellation); + + /*! + \brief Get GNSS position. Called after gnssScan to retrieve the position calculated by the internal solver. + \param pos Pointer to LR11x0GnssPosition_t structure to populate. + \param filtered Whether to save the filtered, or unfiltered values. Defaults to true (filtered). + \returns \ref status_codes + */ + int16_t getGnssPosition(LR11x0GnssPosition_t* pos, bool filtered = true); + + /*! + \brief Get GNSS satellites found during the last scan. + \param sats Pointer to array of LR11x0GnssSatellite_t structures to populate. + \param numSats Number of satellites to read. Can be retrieved from LR11x0GnssResult_t passed to gnssScan. + \returns \ref status_codes + */ + int16_t getGnssSatellites(LR11x0GnssSatellite_t* sats, uint8_t numSats); + + /*! + \brief Get modem currently in use by the radio. + \param modem Pointer to a variable to save the retrieved configuration into. + \returns \ref status_codes + */ + int16_t getModem(ModemType_t* modem) override; + + /*! + \brief Perform image rejection calibration for the specified frequency band. + WARNING: Use at your own risk! Setting incorrect values may lead to decreased performance + \param freqMin Frequency band lower bound. + \param freqMax Frequency band upper bound. + \returns \ref status_codes + */ + int16_t calibrateImageRejection(float freqMin, float freqMax); + +#if !RADIOLIB_GODMODE && !RADIOLIB_LOW_LEVEL + protected: +#endif + Module* getMod() override; + + // LR11x0 SPI command implementations + int16_t writeRegMem32(uint32_t addr, uint32_t* data, size_t len); + int16_t readRegMem32(uint32_t addr, uint32_t* data, size_t len); + int16_t writeBuffer8(uint8_t* data, size_t len); + int16_t readBuffer8(uint8_t* data, size_t len, size_t offset); + int16_t clearRxBuffer(void); + int16_t writeRegMemMask32(uint32_t addr, uint32_t mask, uint32_t data); + + int16_t getStatus(uint8_t* stat1, uint8_t* stat2, uint32_t* irq); + int16_t getVersion(uint8_t* hw, uint8_t* device, uint8_t* major, uint8_t* minor); + int16_t getErrors(uint16_t* err); + int16_t clearErrors(void); + int16_t calibrate(uint8_t params); + int16_t setRegMode(uint8_t mode); + int16_t setDioAsRfSwitch(uint8_t en, uint8_t stbyCfg, uint8_t rxCfg, uint8_t txCfg, uint8_t txHpCfg, uint8_t txHfCfg, uint8_t gnssCfg, uint8_t wifiCfg); + int16_t setDioIrqParams(uint32_t irq1, uint32_t irq2); + int16_t setDioIrqParams(uint32_t irq); + int16_t clearIrq(uint32_t irq); + int16_t configLfClock(uint8_t setup); + int16_t setTcxoMode(uint8_t tune, uint32_t delay); + int16_t reboot(bool stay); + int16_t getVbat(float* vbat); + int16_t getTemp(float* temp); + int16_t setFs(void); + int16_t getRandomNumber(uint32_t* rnd); + int16_t eraseInfoPage(void); + int16_t writeInfoPage(uint16_t addr, const uint32_t* data, size_t len); + int16_t readInfoPage(uint16_t addr, uint32_t* data, size_t len); + int16_t getChipEui(uint8_t* eui); + int16_t getSemtechJoinEui(uint8_t* eui); + int16_t deriveRootKeysAndGetPin(uint8_t* pin); + int16_t enableSpiCrc(bool en); + int16_t driveDiosInSleepMode(bool en); + + int16_t resetStats(void); + int16_t getStats(uint16_t* nbPktReceived, uint16_t* nbPktCrcError, uint16_t* data1, uint16_t* data2); + int16_t getPacketType(uint8_t* type); + int16_t getRxBufferStatus(uint8_t* len, uint8_t* startOffset); + int16_t getPacketStatusLoRa(float* rssiPkt, float* snrPkt, float* signalRssiPkt); + int16_t getPacketStatusGFSK(float* rssiSync, float* rssiAvg, uint8_t* rxLen, uint8_t* stat); + int16_t getRssiInst(float* rssi); + int16_t setGfskSyncWord(uint8_t* sync); + int16_t setLoRaPublicNetwork(bool pub); + int16_t setRx(uint32_t timeout); + int16_t setTx(uint32_t timeout); + int16_t setRfFrequency(uint32_t rfFreq); + int16_t autoTxRx(uint32_t delay, uint8_t intMode, uint32_t timeout); + int16_t setCadParams(uint8_t symNum, uint8_t detPeak, uint8_t detMin, uint8_t cadExitMode, uint32_t timeout); + int16_t setPacketType(uint8_t type); + int16_t setModulationParamsLoRa(uint8_t sf, uint8_t bw, uint8_t cr, uint8_t ldro); + int16_t setModulationParamsGFSK(uint32_t br, uint8_t sh, uint8_t rxBw, uint32_t freqDev); + int16_t setModulationParamsLrFhss(uint32_t br, uint8_t sh); + int16_t setModulationParamsSigfox(uint32_t br, uint8_t sh); + int16_t setPacketParamsLoRa(uint16_t preambleLen, uint8_t hdrType, uint8_t payloadLen, uint8_t crcType, uint8_t invertIQ); + int16_t setPacketParamsGFSK(uint16_t preambleLen, uint8_t preambleDetectorLen, uint8_t syncWordLen, uint8_t addrCmp, uint8_t packType, uint8_t payloadLen, uint8_t crcType, uint8_t whiten); + int16_t setPacketParamsSigfox(uint8_t payloadLen, uint16_t rampUpDelay, uint16_t rampDownDelay, uint16_t bitNum); + int16_t setTxParams(int8_t pwr, uint8_t ramp); + int16_t setPacketAdrs(uint8_t node, uint8_t broadcast); + int16_t setRxTxFallbackMode(uint8_t mode); + int16_t setRxDutyCycle(uint32_t rxPeriod, uint32_t sleepPeriod, uint8_t mode); + int16_t setPaConfig(uint8_t paSel, uint8_t regPaSupply, uint8_t paDutyCycle, uint8_t paHpSel); + int16_t stopTimeoutOnPreamble(bool stop); + int16_t setCad(void); + int16_t setTxCw(void); + int16_t setTxInfinitePreamble(void); + int16_t setLoRaSynchTimeout(uint8_t symbolNum); + int16_t setRangingAddr(uint32_t addr, uint8_t checkLen); + int16_t setRangingReqAddr(uint32_t addr); + int16_t getRangingResult(uint8_t type, float* res); + int16_t setRangingTxRxDelay(uint32_t delay); + int16_t setGfskCrcParams(uint32_t init, uint32_t poly); + int16_t setGfskWhitParams(uint16_t seed); + int16_t setRangingParameter(uint8_t symbolNum); + int16_t setRssiCalibration(const int8_t* tune, int16_t gainOffset); + int16_t setLoRaSyncWord(uint8_t sync); + int16_t lrFhssBuildFrame(uint8_t hdrCount, uint8_t cr, uint8_t grid, bool hop, uint8_t bw, uint16_t hopSeq, int8_t devOffset, uint8_t* payload, size_t len); + int16_t lrFhssSetSyncWord(uint32_t sync); + int16_t configBleBeacon(uint8_t chan, uint8_t* payload, size_t len); + int16_t getLoRaRxHeaderInfos(uint8_t* info); + int16_t bleBeaconSend(uint8_t chan, uint8_t* payload, size_t len); + + int16_t wifiScan(uint8_t type, uint16_t mask, uint8_t acqMode, uint8_t nbMaxRes, uint8_t nbScanPerChan, uint16_t timeout, uint8_t abortOnTimeout); + int16_t wifiScanTimeLimit(uint8_t type, uint16_t mask, uint8_t acqMode, uint8_t nbMaxRes, uint16_t timePerChan, uint16_t timeout); + int16_t wifiCountryCode(uint16_t mask, uint8_t nbMaxRes, uint8_t nbScanPerChan, uint16_t timeout, uint8_t abortOnTimeout); + int16_t wifiCountryCodeTimeLimit(uint16_t mask, uint8_t nbMaxRes, uint16_t timePerChan, uint16_t timeout); + int16_t wifiGetNbResults(uint8_t* nbResults); + int16_t wifiReadResults(uint8_t index, uint8_t nbResults, uint8_t format, uint8_t* results); + int16_t wifiResetCumulTimings(void); + int16_t wifiReadCumulTimings(uint32_t* detection, uint32_t* capture, uint32_t* demodulation); + int16_t wifiGetNbCountryCodeResults(uint8_t* nbResults); + int16_t wifiReadCountryCodeResults(uint8_t index, uint8_t nbResults, uint8_t* results); + int16_t wifiCfgTimestampAPphone(uint32_t timestamp); + int16_t wifiReadVersion(uint8_t* major, uint8_t* minor); + + int16_t gnssReadRssi(int8_t* rssi); + int16_t gnssSetConstellationToUse(uint8_t mask); + int16_t gnssReadConstellationToUse(uint8_t* mask); + int16_t gnssSetAlmanacUpdate(uint8_t mask); + int16_t gnssReadAlmanacUpdate(uint8_t* mask); + int16_t gnssSetFreqSearchSpace(uint8_t freq); + int16_t gnssReadFreqSearchSpace(uint8_t* freq); + int16_t gnssReadVersion(uint8_t* fw, uint8_t* almanac); + int16_t gnssReadSupportedConstellations(uint8_t* mask); + int16_t gnssSetMode(uint8_t mode); + int16_t gnssAutonomous(uint32_t gpsTime, uint8_t resMask, uint8_t nbSvMask); + int16_t gnssAssisted(uint32_t gpsTime, uint8_t effort, uint8_t resMask, uint8_t nbSvMask); + int16_t gnssSetAssistancePosition(float lat, float lon); + int16_t gnssReadAssistancePosition(float* lat, float* lon); + int16_t gnssPushSolverMsg(uint8_t* payload, size_t len); + int16_t gnssPushDmMsg(uint8_t* payload, size_t len); + int16_t gnssGetContextStatus(uint8_t* fwVersion, uint32_t* almanacCrc, uint8_t* errCode, uint8_t* almUpdMask, uint8_t* freqSpace); + int16_t gnssGetNbSvDetected(uint8_t* nbSv); + int16_t gnssGetSvDetected(uint8_t* svId, uint8_t* snr, int16_t* doppler, size_t nbSv); + int16_t gnssGetConsumption(uint32_t* cpu, uint32_t* radio); + int16_t gnssGetResultSize(uint16_t* size); + int16_t gnssReadResults(uint8_t* result, uint16_t size); + int16_t gnssAlmanacFullUpdateHeader(uint16_t date, uint32_t globalCrc); + int16_t gnssAlmanacFullUpdateSV(uint8_t svn, uint8_t* svnAlmanac); + int16_t gnssAlmanacReadAddrSize(uint32_t* addr, uint16_t* size); + int16_t gnssAlmanacReadSV(uint8_t svId, uint8_t* almanac); + int16_t gnssGetNbSvVisible(uint32_t time, float lat, float lon, uint8_t constellation, uint8_t* nbSv); + int16_t gnssGetSvVisible(uint8_t nbSv, uint8_t** svId, int16_t** doppler, int16_t** dopplerErr); + int16_t gnssPerformScan(uint8_t effort, uint8_t resMask, uint8_t nbSvMax); + int16_t gnssReadLastScanModeLaunched(uint8_t* lastScanMode); + int16_t gnssFetchTime(uint8_t effort, uint8_t opt); + int16_t gnssReadTime(uint8_t* err, uint32_t* time, uint32_t* nbUs, uint32_t* timeAccuracy); + int16_t gnssResetTime(void); + int16_t gnssResetPosition(void); + int16_t gnssReadWeekNumberRollover(uint8_t* status, uint8_t* rollover); + int16_t gnssReadDemodStatus(int8_t* status, uint8_t* info); + int16_t gnssReadCumulTiming(uint32_t* timing, uint8_t* constDemod); + int16_t gnssSetTime(uint32_t time, uint16_t accuracy); + int16_t gnssReadDopplerSolverRes(uint8_t* error, uint8_t* nbSvUsed, float* lat, float* lon, uint16_t* accuracy, uint16_t* xtal, float* latFilt, float* lonFilt, uint16_t* accuracyFilt, uint16_t* xtalFilt); + int16_t gnssReadDelayResetAP(uint32_t* delay); + int16_t gnssAlmanacUpdateFromSat(uint8_t effort, uint8_t bitMask); + int16_t gnssReadAlmanacStatus(uint8_t* status); + int16_t gnssReadKeepSyncStatus(uint8_t mask, uint8_t* nbSvVisible, uint32_t* elapsed); + int16_t gnssConfigAlmanacUpdatePeriod(uint8_t bitMask, uint8_t svType, uint16_t period); + int16_t gnssReadAlmanacUpdatePeriod(uint8_t bitMask, uint8_t svType, uint16_t* period); + int16_t gnssConfigDelayResetAP(uint32_t delay); + int16_t gnssGetSvWarmStart(uint8_t bitMask, uint8_t* sv, uint8_t nbVisSat); + int16_t gnssReadWarmStartStatus(uint8_t bitMask, uint8_t* nbVisSat, uint32_t* timeElapsed); + int16_t gnssGetSvSync(uint8_t mask, uint8_t nbSv, uint8_t* syncList); + int16_t gnssWriteBitMaskSatActivated(uint8_t bitMask, uint32_t* bitMaskActivated0, uint32_t* bitMaskActivated1); + void gnssAbort(); + + int16_t cryptoSetKey(uint8_t keyId, uint8_t* key); + int16_t cryptoDeriveKey(uint8_t srcKeyId, uint8_t dstKeyId, uint8_t* key); + int16_t cryptoProcessJoinAccept(uint8_t decKeyId, uint8_t verKeyId, uint8_t lwVer, uint8_t* header, uint8_t* dataIn, size_t len, uint8_t* dataOut); + int16_t cryptoComputeAesCmac(uint8_t keyId, uint8_t* data, size_t len, uint32_t* mic); + int16_t cryptoVerifyAesCmac(uint8_t keyId, uint32_t micExp, uint8_t* data, size_t len, bool* result); + int16_t cryptoAesEncrypt01(uint8_t keyId, uint8_t* dataIn, size_t len, uint8_t* dataOut); + int16_t cryptoAesEncrypt(uint8_t keyId, uint8_t* dataIn, size_t len, uint8_t* dataOut); + int16_t cryptoAesDecrypt(uint8_t keyId, uint8_t* dataIn, size_t len, uint8_t* dataOut); + int16_t cryptoStoreToFlash(void); + int16_t cryptoRestoreFromFlash(void); + int16_t cryptoSetParam(uint8_t id, uint32_t value); + int16_t cryptoGetParam(uint8_t id, uint32_t* value); + int16_t cryptoCheckEncryptedFirmwareImage(uint32_t offset, uint32_t* data, size_t len, bool nonvolatile); + int16_t cryptoCheckEncryptedFirmwareImageResult(bool* result); + + int16_t bootEraseFlash(void); + int16_t bootWriteFlashEncrypted(uint32_t offset, uint32_t* data, size_t len, bool nonvolatile); + int16_t bootReboot(bool stay); + int16_t bootGetPin(uint8_t* pin); + int16_t bootGetChipEui(uint8_t* eui); + int16_t bootGetJoinEui(uint8_t* eui); + + int16_t SPIcommand(uint16_t cmd, bool write, uint8_t* data, size_t len, uint8_t* out = NULL, size_t outLen = 0); + +#if !RADIOLIB_GODMODE + protected: +#endif + uint8_t chipType = 0; + float freqMHz = 0; + +#if !RADIOLIB_GODMODE + private: +#endif + Module* mod; + + // cached LoRa parameters + uint8_t bandwidth = 0, spreadingFactor = 0, codingRate = 0, ldrOptimize = 0, crcTypeLoRa = 0, headerType = 0; + uint16_t preambleLengthLoRa = 0; + float bandwidthKhz = 0; + bool ldroAuto = true; + size_t implicitLen = 0; + bool invertIQEnabled = false; + + // cached GFSK parameters + uint32_t bitRate = 0, frequencyDev = 0; + uint8_t preambleDetLength = 0, rxBandwidth = 0, pulseShape = 0, crcTypeGFSK = 0, syncWordLength = 0, addrComp = 0, whitening = 0, packetType = 0, node = 0; + uint16_t preambleLengthGFSK = 0; + + // cached LR-FHSS parameters + uint8_t lrFhssCr = 0, lrFhssBw = 0, lrFhssHdrCount = 0, lrFhssGrid = 0; + uint16_t lrFhssHopSeq = 0; + + float dataRateMeasured = 0; + + uint8_t wifiScanMode = 0; + + int16_t modSetup(float tcxoVoltage, uint8_t modem); + static int16_t SPIparseStatus(uint8_t in); + static int16_t SPIcheckStatus(Module* mod); + bool findChip(uint8_t ver); + int16_t config(uint8_t modem); + int16_t setPacketMode(uint8_t mode, uint8_t len); + int16_t startCad(uint8_t symbolNum, uint8_t detPeak, uint8_t detMin, uint8_t exitMode, RadioLibTime_t timeout); + int16_t setHeaderType(uint8_t hdrType, size_t len = 0xFF); + + // common methods to avoid some copy-paste + int16_t bleBeaconCommon(uint16_t cmd, uint8_t chan, uint8_t* payload, size_t len); + int16_t writeCommon(uint16_t cmd, uint32_t addrOffset, const uint32_t* data, size_t len, bool nonvolatile); + int16_t cryptoCommon(uint16_t cmd, uint8_t keyId, uint8_t* dataIn, size_t len, uint8_t* dataOut); +}; + +#endif + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/LR11x0/LR11x0_firmware.h b/software/firmware/source/libraries/RadioLib/src/modules/LR11x0/LR11x0_firmware.h new file mode 100644 index 000000000..4fb677417 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/LR11x0/LR11x0_firmware.h @@ -0,0 +1,38 @@ +#if !defined(_RADIOLIB_LR11X0_FIRMWARE_H) +#define _RADIOLIB_LR11X0_FIRMWARE_H + +#if defined(RADIOLIB_LR1110_FIRMWARE_IN_RAM) + #define RADIOLIB_LR1110_FIRMWARE_ATTR +#else + #define RADIOLIB_LR1110_FIRMWARE_ATTR RADIOLIB_NONVOLATILE +#endif + +#define RADIOLIB_LR11X0_FIRMWARE_IMAGE_SIZE LR11XX_FIRMWARE_IMAGE_SIZE + +#if defined(RADIOLIB_LR1110_FIRMWARE_0303) + #include "firmware/lr1110_transceiver_0303.h" +#elif defined(RADIOLIB_LR1110_FIRMWARE_0304) + #include "firmware/lr1110_transceiver_0304.h" +#elif defined(RADIOLIB_LR1110_FIRMWARE_0305) + #include "firmware/lr1110_transceiver_0305.h" +#elif defined(RADIOLIB_LR1110_FIRMWARE_0306) + #include "firmware/lr1110_transceiver_0306.h" +#elif defined(RADIOLIB_LR1110_FIRMWARE_0307) + #include "firmware/lr1110_transceiver_0307.h" +#elif defined(RADIOLIB_LR1110_FIRMWARE_0401) + #include "firmware/lr1110_transceiver_0401.h" +#elif defined(RADIOLIB_LR1120_FIRMWARE_0101) + #include "firmware/lr1120_transceiver_0101.h" +#elif defined(RADIOLIB_LR1120_FIRMWARE_0102) + #include "firmware/lr1120_transceiver_0102.h" +#elif defined(RADIOLIB_LR1120_FIRMWARE_0201) + #include "firmware/lr1120_transceiver_0201.h" +#elif defined(RADIOLIB_LR1121_FIRMWARE_0102) + #include "firmware/lr1121_transceiver_0102.h" +#elif defined(RADIOLIB_LR1121_FIRMWARE_0103) + #include "firmware/lr1121_transceiver_0103.h" +#else + #error "No LR11x0 firmware image selected!" +#endif + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/RF69/RF69.cpp b/software/firmware/source/libraries/RadioLib/src/modules/RF69/RF69.cpp new file mode 100644 index 000000000..2bbfeb57e --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/RF69/RF69.cpp @@ -0,0 +1,1080 @@ +#include "RF69.h" +#include +#if !RADIOLIB_EXCLUDE_RF69 + +RF69::RF69(Module* module) : PhysicalLayer(RADIOLIB_RF69_FREQUENCY_STEP_SIZE, RADIOLIB_RF69_MAX_PACKET_LENGTH) { + this->mod = module; +} + +int16_t RF69::begin(float freq, float br, float freqDev, float rxBw, int8_t pwr, uint8_t preambleLen) { + // set module properties + this->mod->init(); + this->mod->hal->pinMode(this->mod->getIrq(), this->mod->hal->GpioModeInput); + + // try to find the RF69 chip + uint8_t i = 0; + bool flagFound = false; + while((i < 10) && !flagFound) { + // reset the module + reset(); + + // check version register + int16_t version = getChipVersion(); + if(version == RADIOLIB_RF69_CHIP_VERSION) { + flagFound = true; + } else { + RADIOLIB_DEBUG_BASIC_PRINTLN("RF69 not found! (%d of 10 tries) RADIOLIB_RF69_REG_VERSION == 0x%04X, expected 0x0024", i + 1, version); + this->mod->hal->delay(10); + i++; + } + } + + if(!flagFound) { + RADIOLIB_DEBUG_BASIC_PRINTLN("No RF69 found!"); + this->mod->term(); + return(RADIOLIB_ERR_CHIP_NOT_FOUND); + } else { + RADIOLIB_DEBUG_BASIC_PRINTLN("M\tRF69"); + } + + // configure settings not accessible by API + int16_t state = config(); + RADIOLIB_ASSERT(state); + + // configure publicly accessible settings + state = setFrequency(freq); + RADIOLIB_ASSERT(state); + + // configure bitrate + this->rxBandwidth = rxBw; + state = setBitRate(br); + RADIOLIB_ASSERT(state); + + // configure default RX bandwidth + state = setRxBandwidth(rxBw); + RADIOLIB_ASSERT(state); + + // configure default frequency deviation + state = setFrequencyDeviation(freqDev); + RADIOLIB_ASSERT(state); + + // configure default TX output power + state = setOutputPower(pwr); + RADIOLIB_ASSERT(state); + + // configure default preamble length + state = setPreambleLength(preambleLen); + RADIOLIB_ASSERT(state); + + // set default packet length mode + state = variablePacketLengthMode(); + RADIOLIB_ASSERT(state); + + // set default sync word + uint8_t syncWord[] = RADIOLIB_RF69_DEFAULT_SW; + state = setSyncWord(syncWord, sizeof(syncWord)); + RADIOLIB_ASSERT(state); + + // set default data shaping + state = setDataShaping(RADIOLIB_SHAPING_NONE); + RADIOLIB_ASSERT(state); + + // set default encoding + state = setEncoding(RADIOLIB_ENCODING_NRZ); + RADIOLIB_ASSERT(state); + + // set CRC on by default + state = setCrcFiltering(true); + RADIOLIB_ASSERT(state); + + return(state); +} + +void RF69::reset() { + this->mod->hal->pinMode(this->mod->getRst(), this->mod->hal->GpioModeOutput); + this->mod->hal->digitalWrite(this->mod->getRst(), this->mod->hal->GpioLevelHigh); + this->mod->hal->delay(1); + this->mod->hal->digitalWrite(this->mod->getRst(), this->mod->hal->GpioLevelLow); + this->mod->hal->delay(10); +} + +int16_t RF69::transmit(const uint8_t* data, size_t len, uint8_t addr) { + // calculate timeout (5ms + 500 % of expected time-on-air) + RadioLibTime_t timeout = 5 + (RadioLibTime_t)((((float)(len * 8)) / this->bitRate) * 5); + + // start transmission + int16_t state = startTransmit(data, len, addr); + RADIOLIB_ASSERT(state); + + // wait for transmission end or timeout + RadioLibTime_t start = this->mod->hal->millis(); + while(!this->mod->hal->digitalRead(this->mod->getIrq())) { + this->mod->hal->yield(); + + if(this->mod->hal->millis() - start > timeout) { + finishTransmit(); + return(RADIOLIB_ERR_TX_TIMEOUT); + } + } + + return(finishTransmit()); +} + +int16_t RF69::receive(uint8_t* data, size_t len) { + // calculate timeout (500 ms + 400 full 64-byte packets at current bit rate) + RadioLibTime_t timeout = 500 + (1.0/(this->bitRate))*(RADIOLIB_RF69_MAX_PACKET_LENGTH*400.0); + + // start reception + int16_t state = startReceive(); + RADIOLIB_ASSERT(state); + + // wait for packet reception or timeout + RadioLibTime_t start = this->mod->hal->millis(); + while(!this->mod->hal->digitalRead(this->mod->getIrq())) { + this->mod->hal->yield(); + + if(this->mod->hal->millis() - start > timeout) { + standby(); + clearIRQFlags(); + return(RADIOLIB_ERR_RX_TIMEOUT); + } + } + + // read packet data + return(readData(data, len)); +} + +int16_t RF69::sleep() { + // set RF switch (if present) + this->mod->setRfSwitchState(Module::MODE_IDLE); + + // set module to sleep + return(setMode(RADIOLIB_RF69_SLEEP)); +} + +int16_t RF69::standby() { + // set RF switch (if present) + this->mod->setRfSwitchState(Module::MODE_IDLE); + + // set module to standby + return(setMode(RADIOLIB_RF69_STANDBY)); +} + +int16_t RF69::standby(uint8_t mode) { + (void)mode; + return(standby()); +} + +int16_t RF69::transmitDirect(uint32_t frf) { + // set RF switch (if present) + this->mod->setRfSwitchState(Module::MODE_TX); + + // user requested to start transmitting immediately (required for RTTY) + if(frf != 0) { + this->mod->SPIwriteRegister(RADIOLIB_RF69_REG_FRF_MSB, (frf & 0xFF0000) >> 16); + this->mod->SPIwriteRegister(RADIOLIB_RF69_REG_FRF_MID, (frf & 0x00FF00) >> 8); + this->mod->SPIwriteRegister(RADIOLIB_RF69_REG_FRF_LSB, frf & 0x0000FF); + + return(setMode(RADIOLIB_RF69_TX)); + } + + // activate direct mode + int16_t state = directMode(); + RADIOLIB_ASSERT(state); + + // start transmitting + return(setMode(RADIOLIB_RF69_TX)); +} + +int16_t RF69::receiveDirect() { + // set RF switch (if present) + this->mod->setRfSwitchState(Module::MODE_RX); + + // activate direct mode + int16_t state = directMode(); + RADIOLIB_ASSERT(state); + + // start receiving + return(setMode(RADIOLIB_RF69_RX)); +} + +int16_t RF69::directMode() { + // set mode to standby + int16_t state = setMode(RADIOLIB_RF69_STANDBY); + RADIOLIB_ASSERT(state); + + // set DIO mapping + state = this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_DIO_MAPPING_1, RADIOLIB_RF69_DIO1_CONT_DCLK | RADIOLIB_RF69_DIO2_CONT_DATA, 5, 2); + RADIOLIB_ASSERT(state); + + // set continuous mode + if(this->bitSync) { + return(this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_DATA_MODUL, RADIOLIB_RF69_CONTINUOUS_MODE_WITH_SYNC, 6, 5)); + } else { + return(this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_DATA_MODUL, RADIOLIB_RF69_CONTINUOUS_MODE, 6, 5)); + } +} + +int16_t RF69::packetMode() { + return(this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_DATA_MODUL, RADIOLIB_RF69_PACKET_MODE, 6, 5)); +} + +void RF69::setAESKey(uint8_t* key) { + this->mod->SPIwriteRegisterBurst(RADIOLIB_RF69_REG_AES_KEY_1, key, 16); +} + +int16_t RF69::enableAES() { + return(this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_PACKET_CONFIG_2, RADIOLIB_RF69_AES_ON, 0, 0)); +} + +int16_t RF69::disableAES() { + return(this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_PACKET_CONFIG_2, RADIOLIB_RF69_AES_OFF, 0, 0)); +} + +int16_t RF69::startReceive() { + // set mode to standby + int16_t state = setMode(RADIOLIB_RF69_STANDBY); + RADIOLIB_ASSERT(state); + + // set RX timeouts and DIO pin mapping + state = this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_DIO_MAPPING_1, RADIOLIB_RF69_DIO0_PACK_PAYLOAD_READY, 7, 4); + state |= this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_RX_TIMEOUT_1, RADIOLIB_RF69_TIMEOUT_RX_START); + state |= this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_RX_TIMEOUT_2, RADIOLIB_RF69_TIMEOUT_RSSI_THRESH); + RADIOLIB_ASSERT(state); + + // clear interrupt flags + clearIRQFlags(); + + // set RF switch (if present) + this->mod->setRfSwitchState(Module::MODE_RX); + + // set mode to receive + state = this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_OCP, RADIOLIB_RF69_OCP_ON | RADIOLIB_RF69_OCP_TRIM); + state |= this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_TEST_PA1, RADIOLIB_RF69_PA1_NORMAL); + state |= this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_TEST_PA2, RADIOLIB_RF69_PA2_NORMAL); + RADIOLIB_ASSERT(state); + + state = setMode(RADIOLIB_RF69_RX); + + return(state); +} + +int16_t RF69::startReceive(uint32_t timeout, uint32_t irqFlags, uint32_t irqMask, size_t len) { + (void)timeout; + (void)irqFlags; + (void)irqMask; + (void)len; + return(startReceive()); +} + +void RF69::setDio0Action(void (*func)(void)) { + this->mod->hal->attachInterrupt(this->mod->hal->pinToInterrupt(this->mod->getIrq()), func, this->mod->hal->GpioInterruptRising); +} + +void RF69::clearDio0Action() { + this->mod->hal->detachInterrupt(this->mod->hal->pinToInterrupt(this->mod->getIrq())); +} + +void RF69::setDio1Action(void (*func)(void)) { + if(this->mod->getGpio() == RADIOLIB_NC) { + return; + } + this->mod->hal->pinMode(this->mod->getGpio(), this->mod->hal->GpioModeInput); + this->mod->hal->attachInterrupt(this->mod->hal->pinToInterrupt(this->mod->getGpio()), func, this->mod->hal->GpioInterruptRising); +} + +void RF69::clearDio1Action() { + if(this->mod->getGpio() == RADIOLIB_NC) { + return; + } + this->mod->hal->detachInterrupt(this->mod->hal->pinToInterrupt(this->mod->getGpio())); +} + +void RF69::setPacketReceivedAction(void (*func)(void)) { + this->setDio0Action(func); +} + +void RF69::clearPacketReceivedAction() { + this->clearDio0Action(); +} + +void RF69::setPacketSentAction(void (*func)(void)) { + this->setDio0Action(func); +} + +void RF69::clearPacketSentAction() { + this->clearDio0Action(); +} + +void RF69::setFifoEmptyAction(void (*func)(void)) { + // set DIO1 to the FIFO empty event (the register setting is done in startTransmit) + if(this->mod->getGpio() == RADIOLIB_NC) { + return; + } + this->mod->hal->pinMode(this->mod->getGpio(), this->mod->hal->GpioModeInput); + + // we need to invert the logic here (as compared to setDio1Action), since we are using the "FIFO not empty interrupt" + this->mod->hal->attachInterrupt(this->mod->hal->pinToInterrupt(this->mod->getGpio()), func, this->mod->hal->GpioInterruptFalling); +} + +void RF69::clearFifoEmptyAction() { + clearDio1Action(); +} + +void RF69::setFifoFullAction(void (*func)(void)) { + // set the interrupt + this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_FIFO_THRESH, RADIOLIB_RF69_FIFO_THRESH, 6, 0); + this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_DIO_MAPPING_1, RADIOLIB_RF69_DIO1_PACK_FIFO_LEVEL, 5, 4); + + // set DIO1 to the FIFO full event + setDio1Action(func); +} + +void RF69::clearFifoFullAction() { + clearDio1Action(); + this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_DIO_MAPPING_1, 0x00, 5, 4); +} + +bool RF69::fifoAdd(uint8_t* data, int totalLen, int* remLen) { + // subtract first (this may be the first time we get to modify the remaining length) + *remLen -= RADIOLIB_RF69_FIFO_THRESH - 1; + + // check if there is still something left to send + if(*remLen <= 0) { + // we're done + return(true); + } + + // calculate the number of bytes we can copy + int len = *remLen; + if(len > RADIOLIB_RF69_FIFO_THRESH - 1) { + len = RADIOLIB_RF69_FIFO_THRESH - 1; + } + + // copy the bytes to the FIFO + this->mod->SPIwriteRegisterBurst(RADIOLIB_RF69_REG_FIFO, &data[totalLen - *remLen], len); + + // we're not done yet + return(false); +} + +bool RF69::fifoGet(volatile uint8_t* data, int totalLen, volatile int* rcvLen) { + // get pointer to the correct position in data buffer + uint8_t* dataPtr = (uint8_t*)&data[*rcvLen]; + + // check how much data are we still expecting + uint8_t len = RADIOLIB_RF69_FIFO_THRESH - 1; + if(totalLen - *rcvLen < len) { + // we're nearly at the end + len = totalLen - *rcvLen; + } + + // get the data + this->mod->SPIreadRegisterBurst(RADIOLIB_RF69_REG_FIFO, len, dataPtr); + *rcvLen = *rcvLen + len; + + // check if we're done + if(*rcvLen >= totalLen) { + return(true); + } + return(false); +} + +int16_t RF69::startTransmit(const uint8_t* data, size_t len, uint8_t addr) { + // set mode to standby + int16_t state = setMode(RADIOLIB_RF69_STANDBY); + RADIOLIB_ASSERT(state); + + // clear interrupt flags + clearIRQFlags(); + + // set DIO mapping + if(len > RADIOLIB_RF69_MAX_PACKET_LENGTH) { + state = this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_DIO_MAPPING_1, RADIOLIB_RF69_DIO1_PACK_FIFO_NOT_EMPTY, 5, 4); + } else { + state = this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_DIO_MAPPING_1, RADIOLIB_RF69_DIO0_PACK_PACKET_SENT, 7, 6); + } + RADIOLIB_ASSERT(state); + + // optionally write packet length + if (this->packetLengthConfig == RADIOLIB_RF69_PACKET_FORMAT_VARIABLE) { + this->mod->SPIwriteRegister(RADIOLIB_RF69_REG_FIFO, len); + } + + // check address filtering + uint8_t filter = this->mod->SPIgetRegValue(RADIOLIB_RF69_REG_PACKET_CONFIG_1, 2, 1); + if((filter == RADIOLIB_RF69_ADDRESS_FILTERING_NODE) || (filter == RADIOLIB_RF69_ADDRESS_FILTERING_NODE_BROADCAST)) { + this->mod->SPIwriteRegister(RADIOLIB_RF69_REG_FIFO, addr); + } + + // write packet to FIFO + size_t packetLen = len; + if(len > RADIOLIB_RF69_MAX_PACKET_LENGTH) { + packetLen = RADIOLIB_RF69_FIFO_THRESH - 1; + this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_FIFO_THRESH, RADIOLIB_RF69_TX_START_CONDITION_FIFO_NOT_EMPTY, 7, 7); + } + this->mod->SPIwriteRegisterBurst(RADIOLIB_RF69_REG_FIFO, const_cast(data), packetLen); + + // this is a hack, but it seems than in Stream mode, Rx FIFO level is getting triggered 1 byte before it should + // just add a padding byte that can be dropped without consequence + if(len > RADIOLIB_RF69_MAX_PACKET_LENGTH) { + this->mod->SPIwriteRegister(RADIOLIB_RF69_REG_FIFO, '/'); + } + + // enable +20 dBm operation + if(this->power > 17) { + state = this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_OCP, RADIOLIB_RF69_OCP_OFF | 0x0F); + state |= this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_TEST_PA1, RADIOLIB_RF69_PA1_20_DBM); + state |= this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_TEST_PA2, RADIOLIB_RF69_PA2_20_DBM); + RADIOLIB_ASSERT(state); + } + + // set RF switch (if present) + this->mod->setRfSwitchState(Module::MODE_TX); + + // set mode to transmit + state = setMode(RADIOLIB_RF69_TX); + + return(state); +} + +int16_t RF69::finishTransmit() { + // clear interrupt flags + clearIRQFlags(); + + // set mode to standby to disable transmitter/RF switch + return(standby()); +} + +int16_t RF69::readData(uint8_t* data, size_t len) { + // set mode to standby + int16_t state = standby(); + RADIOLIB_ASSERT(state); + + // get packet length + size_t length = getPacketLength(); + size_t dumpLen = 0; + if((len != 0) && (len < length)) { + // user requested less data than we got, only return what was requested + dumpLen = length - len; + length = len; + } + + // check address filtering + uint8_t filter = this->mod->SPIgetRegValue(RADIOLIB_RF69_REG_PACKET_CONFIG_1, 2, 1); + if((filter == RADIOLIB_RF69_ADDRESS_FILTERING_NODE) || (filter == RADIOLIB_RF69_ADDRESS_FILTERING_NODE_BROADCAST)) { + this->mod->SPIreadRegister(RADIOLIB_RF69_REG_FIFO); + } + + // read packet data + this->mod->SPIreadRegisterBurst(RADIOLIB_RF69_REG_FIFO, length, data); + + // dump the bytes that weren't requested + if(dumpLen != 0) { + clearFIFO(dumpLen); + } + + // clear internal flag so getPacketLength can return the new packet length + this->packetLengthQueried = false; + + // clear interrupt flags + clearIRQFlags(); + + return(RADIOLIB_ERR_NONE); +} + +int16_t RF69::setOOK(bool enable) { + // set OOK and if successful, save the new setting + int16_t state = RADIOLIB_ERR_NONE; + if(enable) { + state = this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_DATA_MODUL, RADIOLIB_RF69_OOK, 4, 3, 5); + } else { + state = this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_DATA_MODUL, RADIOLIB_RF69_FSK, 4, 3, 5); + } + + if(state == RADIOLIB_ERR_NONE) { + this->ookEnabled = enable; + } + + // call setRxBandwidth again, since register values differ based on OOK mode being enabled + state |= setRxBandwidth(this->rxBandwidth); + + return(state); +} + +int16_t RF69::setOokThresholdType(uint8_t type) { + if((type != RADIOLIB_RF69_OOK_THRESH_FIXED) && (type != RADIOLIB_RF69_OOK_THRESH_PEAK) && (type != RADIOLIB_RF69_OOK_THRESH_AVERAGE)) { + return(RADIOLIB_ERR_INVALID_OOK_RSSI_PEAK_TYPE); + } + return(this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_OOK_PEAK, type, 7, 3, 5)); +} + +int16_t RF69::setOokFixedThreshold(uint8_t value) { + return(this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_OOK_FIX, value, 7, 0, 5)); +} + +int16_t RF69::setOokPeakThresholdDecrement(uint8_t value) { + return(this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_OOK_PEAK, value, 2, 0, 5)); +} + +int16_t RF69::setFrequency(float freq) { + // check allowed frequency range + if(!(((freq > 290.0) && (freq < 340.0)) || + ((freq > 431.0) && (freq < 510.0)) || + ((freq > 862.0) && (freq < 1020.0)))) { + return(RADIOLIB_ERR_INVALID_FREQUENCY); + } + + // set mode to standby + setMode(RADIOLIB_RF69_STANDBY); + + //set carrier frequency + //FRF(23:0) = freq / Fstep = freq * (1 / Fstep) = freq * (2^19 / 32.0) (pag. 17 of datasheet) + uint32_t FRF = (freq * (uint32_t(1) << RADIOLIB_RF69_DIV_EXPONENT)) / RADIOLIB_RF69_CRYSTAL_FREQ; + this->mod->SPIwriteRegister(RADIOLIB_RF69_REG_FRF_MSB, (FRF & 0xFF0000) >> 16); + this->mod->SPIwriteRegister(RADIOLIB_RF69_REG_FRF_MID, (FRF & 0x00FF00) >> 8); + this->mod->SPIwriteRegister(RADIOLIB_RF69_REG_FRF_LSB, FRF & 0x0000FF); + + return(RADIOLIB_ERR_NONE); +} + +int16_t RF69::getFrequency(float *freq) { + uint32_t FRF = 0; + + //FRF(23:0) = [ [FRF_MSB]|[FRF_MID]|[FRF_LSB]] + //FRF(32:0) = [0x00|[FRF_MSB]|[FRF_MID]|[FRF_LSB]] + FRF |= (((uint32_t)(this->mod->SPIgetRegValue(RADIOLIB_RF69_REG_FRF_MSB, 7, 0)) << 16) & 0x00FF0000); + FRF |= (((uint32_t)(this->mod->SPIgetRegValue(RADIOLIB_RF69_REG_FRF_MID, 7, 0)) << 8) & 0x0000FF00); + FRF |= (((uint32_t)(this->mod->SPIgetRegValue(RADIOLIB_RF69_REG_FRF_LSB, 7, 0)) << 0) & 0x000000FF); + + //freq = Fstep * FRF(23:0) = (32.0 / 2^19) * FRF(23:0) (pag. 17 of datasheet) + *freq = FRF * ( RADIOLIB_RF69_CRYSTAL_FREQ / (uint32_t(1) << RADIOLIB_RF69_DIV_EXPONENT) ); + + return(RADIOLIB_ERR_NONE); +} + +int16_t RF69::setBitRate(float br) { + // datasheet says 1.2 kbps should be the smallest possible, but 0.512 works fine + RADIOLIB_CHECK_RANGE(br, 0.5, 300.0, RADIOLIB_ERR_INVALID_BIT_RATE); + + // check bitrate-bandwidth ratio + if(!(br < 2000 * this->rxBandwidth)) { + return(RADIOLIB_ERR_INVALID_BIT_RATE_BW_RATIO); + } + + // set mode to standby + setMode(RADIOLIB_RF69_STANDBY); + + // set bit rate + uint16_t bitRateRaw = 32000 / br; + int16_t state = this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_BITRATE_MSB, (bitRateRaw & 0xFF00) >> 8, 7, 0); + state |= this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_BITRATE_LSB, bitRateRaw & 0x00FF, 7, 0); + if(state == RADIOLIB_ERR_NONE) { + this->bitRate = br; + } + return(state); +} + +int16_t RF69::setRxBandwidth(float rxBw) { + // check bitrate-bandwidth ratio + if(!(this->bitRate < 2000 * rxBw)) { + return(RADIOLIB_ERR_INVALID_BIT_RATE_BW_RATIO); + } + + // set mode to standby + int16_t state = setMode(RADIOLIB_RF69_STANDBY); + RADIOLIB_ASSERT(state); + + // calculate exponent and mantissa values for receiver bandwidth + for(int8_t e = 7; e >= 0; e--) { + for(int8_t m = 2; m >= 0; m--) { + float point = (RADIOLIB_RF69_CRYSTAL_FREQ * 1000000.0)/(((4 * m) + 16) * ((uint32_t)1 << (e + (this->ookEnabled ? 3 : 2)))); + if(fabsf(rxBw - (point / 1000.0)) <= 0.1) { + // set Rx bandwidth + state = this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_RX_BW, (m << 3) | e, 4, 0); + if(state == RADIOLIB_ERR_NONE) { + this->rxBandwidth = rxBw; + } + return(state); + } + } + } + + return(RADIOLIB_ERR_INVALID_RX_BANDWIDTH); +} + +int16_t RF69::setFrequencyDeviation(float freqDev) { + // set frequency deviation to lowest available setting (required for digimodes) + float newFreqDev = freqDev; + if(freqDev < 0.0) { + newFreqDev = 0.6; + } + + // check frequency deviation range + if(!((newFreqDev + this->bitRate/2 <= 500))) { + return(RADIOLIB_ERR_INVALID_FREQUENCY_DEVIATION); + } + + // set mode to standby + setMode(RADIOLIB_RF69_STANDBY); + + // set frequency deviation from carrier frequency + uint32_t fdev = (newFreqDev * (uint32_t(1) << RADIOLIB_RF69_DIV_EXPONENT)) / 32000; + int16_t state = this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_FDEV_MSB, (fdev & 0xFF00) >> 8, 5, 0); + state |= this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_FDEV_LSB, fdev & 0x00FF, 7, 0); + + return(state); +} + +int16_t RF69::getFrequencyDeviation(float *freqDev) { + if(freqDev == NULL) { + return(RADIOLIB_ERR_NULL_POINTER); + } + + if(this->ookEnabled) { + *freqDev = 0.0; + + return(RADIOLIB_ERR_NONE); + } + + // get raw value from register + uint32_t fdev = 0; + fdev |= (uint32_t)((this->mod->SPIgetRegValue(RADIOLIB_RF69_REG_FDEV_MSB, 5, 0) << 8) & 0x0000FF00); + fdev |= (uint32_t)((this->mod->SPIgetRegValue(RADIOLIB_RF69_REG_FDEV_LSB, 7, 0) << 0) & 0x000000FF); + + // calculate frequency deviation from raw value obtained from register + // Fdev = Fstep * Fdev(13:0) (pag. 20 of datasheet) + *freqDev = (1000.0 * fdev * RADIOLIB_RF69_CRYSTAL_FREQ) / + (uint32_t(1) << RADIOLIB_RF69_DIV_EXPONENT); + + return(RADIOLIB_ERR_NONE); +} + +int16_t RF69::setOutputPower(int8_t pwr, bool highPower) { + if(highPower) { + RADIOLIB_CHECK_RANGE(pwr, -2, 20, RADIOLIB_ERR_INVALID_OUTPUT_POWER); + } else { + RADIOLIB_CHECK_RANGE(pwr, -18, 13, RADIOLIB_ERR_INVALID_OUTPUT_POWER); + } + + // set mode to standby + setMode(RADIOLIB_RF69_STANDBY); + + // set output power + int16_t state; + if(highPower) { + // check if both PA1 and PA2 are needed + if(pwr <= 10) { + // -2 to 13 dBm, PA1 is enough + state = this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_PA_LEVEL, RADIOLIB_RF69_PA0_OFF | RADIOLIB_RF69_PA1_ON | RADIOLIB_RF69_PA2_OFF | (power + 18), 7, 0); + } else if(pwr <= 17) { + // 13 to 17 dBm, both PAs required + state = this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_PA_LEVEL, RADIOLIB_RF69_PA0_OFF | RADIOLIB_RF69_PA1_ON | RADIOLIB_RF69_PA2_ON | (power + 14), 7, 0); + } else { + // 18 - 20 dBm, both PAs and hig power settings required + state = this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_PA_LEVEL, RADIOLIB_RF69_PA0_OFF | RADIOLIB_RF69_PA1_ON | RADIOLIB_RF69_PA2_ON | (power + 11), 7, 0); + } + + } else { + // low power module, use only PA0 + state = this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_PA_LEVEL, RADIOLIB_RF69_PA0_ON | RADIOLIB_RF69_PA1_OFF | RADIOLIB_RF69_PA2_OFF | (power + 18), 7, 0); + } + + // cache the power value + if(state == RADIOLIB_ERR_NONE) { + this->power = pwr; + } + + return(state); +} + +int16_t RF69::setSyncWord(uint8_t* syncWord, size_t len, uint8_t maxErrBits) { + // check constraints + if((maxErrBits > 7) || (len > 8)) { + return(RADIOLIB_ERR_INVALID_SYNC_WORD); + } + + // sync word must not contain value 0x00 + for(uint8_t i = 0; i < len; i++) { + if(syncWord[i] == 0x00) { + return(RADIOLIB_ERR_INVALID_SYNC_WORD); + } + } + + int16_t state = enableSyncWordFiltering(maxErrBits); + RADIOLIB_ASSERT(state); + + // set sync word register + this->mod->SPIwriteRegisterBurst(RADIOLIB_RF69_REG_SYNC_VALUE_1, syncWord, len); + + if(state == RADIOLIB_ERR_NONE) { + this->syncWordLength = len; + } + + return(state); +} + +int16_t RF69::setPreambleLength(uint8_t preambleLen) { + // RF69 configures preamble length in bytes + if(preambleLen % 8 != 0) { + return(RADIOLIB_ERR_INVALID_PREAMBLE_LENGTH); + } + + uint8_t preLenBytes = preambleLen / 8; + this->mod->SPIwriteRegister(RADIOLIB_RF69_REG_PREAMBLE_MSB, 0x00); + + return (this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_PREAMBLE_LSB, preLenBytes)); +} + +int16_t RF69::setNodeAddress(uint8_t nodeAddr) { + // enable address filtering (node only) + int16_t state = this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_PACKET_CONFIG_1, RADIOLIB_RF69_ADDRESS_FILTERING_NODE, 2, 1); + RADIOLIB_ASSERT(state); + + // set node address + return(this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_NODE_ADRS, nodeAddr)); +} + +int16_t RF69::setBroadcastAddress(uint8_t broadAddr) { + // enable address filtering (node + broadcast) + int16_t state = this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_PACKET_CONFIG_1, RADIOLIB_RF69_ADDRESS_FILTERING_NODE_BROADCAST, 2, 1); + RADIOLIB_ASSERT(state); + + // set broadcast address + return(this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_BROADCAST_ADRS, broadAddr)); +} + +int16_t RF69::disableAddressFiltering() { + // disable address filtering + int16_t state = this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_PACKET_CONFIG_1, RADIOLIB_RF69_ADDRESS_FILTERING_OFF, 2, 1); + RADIOLIB_ASSERT(state); + + // set node address to default (0x00) + state = this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_NODE_ADRS, 0x00); + RADIOLIB_ASSERT(state); + + // set broadcast address to default (0x00) + return(this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_BROADCAST_ADRS, 0x00)); +} + +void RF69::setAmbientTemperature(int16_t tempAmbient) { + this->tempOffset = getTemperature() - tempAmbient; +} + +int16_t RF69::getTemperature() { + // set mode to STANDBY + setMode(RADIOLIB_RF69_STANDBY); + + // start temperature measurement + this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_TEMP_1, RADIOLIB_RF69_TEMP_MEAS_START, 3, 3); + + // wait until measurement is finished + while(this->mod->SPIgetRegValue(RADIOLIB_RF69_REG_TEMP_1, 2, 2) == RADIOLIB_RF69_TEMP_MEAS_RUNNING) { + // check every 10 us + this->mod->hal->delay(10); + } + int8_t rawTemp = this->mod->SPIgetRegValue(RADIOLIB_RF69_REG_TEMP_2); + + return(0 - (rawTemp + this->tempOffset)); +} + +size_t RF69::getPacketLength(bool update) { + if(!this->packetLengthQueried && update) { + if (this->packetLengthConfig == RADIOLIB_RF69_PACKET_FORMAT_VARIABLE) { + this->packetLength = this->mod->SPIreadRegister(RADIOLIB_RF69_REG_FIFO); + } else { + this->packetLength = this->mod->SPIreadRegister(RADIOLIB_RF69_REG_PAYLOAD_LENGTH); + } + this->packetLengthQueried = true; + } + + return(this->packetLength); +} + +int16_t RF69::fixedPacketLengthMode(uint8_t len) { + return(setPacketMode(RADIOLIB_RF69_PACKET_FORMAT_FIXED, len)); +} + +int16_t RF69::variablePacketLengthMode(uint8_t maxLen) { + return(setPacketMode(RADIOLIB_RF69_PACKET_FORMAT_VARIABLE, maxLen)); +} + +int16_t RF69::enableSyncWordFiltering(uint8_t maxErrBits) { + // enable sync word recognition + return(this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_SYNC_CONFIG, RADIOLIB_RF69_SYNC_ON | RADIOLIB_RF69_FIFO_FILL_CONDITION_SYNC | (this->syncWordLength - 1) << 3 | maxErrBits, 7, 0)); +} + +int16_t RF69::disableSyncWordFiltering() { + // disable sync word detection and generation + return(this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_SYNC_CONFIG, RADIOLIB_RF69_SYNC_OFF | RADIOLIB_RF69_FIFO_FILL_CONDITION, 7, 6)); +} + +int16_t RF69::enableContinuousModeBitSync() { + int16_t state = this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_DATA_MODUL, RADIOLIB_RF69_CONTINUOUS_MODE_WITH_SYNC, 6, 5); + if(state == RADIOLIB_ERR_NONE) { + this->bitSync = true; + } + + return(state); +} + +int16_t RF69::disableContinuousModeBitSync() { + int16_t state = this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_DATA_MODUL, RADIOLIB_RF69_CONTINUOUS_MODE, 6, 5); + if(state == RADIOLIB_ERR_NONE) { + this->bitSync = false; + } + + return(state); +} + +int16_t RF69::setCrcFiltering(bool crcOn) { + if (crcOn == true) { + return(this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_PACKET_CONFIG_1, RADIOLIB_RF69_CRC_ON, 4, 4)); + } else { + return(this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_PACKET_CONFIG_1, RADIOLIB_RF69_CRC_OFF, 4, 4)); + } +} + +int16_t RF69::setPromiscuousMode(bool enable) { + int16_t state = RADIOLIB_ERR_NONE; + + if (this->promiscuous == enable) { + return(state); + } + + if (enable == true) { + // disable preamble detection and generation + state = setPreambleLength(0); + RADIOLIB_ASSERT(state); + + // disable sync word filtering and insertion + state = disableSyncWordFiltering(); + RADIOLIB_ASSERT(state); + + // disable CRC filtering + state = setCrcFiltering(false); + } else { + // enable preamble detection and generation + state = setPreambleLength(RADIOLIB_RF69_DEFAULT_PREAMBLELEN); + RADIOLIB_ASSERT(state); + + // enable sync word filtering and insertion + state = enableSyncWordFiltering(); + RADIOLIB_ASSERT(state); + + // enable CRC filtering + state = setCrcFiltering(true); + } + if(state == RADIOLIB_ERR_NONE) { + this->promiscuous = enable; + } + + + return(state); +} + +int16_t RF69::setDataShaping(uint8_t sh) { + // set mode to standby + int16_t state = standby(); + RADIOLIB_ASSERT(state); + + // set data shaping + switch(sh) { + case RADIOLIB_SHAPING_NONE: + return(this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_DATA_MODUL, RADIOLIB_RF69_NO_SHAPING, 1, 0)); + case RADIOLIB_SHAPING_0_3: + return(this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_DATA_MODUL, RADIOLIB_RF69_FSK_GAUSSIAN_0_3, 1, 0)); + case RADIOLIB_SHAPING_0_5: + return(this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_DATA_MODUL, RADIOLIB_RF69_FSK_GAUSSIAN_0_5, 1, 0)); + case RADIOLIB_SHAPING_1_0: + return(this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_DATA_MODUL, RADIOLIB_RF69_FSK_GAUSSIAN_1_0, 1, 0)); + default: + return(RADIOLIB_ERR_INVALID_DATA_SHAPING); + } +} + +int16_t RF69::setEncoding(uint8_t encoding) { + // set mode to standby + int16_t state = standby(); + RADIOLIB_ASSERT(state); + + // set encoding + switch(encoding) { + case RADIOLIB_ENCODING_NRZ: + return(this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_PACKET_CONFIG_1, RADIOLIB_RF69_DC_FREE_NONE, 6, 5)); + case RADIOLIB_ENCODING_MANCHESTER: + return(this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_PACKET_CONFIG_1, RADIOLIB_RF69_DC_FREE_MANCHESTER, 6, 5)); + case RADIOLIB_ENCODING_WHITENING: + return(this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_PACKET_CONFIG_1, RADIOLIB_RF69_DC_FREE_WHITENING, 6, 5)); + default: + return(RADIOLIB_ERR_INVALID_ENCODING); + } +} + +int16_t RF69::setLnaTestBoost(bool value) { + if(value) { + return (this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_TEST_LNA, RADIOLIB_RF69_TEST_LNA_BOOST_HIGH, 7, 0)); + } + + return(this->mod->SPIsetRegValue(RADIOLIB_RF69_TEST_LNA_BOOST_NORMAL, RADIOLIB_RF69_TEST_LNA_BOOST_HIGH, 7, 0)); +} + +float RF69::getRSSI() { + return(-1.0 * (this->mod->SPIgetRegValue(RADIOLIB_RF69_REG_RSSI_VALUE)/2.0)); +} + +int16_t RF69::setRSSIThreshold(float dbm) { + RADIOLIB_CHECK_RANGE(dbm, -127.5, 0, RADIOLIB_ERR_INVALID_RSSI_THRESHOLD); + + return this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_RSSI_THRESH, (uint8_t)(-2.0 * dbm), 7, 0); +} + +void RF69::setRfSwitchPins(uint32_t rxEn, uint32_t txEn) { + this->mod->setRfSwitchPins(rxEn, txEn); +} + +void RF69::setRfSwitchTable(const uint32_t (&pins)[Module::RFSWITCH_MAX_PINS], const Module::RfSwitchMode_t table[]) { + this->mod->setRfSwitchTable(pins, table); +} + +uint8_t RF69::randomByte() { + // set mode to Rx + setMode(RADIOLIB_RF69_RX); + + // wait a bit for the RSSI reading to stabilise + this->mod->hal->delay(10); + + // read RSSI value 8 times, always keep just the least significant bit + uint8_t randByte = 0x00; + for(uint8_t i = 0; i < 8; i++) { + randByte |= ((this->mod->SPIreadRegister(RADIOLIB_RF69_REG_RSSI_VALUE) & 0x01) << i); + } + + // set mode to standby + setMode(RADIOLIB_RF69_STANDBY); + + return(randByte); +} + +#if !RADIOLIB_EXCLUDE_DIRECT_RECEIVE +void RF69::setDirectAction(void (*func)(void)) { + setDio1Action(func); +} + +void RF69::readBit(uint32_t pin) { + updateDirectBuffer((uint8_t)this->mod->hal->digitalRead(pin)); +} +#endif + +int16_t RF69::setDIOMapping(uint32_t pin, uint32_t value) { + if(pin > 5) { + return(RADIOLIB_ERR_INVALID_DIO_PIN); + } + + if(pin < 4) { + return(this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_DIO_MAPPING_1, value, 7 - 2 * pin, 6 - 2 * pin)); + } + + return(this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_DIO_MAPPING_2, value, 15 - 2 * pin, 14 - 2 * pin)); +} + +Module* RF69::getMod() { + return(this->mod); +} + +int16_t RF69::getChipVersion() { + return(this->mod->SPIgetRegValue(RADIOLIB_RF69_REG_VERSION)); +} + +int16_t RF69::config() { + int16_t state = RADIOLIB_ERR_NONE; + + // set mode to STANDBY + state = setMode(RADIOLIB_RF69_STANDBY); + RADIOLIB_ASSERT(state); + + // set operation modes + state = this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_OP_MODE, RADIOLIB_RF69_SEQUENCER_ON | RADIOLIB_RF69_LISTEN_OFF, 7, 6); + RADIOLIB_ASSERT(state); + + // enable over-current protection + state = this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_OCP, RADIOLIB_RF69_OCP_ON, 4, 4); + RADIOLIB_ASSERT(state); + + // set data mode, modulation type and shaping + state = this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_DATA_MODUL, RADIOLIB_RF69_PACKET_MODE | RADIOLIB_RF69_FSK, 6, 3); + state |= this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_DATA_MODUL, RADIOLIB_RF69_FSK_GAUSSIAN_0_3, 1, 0); + RADIOLIB_ASSERT(state); + + // set RSSI threshold + state = this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_RSSI_THRESH, RADIOLIB_RF69_RSSI_THRESHOLD, 7, 0); + RADIOLIB_ASSERT(state); + + // reset FIFO flag + this->mod->SPIwriteRegister(RADIOLIB_RF69_REG_IRQ_FLAGS_2, RADIOLIB_RF69_IRQ_FIFO_OVERRUN); + + // disable ClkOut on DIO5 + state = this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_DIO_MAPPING_2, RADIOLIB_RF69_CLK_OUT_OFF, 2, 0); + RADIOLIB_ASSERT(state); + + // set packet configuration and disable encryption + state = this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_PACKET_CONFIG_1, RADIOLIB_RF69_PACKET_FORMAT_VARIABLE | RADIOLIB_RF69_DC_FREE_NONE | RADIOLIB_RF69_CRC_ON | RADIOLIB_RF69_CRC_AUTOCLEAR_ON | RADIOLIB_RF69_ADDRESS_FILTERING_OFF, 7, 1); + state |= this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_PACKET_CONFIG_2, RADIOLIB_RF69_INTER_PACKET_RX_DELAY, 7, 4); + state |= this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_PACKET_CONFIG_2, RADIOLIB_RF69_AUTO_RX_RESTART_ON | RADIOLIB_RF69_AES_OFF, 1, 0); + RADIOLIB_ASSERT(state); + + // set payload length + state = this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_PAYLOAD_LENGTH, RADIOLIB_RF69_PAYLOAD_LENGTH, 7, 0); + RADIOLIB_ASSERT(state); + + // set FIFO threshold + state = this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_FIFO_THRESH, RADIOLIB_RF69_TX_START_CONDITION_FIFO_NOT_EMPTY | RADIOLIB_RF69_FIFO_THRESH, 7, 0); + RADIOLIB_ASSERT(state); + + // set Rx timeouts + state = this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_RX_TIMEOUT_1, RADIOLIB_RF69_TIMEOUT_RX_START, 7, 0); + state |= this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_RX_TIMEOUT_2, RADIOLIB_RF69_TIMEOUT_RSSI_THRESH, 7, 0); + RADIOLIB_ASSERT(state); + + // enable improved fading margin + state = this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_TEST_DAGC, RADIOLIB_RF69_CONTINUOUS_DAGC_LOW_BETA_OFF, 7, 0); + + return(state); +} + +int16_t RF69::setPacketMode(uint8_t mode, uint8_t len) { + // check length + if (len > RADIOLIB_RF69_MAX_PACKET_LENGTH) { + return(RADIOLIB_ERR_PACKET_TOO_LONG); + } + + // set to fixed packet length + int16_t state = this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_PACKET_CONFIG_1, mode, 7, 7); + RADIOLIB_ASSERT(state); + + // set length to register + state = this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_PAYLOAD_LENGTH, len); + RADIOLIB_ASSERT(state); + + // update the cached value + this->packetLengthConfig = mode; + return(state); +} + +int16_t RF69::setMode(uint8_t mode) { + return(this->mod->SPIsetRegValue(RADIOLIB_RF69_REG_OP_MODE, mode, 4, 2)); +} + +void RF69::clearIRQFlags() { + this->mod->SPIwriteRegister(RADIOLIB_RF69_REG_IRQ_FLAGS_1, 0b11111111); + this->mod->SPIwriteRegister(RADIOLIB_RF69_REG_IRQ_FLAGS_2, 0b11111111); +} + +void RF69::clearFIFO(size_t count) { + while(count) { + this->mod->SPIreadRegister(RADIOLIB_RF69_REG_FIFO); + count--; + } +} + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/RF69/RF69.h b/software/firmware/source/libraries/RadioLib/src/modules/RF69/RF69.h new file mode 100644 index 000000000..a7a25e494 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/RF69/RF69.h @@ -0,0 +1,1038 @@ +#if !defined(_RADIOLIB_RF69_H) +#define _RADIOLIB_RF69_H + +#include "../../TypeDef.h" + +#if !RADIOLIB_EXCLUDE_RF69 + +#include "../../Module.h" + +#include "../../protocols/PhysicalLayer/PhysicalLayer.h" + +// RF69 physical layer properties +#define RADIOLIB_RF69_FREQUENCY_STEP_SIZE 61.03515625 +#define RADIOLIB_RF69_MAX_PACKET_LENGTH 64 +#define RADIOLIB_RF69_CRYSTAL_FREQ 32.0 +#define RADIOLIB_RF69_DIV_EXPONENT 19 + +// RF69 register map +#define RADIOLIB_RF69_REG_FIFO 0x00 +#define RADIOLIB_RF69_REG_OP_MODE 0x01 +#define RADIOLIB_RF69_REG_DATA_MODUL 0x02 +#define RADIOLIB_RF69_REG_BITRATE_MSB 0x03 +#define RADIOLIB_RF69_REG_BITRATE_LSB 0x04 +#define RADIOLIB_RF69_REG_FDEV_MSB 0x05 +#define RADIOLIB_RF69_REG_FDEV_LSB 0x06 +#define RADIOLIB_RF69_REG_FRF_MSB 0x07 +#define RADIOLIB_RF69_REG_FRF_MID 0x08 +#define RADIOLIB_RF69_REG_FRF_LSB 0x09 +#define RADIOLIB_RF69_REG_OSC_1 0x0A +#define RADIOLIB_RF69_REG_AFC_CTRL 0x0B +#define RADIOLIB_RF69_REG_LISTEN_1 0x0D +#define RADIOLIB_RF69_REG_LISTEN_2 0x0E +#define RADIOLIB_RF69_REG_LISTEN_3 0x0F +#define RADIOLIB_RF69_REG_VERSION 0x10 +#define RADIOLIB_RF69_REG_PA_LEVEL 0x11 +#define RADIOLIB_RF69_REG_PA_RAMP 0x12 +#define RADIOLIB_RF69_REG_OCP 0x13 +#define RADIOLIB_RF69_REG_LNA 0x18 +#define RADIOLIB_RF69_REG_RX_BW 0x19 +#define RADIOLIB_RF69_REG_AFC_BW 0x1A +#define RADIOLIB_RF69_REG_OOK_PEAK 0x1B +#define RADIOLIB_RF69_REG_OOK_AVG 0x1C +#define RADIOLIB_RF69_REG_OOK_FIX 0x1D +#define RADIOLIB_RF69_REG_AFC_FEI 0x1E +#define RADIOLIB_RF69_REG_AFC_MSB 0x1F +#define RADIOLIB_RF69_REG_AFC_LSB 0x20 +#define RADIOLIB_RF69_REG_FEI_MSB 0x21 +#define RADIOLIB_RF69_REG_FEI_LSB 0x22 +#define RADIOLIB_RF69_REG_RSSI_CONFIG 0x23 +#define RADIOLIB_RF69_REG_RSSI_VALUE 0x24 +#define RADIOLIB_RF69_REG_DIO_MAPPING_1 0x25 +#define RADIOLIB_RF69_REG_DIO_MAPPING_2 0x26 +#define RADIOLIB_RF69_REG_IRQ_FLAGS_1 0x27 +#define RADIOLIB_RF69_REG_IRQ_FLAGS_2 0x28 +#define RADIOLIB_RF69_REG_RSSI_THRESH 0x29 +#define RADIOLIB_RF69_REG_RX_TIMEOUT_1 0x2A +#define RADIOLIB_RF69_REG_RX_TIMEOUT_2 0x2B +#define RADIOLIB_RF69_REG_PREAMBLE_MSB 0x2C +#define RADIOLIB_RF69_REG_PREAMBLE_LSB 0x2D +#define RADIOLIB_RF69_REG_SYNC_CONFIG 0x2E +#define RADIOLIB_RF69_REG_SYNC_VALUE_1 0x2F +#define RADIOLIB_RF69_REG_SYNC_VALUE_2 0x30 +#define RADIOLIB_RF69_REG_SYNC_VALUE_3 0x31 +#define RADIOLIB_RF69_REG_SYNC_VALUE_4 0x32 +#define RADIOLIB_RF69_REG_SYNC_VALUE_5 0x33 +#define RADIOLIB_RF69_REG_SYNC_VALUE_6 0x34 +#define RADIOLIB_RF69_REG_SYNC_VALUE_7 0x35 +#define RADIOLIB_RF69_REG_SYNC_VALUE_8 0x36 +#define RADIOLIB_RF69_REG_PACKET_CONFIG_1 0x37 +#define RADIOLIB_RF69_REG_PAYLOAD_LENGTH 0x38 +#define RADIOLIB_RF69_REG_NODE_ADRS 0x39 +#define RADIOLIB_RF69_REG_BROADCAST_ADRS 0x3A +#define RADIOLIB_RF69_REG_AUTO_MODES 0x3B +#define RADIOLIB_RF69_REG_FIFO_THRESH 0x3C +#define RADIOLIB_RF69_REG_PACKET_CONFIG_2 0x3D +#define RADIOLIB_RF69_REG_AES_KEY_1 0x3E +#define RADIOLIB_RF69_REG_AES_KEY_2 0x3F +#define RADIOLIB_RF69_REG_AES_KEY_3 0x40 +#define RADIOLIB_RF69_REG_AES_KEY_4 0x41 +#define RADIOLIB_RF69_REG_AES_KEY_5 0x42 +#define RADIOLIB_RF69_REG_AES_KEY_6 0x43 +#define RADIOLIB_RF69_REG_AES_KEY_7 0x44 +#define RADIOLIB_RF69_REG_AES_KEY_8 0x45 +#define RADIOLIB_RF69_REG_AES_KEY_9 0x46 +#define RADIOLIB_RF69_REG_AES_KEY_10 0x47 +#define RADIOLIB_RF69_REG_AES_KEY_11 0x48 +#define RADIOLIB_RF69_REG_AES_KEY_12 0x49 +#define RADIOLIB_RF69_REG_AES_KEY_13 0x4A +#define RADIOLIB_RF69_REG_AES_KEY_14 0x4B +#define RADIOLIB_RF69_REG_AES_KEY_15 0x4C +#define RADIOLIB_RF69_REG_AES_KEY_16 0x4D +#define RADIOLIB_RF69_REG_TEMP_1 0x4E +#define RADIOLIB_RF69_REG_TEMP_2 0x4F +#define RADIOLIB_RF69_REG_TEST_LNA 0x58 +#define RADIOLIB_RF69_REG_TEST_PA1 0x5A +#define RADIOLIB_RF69_REG_TEST_PA2 0x5C +#define RADIOLIB_RF69_REG_TEST_DAGC 0x6F + +// RF69 modem settings +// RADIOLIB_RF69_REG_OP_MODE MSB LSB DESCRIPTION +#define RADIOLIB_RF69_SEQUENCER_OFF 0b10000000 // 7 7 disable automatic sequencer +#define RADIOLIB_RF69_SEQUENCER_ON 0b00000000 // 7 7 enable automatic sequencer +#define RADIOLIB_RF69_LISTEN_OFF 0b00000000 // 6 6 disable Listen mode +#define RADIOLIB_RF69_LISTEN_ON 0b01000000 // 6 6 enable Listen mode +#define RADIOLIB_RF69_LISTEN_ABORT 0b00100000 // 5 5 abort Listen mode (has to be set together with RF69_LISTEN_OFF) +#define RADIOLIB_RF69_SLEEP 0b00000000 // 4 2 sleep +#define RADIOLIB_RF69_STANDBY 0b00000100 // 4 2 standby +#define RADIOLIB_RF69_FS 0b00001000 // 4 2 frequency synthesis +#define RADIOLIB_RF69_TX 0b00001100 // 4 2 transmit +#define RADIOLIB_RF69_RX 0b00010000 // 4 2 receive + +// RADIOLIB_RF69_REG_DATA_MODUL +#define RADIOLIB_RF69_PACKET_MODE 0b00000000 // 6 5 packet mode (default) +#define RADIOLIB_RF69_CONTINUOUS_MODE_WITH_SYNC 0b01000000 // 6 5 continuous mode with bit synchronizer +#define RADIOLIB_RF69_CONTINUOUS_MODE 0b01100000 // 6 5 continuous mode without bit synchronizer +#define RADIOLIB_RF69_FSK 0b00000000 // 4 3 modulation: FSK (default) +#define RADIOLIB_RF69_OOK 0b00001000 // 4 3 OOK +#define RADIOLIB_RF69_NO_SHAPING 0b00000000 // 1 0 modulation shaping: no shaping (default) +#define RADIOLIB_RF69_FSK_GAUSSIAN_1_0 0b00000001 // 1 0 FSK modulation Gaussian filter, BT = 1.0 +#define RADIOLIB_RF69_FSK_GAUSSIAN_0_5 0b00000010 // 1 0 FSK modulation Gaussian filter, BT = 0.5 +#define RADIOLIB_RF69_FSK_GAUSSIAN_0_3 0b00000011 // 1 0 FSK modulation Gaussian filter, BT = 0.3 +#define RADIOLIB_RF69_OOK_FILTER_BR 0b00000001 // 1 0 OOK modulation filter, f_cutoff = BR +#define RADIOLIB_RF69_OOK_FILTER_2BR 0b00000010 // 1 0 OOK modulation filter, f_cutoff = 2*BR + +// RADIOLIB_RF69_REG_BITRATE_MSB + REG_BITRATE_LSB +#define RADIOLIB_RF69_BITRATE_MSB 0x1A // 7 0 bit rate setting: rate = F(XOSC) / BITRATE +#define RADIOLIB_RF69_BITRATE_LSB 0x0B // 7 0 default value: 4.8 kbps + +// RADIOLIB_RF69_REG_FDEV_MSB + REG_FDEV_LSB +#define RADIOLIB_RF69_FDEV_MSB 0x00 // 5 0 frequency deviation: f_dev = f_step * FDEV +#define RADIOLIB_RF69_FDEV_LSB 0x52 // 7 0 default value: 5 kHz + +// RADIOLIB_RF69_REG_FRF_MSB + REG_FRF_MID + REG_FRF_LSB +#define RADIOLIB_RF69_FRF_MSB 0xE4 // 7 0 carrier frequency setting: f_RF = (F(XOSC) * FRF)/2^19 +#define RADIOLIB_RF69_FRF_MID 0xC0 // 7 0 where F(XOSC) = 32 MHz +#define RADIOLIB_RF69_FRF_LSB 0x00 // 7 0 default value: 915 MHz + +// RADIOLIB_RF69_REG_OSC_1 +#define RADIOLIB_RF69_RC_CAL_START 0b10000000 // 7 7 force RC oscillator calibration +#define RADIOLIB_RF69_RC_CAL_RUNNING 0b00000000 // 6 6 RC oscillator calibration is still running +#define RADIOLIB_RF69_RC_CAL_DONE 0b00000000 // 5 5 RC oscillator calibration has finished + +// RADIOLIB_RF69_REG_AFC_CTRL +#define RADIOLIB_RF69_AFC_LOW_BETA_OFF 0b00000000 // 5 5 standard AFC routine +#define RADIOLIB_RF69_AFC_LOW_BETA_ON 0b00100000 // 5 5 improved AFC routine for signals with modulation index less than 2 + +// RADIOLIB_RF69_REG_LISTEN_1 +#define RADIOLIB_RF69_LISTEN_RES_IDLE_64_US 0b01000000 // 7 6 resolution of Listen mode idle time: 64 us +#define RADIOLIB_RF69_LISTEN_RES_IDLE_4_1_MS 0b10000000 // 7 6 4.1 ms (default) +#define RADIOLIB_RF69_LISTEN_RES_IDLE_262_MS 0b11000000 // 7 6 262 ms +#define RADIOLIB_RF69_LISTEN_RES_RX_64_US 0b00010000 // 5 4 resolution of Listen mode rx time: 64 us (default) +#define RADIOLIB_RF69_LISTEN_RES_RX_4_1_MS 0b00100000 // 5 4 4.1 ms +#define RADIOLIB_RF69_LISTEN_RES_RX_262_MS 0b00110000 // 5 4 262 ms +#define RADIOLIB_RF69_LISTEN_ACCEPT_ABOVE_RSSI_THRESH 0b00000000 // 3 3 packet acceptance criteria: RSSI above threshold +#define RADIOLIB_RF69_LISTEN_ACCEPT_MATCH_SYNC_ADDRESS 0b00001000 // 3 3 RSSI above threshold AND sync address matched +#define RADIOLIB_RF69_LISTEN_END_KEEP_RX 0b00000000 // 2 1 action after packet acceptance: stay in Rx mode +#define RADIOLIB_RF69_LISTEN_END_KEEP_RX_TIMEOUT 0b00000010 // 2 1 stay in Rx mode until timeout (default) +#define RADIOLIB_RF69_LISTEN_END_KEEP_RX_TIMEOUT_RESUME 0b00000100 // 2 1 stay in Rx mode until timeout, Listen mode will resume + +// RADIOLIB_RF69_REG_LISTEN_2 +#define RADIOLIB_RF69_LISTEN_COEF_IDLE 0xF5 // 7 0 duration of idle phase in Listen mode + +// RADIOLIB_RF69_REG_LISTEN_3 +#define RADIOLIB_RF69_LISTEN_COEF_RX 0x20 // 7 0 duration of Rx phase in Listen mode + +// RADIOLIB_RF69_REG_VERSION +#define RADIOLIB_RF69_CHIP_VERSION 0x24 // 7 0 + +// RADIOLIB_RF69_REG_PA_LEVEL +#define RADIOLIB_RF69_PA0_OFF 0b00000000 // 7 7 PA0 disabled +#define RADIOLIB_RF69_PA0_ON 0b10000000 // 7 7 PA0 enabled (default) +#define RADIOLIB_RF69_PA1_OFF 0b00000000 // 6 6 PA1 disabled (default) +#define RADIOLIB_RF69_PA1_ON 0b01000000 // 6 6 PA1 enabled +#define RADIOLIB_RF69_PA2_OFF 0b00000000 // 5 5 PA2 disabled (default) +#define RADIOLIB_RF69_PA2_ON 0b00100000 // 5 5 PA2 enabled +#define RADIOLIB_RF69_OUTPUT_POWER 0b00011111 // 4 0 output power: P_out = -18 + OUTPUT_POWER + +// RADIOLIB_RF69_REG_PA_RAMP +#define RADIOLIB_RF69_PA_RAMP_3_4_MS 0b00000000 // 3 0 PA ramp rise/fall time: 3.4 ms +#define RADIOLIB_RF69_PA_RAMP_2_MS 0b00000001 // 3 0 2 ms +#define RADIOLIB_RF69_PA_RAMP_1_MS 0b00000010 // 3 0 1 ms +#define RADIOLIB_RF69_PA_RAMP_500_US 0b00000011 // 3 0 500 us +#define RADIOLIB_RF69_PA_RAMP_250_US 0b00000100 // 3 0 250 us +#define RADIOLIB_RF69_PA_RAMP_125_US 0b00000101 // 3 0 125 us +#define RADIOLIB_RF69_PA_RAMP_100_US 0b00000110 // 3 0 100 us +#define RADIOLIB_RF69_PA_RAMP_62_US 0b00000111 // 3 0 62 us +#define RADIOLIB_RF69_PA_RAMP_50_US 0b00001000 // 3 0 50 us +#define RADIOLIB_RF69_PA_RAMP_40_US 0b00001001 // 3 0 40 us (default) +#define RADIOLIB_RF69_PA_RAMP_31_US 0b00001010 // 3 0 31 us +#define RADIOLIB_RF69_PA_RAMP_25_US 0b00001011 // 3 0 25 us +#define RADIOLIB_RF69_PA_RAMP_20_US 0b00001100 // 3 0 20 us +#define RADIOLIB_RF69_PA_RAMP_15_US 0b00001101 // 3 0 15 us +#define RADIOLIB_RF69_PA_RAMP_12_US 0b00001110 // 3 0 12 us +#define RADIOLIB_RF69_PA_RAMP_10_US 0b00001111 // 3 0 10 us + +// RADIOLIB_RF69_REG_OCP +#define RADIOLIB_RF69_OCP_OFF 0b00000000 // 4 4 PA overload current protection disabled +#define RADIOLIB_RF69_OCP_ON 0b00010000 // 4 4 PA overload current protection enabled +#define RADIOLIB_RF69_OCP_TRIM 0b00001010 // 3 0 OCP current: I_max(OCP_TRIM = 0b1010) = 95 mA + +// RADIOLIB_RF69_REG_LNA +#define RADIOLIB_RF69_LNA_Z_IN_50_OHM 0b00000000 // 7 7 LNA input impedance: 50 ohm +#define RADIOLIB_RF69_LNA_Z_IN_200_OHM 0b10000000 // 7 7 200 ohm +#define RADIOLIB_RF69_LNA_CURRENT_GAIN 0b00001000 // 5 3 manually set LNA current gain +#define RADIOLIB_RF69_LNA_GAIN_AUTO 0b00000000 // 2 0 LNA gain setting: set automatically by AGC +#define RADIOLIB_RF69_LNA_GAIN_MAX 0b00000001 // 2 0 max gain +#define RADIOLIB_RF69_LNA_GAIN_MAX_6_DB 0b00000010 // 2 0 max gain - 6 dB +#define RADIOLIB_RF69_LNA_GAIN_MAX_12_DB 0b00000011 // 2 0 max gain - 12 dB +#define RADIOLIB_RF69_LNA_GAIN_MAX_24_DB 0b00000100 // 2 0 max gain - 24 dB +#define RADIOLIB_RF69_LNA_GAIN_MAX_36_DB 0b00000101 // 2 0 max gain - 36 dB +#define RADIOLIB_RF69_LNA_GAIN_MAX_48_DB 0b00000110 // 2 0 max gain - 48 dB + +// RADIOLIB_RF69_REG_RX_BW +#define RADIOLIB_RF69_DCC_FREQ 0b01000000 // 7 5 DC offset canceller cutoff frequency (4% Rx BW by default) +#define RADIOLIB_RF69_RX_BW_MANT_16 0b00000000 // 4 3 Channel filter bandwidth FSK: RxBw = F(XOSC)/(RxBwMant * 2^(RxBwExp + 2)) +#define RADIOLIB_RF69_RX_BW_MANT_20 0b00001000 // 4 3 OOK: RxBw = F(XOSC)/(RxBwMant * 2^(RxBwExp + 3)) +#define RADIOLIB_RF69_RX_BW_MANT_24 0b00010000 // 4 3 +#define RADIOLIB_RF69_RX_BW_EXP 0b00000101 // 2 0 default RxBwExp value = 5 + +// RADIOLIB_RF69_REG_AFC_BW +#define RADIOLIB_RF69_DCC_FREQ_AFC 0b10000000 // 7 5 default DccFreq parameter for AFC +#define RADIOLIB_RF69_DCC_RX_BW_MANT_AFC 0b00001000 // 4 3 default RxBwMant parameter for AFC +#define RADIOLIB_RF69_DCC_RX_BW_EXP_AFC 0b00000011 // 2 0 default RxBwExp parameter for AFC + +// RADIOLIB_RF69_REG_OOK_PEAK +#define RADIOLIB_RF69_OOK_THRESH_FIXED 0b00000000 // 7 6 OOK threshold type: fixed +#define RADIOLIB_RF69_OOK_THRESH_PEAK 0b01000000 // 7 6 peak (default) +#define RADIOLIB_RF69_OOK_THRESH_AVERAGE 0b10000000 // 7 6 average +#define RADIOLIB_RF69_OOK_PEAK_THRESH_STEP_0_5_DB 0b00000000 // 5 3 OOK demodulator step size: 0.5 dB (default) +#define RADIOLIB_RF69_OOK_PEAK_THRESH_STEP_1_0_DB 0b00001000 // 5 3 1.0 dB +#define RADIOLIB_RF69_OOK_PEAK_THRESH_STEP_1_5_DB 0b00010000 // 5 3 1.5 dB +#define RADIOLIB_RF69_OOK_PEAK_THRESH_STEP_2_0_DB 0b00011000 // 5 3 2.0 dB +#define RADIOLIB_RF69_OOK_PEAK_THRESH_STEP_3_0_DB 0b00100000 // 5 3 3.0 dB +#define RADIOLIB_RF69_OOK_PEAK_THRESH_STEP_4_0_DB 0b00101000 // 5 3 4.0 dB +#define RADIOLIB_RF69_OOK_PEAK_THRESH_STEP_5_0_DB 0b00110000 // 5 3 5.0 dB +#define RADIOLIB_RF69_OOK_PEAK_THRESH_STEP_6_0_DB 0b00111000 // 5 3 6.0 dB +#define RADIOLIB_RF69_OOK_PEAK_THRESH_DEC_1_1_CHIP 0b00000000 // 2 0 OOK demodulator step period: once per chip (default) +#define RADIOLIB_RF69_OOK_PEAK_THRESH_DEC_1_2_CHIP 0b00000001 // 2 0 once every 2 chips +#define RADIOLIB_RF69_OOK_PEAK_THRESH_DEC_1_4_CHIP 0b00000010 // 2 0 once every 4 chips +#define RADIOLIB_RF69_OOK_PEAK_THRESH_DEC_1_8_CHIP 0b00000011 // 2 0 once every 8 chips +#define RADIOLIB_RF69_OOK_PEAK_THRESH_DEC_2_1_CHIP 0b00000100 // 2 0 2 times per chip +#define RADIOLIB_RF69_OOK_PEAK_THRESH_DEC_4_1_CHIP 0b00000101 // 2 0 4 times per chip +#define RADIOLIB_RF69_OOK_PEAK_THRESH_DEC_8_1_CHIP 0b00000110 // 2 0 8 times per chip +#define RADIOLIB_RF69_OOK_PEAK_THRESH_DEC_16_1_CHIP 0b00000111 // 2 0 16 times per chip + +// RADIOLIB_RF69_REG_OOK_AVG +#define RADIOLIB_RF69_OOK_AVG_THRESH_FILT_32_PI 0b00000000 // 7 6 OOK average filter coefficient: chip rate / 32*pi +#define RADIOLIB_RF69_OOK_AVG_THRESH_FILT_8_PI 0b01000000 // 7 6 chip rate / 8*pi +#define RADIOLIB_RF69_OOK_AVG_THRESH_FILT_4_PI 0b10000000 // 7 6 chip rate / 4*pi (default) +#define RADIOLIB_RF69_OOK_AVG_THRESH_FILT_2_PI 0b11000000 // 7 6 chip rate / 2*pi + +// RADIOLIB_RF69_REG_OOK_FIX +#define RADIOLIB_RF69_OOK_FIXED_THRESH 0b00000110 // 7 0 default OOK fixed threshold (6 dB) + +// RADIOLIB_RF69_REG_AFC_FEI +#define RADIOLIB_RF69_FEI_RUNNING 0b00000000 // 6 6 FEI status: on-going +#define RADIOLIB_RF69_FEI_DONE 0b01000000 // 6 6 done +#define RADIOLIB_RF69_FEI_START 0b00100000 // 5 5 force new FEI measurement +#define RADIOLIB_RF69_AFC_RUNNING 0b00000000 // 4 4 AFC status: on-going +#define RADIOLIB_RF69_AFC_DONE 0b00010000 // 4 4 done +#define RADIOLIB_RF69_AFC_AUTOCLEAR_OFF 0b00000000 // 3 3 AFC register autoclear disabled +#define RADIOLIB_RF69_AFC_AUTOCLEAR_ON 0b00001000 // 3 3 AFC register autoclear enabled +#define RADIOLIB_RF69_AFC_AUTO_OFF 0b00000000 // 2 2 perform AFC only manually +#define RADIOLIB_RF69_AFC_AUTO_ON 0b00000100 // 2 2 perform AFC each time Rx mode is started +#define RADIOLIB_RF69_AFC_CLEAR 0b00000010 // 1 1 clear AFC register +#define RADIOLIB_RF69_AFC_START 0b00000001 // 0 0 start AFC + +// RADIOLIB_RF69_REG_RSSI_CONFIG +#define RADIOLIB_RF69_RSSI_RUNNING 0b00000000 // 1 1 RSSI status: on-going +#define RADIOLIB_RF69_RSSI_DONE 0b00000010 // 1 1 done +#define RADIOLIB_RF69_RSSI_START 0b00000001 // 0 0 start RSSI measurement + +// RADIOLIB_RF69_REG_DIO_MAPPING_1 +#define RADIOLIB_RF69_DIO0_CONT_MODE_READY 0b11000000 // 7 6 +#define RADIOLIB_RF69_DIO0_CONT_PLL_LOCK 0b00000000 // 7 6 +#define RADIOLIB_RF69_DIO0_CONT_SYNC_ADDRESS 0b00000000 // 7 6 +#define RADIOLIB_RF69_DIO0_CONT_TIMEOUT 0b01000000 // 7 6 +#define RADIOLIB_RF69_DIO0_CONT_RSSI 0b10000000 // 7 6 +#define RADIOLIB_RF69_DIO0_CONT_TX_READY 0b01000000 // 7 6 +#define RADIOLIB_RF69_DIO0_PACK_PLL_LOCK 0b11000000 // 7 6 +#define RADIOLIB_RF69_DIO0_PACK_CRC_OK 0b00000000 // 7 6 +#define RADIOLIB_RF69_DIO0_PACK_PAYLOAD_READY 0b01000000 // 7 6 +#define RADIOLIB_RF69_DIO0_PACK_SYNC_ADDRESS 0b10000000 // 7 6 +#define RADIOLIB_RF69_DIO0_PACK_RSSI 0b11000000 // 7 6 +#define RADIOLIB_RF69_DIO0_PACK_PACKET_SENT 0b00000000 // 7 6 +#define RADIOLIB_RF69_DIO0_PACK_TX_READY 0b01000000 // 7 6 +#define RADIOLIB_RF69_DIO1_CONT_PLL_LOCK 0b00110000 // 5 4 +#define RADIOLIB_RF69_DIO1_CONT_DCLK 0b00000000 // 5 4 +#define RADIOLIB_RF69_DIO1_CONT_RX_READY 0b00010000 // 5 4 +#define RADIOLIB_RF69_DIO1_CONT_SYNC_ADDRESS 0b00110000 // 5 4 +#define RADIOLIB_RF69_DIO1_CONT_TX_READY 0b00010000 // 5 4 +#define RADIOLIB_RF69_DIO1_PACK_FIFO_LEVEL 0b00000000 // 5 4 +#define RADIOLIB_RF69_DIO1_PACK_FIFO_FULL 0b00010000 // 5 4 +#define RADIOLIB_RF69_DIO1_PACK_FIFO_NOT_EMPTY 0b00100000 // 5 4 +#define RADIOLIB_RF69_DIO1_PACK_PLL_LOCK 0b00110000 // 5 4 +#define RADIOLIB_RF69_DIO1_PACK_TIMEOUT 0b00110000 // 5 4 +#define RADIOLIB_RF69_DIO2_CONT_DATA 0b00000000 // 3 2 +#define RADIOLIB_RF69_DIO2_PACK_FIFO_NOT_EMPTY 0b00000000 // 3 2 +#define RADIOLIB_RF69_DIO2_PACK_AUTO_MODE 0b00001100 // 3 2 +#define RADIOLIB_RF69_DIO2_PACK_DATA 0b00000100 // 3 2 +#define RADIOLIB_RF69_DIO3_CONT_AUTO_MODE 0b00000010 // 0 1 +#define RADIOLIB_RF69_DIO3_CONT_RSSI 0b00000000 // 0 1 +#define RADIOLIB_RF69_DIO3_CONT_RX_READY 0b00000001 // 0 1 +#define RADIOLIB_RF69_DIO3_CONT_TIMEOUT 0b00000011 // 0 1 +#define RADIOLIB_RF69_DIO3_CONT_TX_READY 0b00000001 // 0 1 +#define RADIOLIB_RF69_DIO3_PACK_FIFO_FULL 0b00000000 // 0 1 +#define RADIOLIB_RF69_DIO3_PACK_PLL_LOCK 0b00000011 // 0 1 +#define RADIOLIB_RF69_DIO3_PACK_RSSI 0b00000001 // 0 1 +#define RADIOLIB_RF69_DIO3_PACK_SYNC_ADDRESSS 0b00000010 // 0 1 +#define RADIOLIB_RF69_DIO3_PACK_TX_READY 0b00000001 // 0 1 + +// RADIOLIB_RF69_REG_DIO_MAPPING_2 +#define RADIOLIB_RF69_DIO4_CONT_PLL_LOCK 0b11000000 // 7 6 +#define RADIOLIB_RF69_DIO4_CONT_TIMEOUT 0b00000000 // 7 6 +#define RADIOLIB_RF69_DIO4_CONT_RX_READY 0b01000000 // 7 6 +#define RADIOLIB_RF69_DIO4_CONT_SYNC_ADDRESS 0b10000000 // 7 6 +#define RADIOLIB_RF69_DIO4_CONT_TX_READY 0b01000000 // 7 6 +#define RADIOLIB_RF69_DIO4_PACK_PLL_LOCK 0b11000000 // 7 6 +#define RADIOLIB_RF69_DIO4_PACK_TIMEOUT 0b00000000 // 7 6 +#define RADIOLIB_RF69_DIO4_PACK_RSSI 0b01000000 // 7 6 +#define RADIOLIB_RF69_DIO4_PACK_RX_READY 0b10000000 // 7 6 +#define RADIOLIB_RF69_DIO4_PACK_MODE_READY 0b00000000 // 7 6 +#define RADIOLIB_RF69_DIO4_PACK_TX_READY 0b01000000 // 7 6 +#define RADIOLIB_RF69_DIO5_CONT_MODE_READY 0b00110000 // 5 4 +#define RADIOLIB_RF69_DIO5_CONT_CLK_OUT 0b00000000 // 5 4 +#define RADIOLIB_RF69_DIO5_CONT_RSSI 0b00010000 // 5 4 +#define RADIOLIB_RF69_DIO5_PACK_MODE_READY 0b00110000 // 5 4 +#define RADIOLIB_RF69_DIO5_PACK_CLK_OUT 0b00000000 // 5 4 +#define RADIOLIB_RF69_DIO5_PACK_DATA 0b00010000 // 5 4 +#define RADIOLIB_RF69_CLK_OUT_FXOSC 0b00000000 // 2 0 ClkOut frequency: F(XOSC) +#define RADIOLIB_RF69_CLK_OUT_FXOSC_2 0b00000001 // 2 0 F(XOSC) / 2 +#define RADIOLIB_RF69_CLK_OUT_FXOSC_4 0b00000010 // 2 0 F(XOSC) / 4 +#define RADIOLIB_RF69_CLK_OUT_FXOSC_8 0b00000011 // 2 0 F(XOSC) / 8 +#define RADIOLIB_RF69_CLK_OUT_FXOSC_16 0b00000100 // 2 0 F(XOSC) / 16 +#define RADIOLIB_RF69_CLK_OUT_FXOSC_32 0b00000101 // 2 0 F(XOSC) / 31 +#define RADIOLIB_RF69_CLK_OUT_RC 0b00000110 // 2 0 RC +#define RADIOLIB_RF69_CLK_OUT_OFF 0b00000111 // 2 0 disabled (default) + +// RADIOLIB_RF69_REG_IRQ_FLAGS_1 +#define RADIOLIB_RF69_IRQ_MODE_READY 0b10000000 // 7 7 requested mode was set +#define RADIOLIB_RF69_IRQ_RX_READY 0b01000000 // 6 6 Rx mode ready +#define RADIOLIB_RF69_IRQ_TX_READY 0b00100000 // 5 5 Tx mode ready +#define RADIOLIB_RF69_IRQ_PLL_LOCK 0b00010000 // 4 4 PLL is locked +#define RADIOLIB_RF69_IRQ_RSSI 0b00001000 // 3 3 RSSI value exceeded RssiThreshold +#define RADIOLIB_RF69_IRQ_TIMEOUT 0b00000100 // 2 2 timeout occurred +#define RADIOLIB_RF69_IRQ_AUTO_MODE 0b00000010 // 1 1 entered intermediate mode +#define RADIOLIB_RF69_SYNC_ADDRESS_MATCH 0b00000001 // 0 0 sync address detected + +// RADIOLIB_RF69_REG_IRQ_FLAGS_2 +#define RADIOLIB_RF69_IRQ_FIFO_FULL 0b10000000 // 7 7 FIFO is full +#define RADIOLIB_RF69_IRQ_FIFO_NOT_EMPTY 0b01000000 // 6 6 FIFO contains at least 1 byte +#define RADIOLIB_RF69_IRQ_FIFO_LEVEL 0b00100000 // 5 5 FIFO contains more than FifoThreshold bytes +#define RADIOLIB_RF69_IRQ_FIFO_OVERRUN 0b00010000 // 4 4 FIFO overrun occurred +#define RADIOLIB_RF69_IRQ_PACKET_SENT 0b00001000 // 3 3 packet was sent +#define RADIOLIB_RF69_IRQ_PAYLOAD_READY 0b00000100 // 2 2 last payload byte received and CRC check passed +#define RADIOLIB_RF69_IRQ_CRC_OK 0b00000010 // 1 1 CRC check passed + +// RADIOLIB_RF69_REG_RSSI_THRESH +#define RADIOLIB_RF69_RSSI_THRESHOLD 0xE4 // 7 0 RSSI threshold level (2 dB by default) + +// RADIOLIB_RF69_REG_RX_TIMEOUT_1 +#define RADIOLIB_RF69_TIMEOUT_RX_START_OFF 0x00 // 7 0 RSSI interrupt timeout disabled (default) +#define RADIOLIB_RF69_TIMEOUT_RX_START 0xFF // 7 0 timeout will occur if RSSI interrupt is not received + +// RADIOLIB_RF69_REG_RX_TIMEOUT_2 +#define RADIOLIB_RF69_TIMEOUT_RSSI_THRESH_OFF 0x00 // 7 0 PayloadReady interrupt timeout disabled (default) +#define RADIOLIB_RF69_TIMEOUT_RSSI_THRESH 0xFF // 7 0 timeout will occur if PayloadReady interrupt is not received + +// RADIOLIB_RF69_REG_PREAMBLE_MSB + REG_PREAMBLE_MSB +#define RADIOLIB_RF69_PREAMBLE_MSB 0x00 // 7 0 2-byte preamble size value +#define RADIOLIB_RF69_PREAMBLE_LSB 0x03 // 7 0 + +// RADIOLIB_RF69_REG_SYNC_CONFIG +#define RADIOLIB_RF69_SYNC_OFF 0b00000000 // 7 7 sync word detection off +#define RADIOLIB_RF69_SYNC_ON 0b10000000 // 7 7 sync word detection on (default) +#define RADIOLIB_RF69_FIFO_FILL_CONDITION_SYNC 0b00000000 // 6 6 FIFO fill condition: on SyncAddress interrupt (default) +#define RADIOLIB_RF69_FIFO_FILL_CONDITION 0b01000000 // 6 6 as long as the bit is set +#define RADIOLIB_RF69_SYNC_SIZE 0b00001000 // 5 3 size of sync word: SyncSize + 1 bytes +#define RADIOLIB_RF69_SYNC_TOL 0b00000000 // 2 0 number of tolerated errors in sync word + +// RADIOLIB_RF69_REG_SYNC_VALUE_1 - SYNC_VALUE_8 +#define RADIOLIB_RF69_SYNC_BYTE_1 0x01 // 7 0 sync word: 1st byte (MSB) +#define RADIOLIB_RF69_SYNC_BYTE_2 0x01 // 7 0 2nd byte +#define RADIOLIB_RF69_SYNC_BYTE_3 0x01 // 7 0 3rd byte +#define RADIOLIB_RF69_SYNC_BYTE_4 0x01 // 7 0 4th byte +#define RADIOLIB_RF69_SYNC_BYTE_5 0x01 // 7 0 5th byte +#define RADIOLIB_RF69_SYNC_BYTE_6 0x01 // 7 0 6th byte +#define RADIOLIB_RF69_SYNC_BYTE_7 0x01 // 7 0 7th byte +#define RADIOLIB_RF69_SYNC_BYTE_8 0x01 // 7 0 8th byte (LSB) + +// RADIOLIB_RF69_REG_PACKET_CONFIG_1 +#define RADIOLIB_RF69_PACKET_FORMAT_FIXED 0b00000000 // 7 7 fixed packet length (default) +#define RADIOLIB_RF69_PACKET_FORMAT_VARIABLE 0b10000000 // 7 7 variable packet length +#define RADIOLIB_RF69_DC_FREE_NONE 0b00000000 // 6 5 DC-free encoding: none (default) +#define RADIOLIB_RF69_DC_FREE_MANCHESTER 0b00100000 // 6 5 Manchester +#define RADIOLIB_RF69_DC_FREE_WHITENING 0b01000000 // 6 5 Whitening +#define RADIOLIB_RF69_CRC_OFF 0b00000000 // 4 4 CRC disabled +#define RADIOLIB_RF69_CRC_ON 0b00010000 // 4 4 CRC enabled (default) +#define RADIOLIB_RF69_CRC_AUTOCLEAR_ON 0b00000000 // 3 3 discard packet when CRC check fails (default) +#define RADIOLIB_RF69_CRC_AUTOCLEAR_OFF 0b00001000 // 3 3 keep packet when CRC check fails +#define RADIOLIB_RF69_ADDRESS_FILTERING_OFF 0b00000000 // 2 1 address filtering: none (default) +#define RADIOLIB_RF69_ADDRESS_FILTERING_NODE 0b00000010 // 2 1 node +#define RADIOLIB_RF69_ADDRESS_FILTERING_NODE_BROADCAST 0b00000100 // 2 1 node or broadcast + +// RADIOLIB_RF69_REG_PAYLOAD_LENGTH +#define RADIOLIB_RF69_PAYLOAD_LENGTH 0xFF // 7 0 payload length + +// RADIOLIB_RF69_REG_AUTO_MODES +#define RADIOLIB_RF69_ENTER_COND_NONE 0b00000000 // 7 5 condition for entering intermediate mode: none, AutoModes disabled (default) +#define RADIOLIB_RF69_ENTER_COND_FIFO_NOT_EMPTY 0b00100000 // 7 5 FifoNotEmpty rising edge +#define RADIOLIB_RF69_ENTER_COND_FIFO_LEVEL 0b01000000 // 7 5 FifoLevel rising edge +#define RADIOLIB_RF69_ENTER_COND_CRC_OK 0b01100000 // 7 5 CrcOk rising edge +#define RADIOLIB_RF69_ENTER_COND_PAYLOAD_READY 0b10000000 // 7 5 PayloadReady rising edge +#define RADIOLIB_RF69_ENTER_COND_SYNC_ADDRESS 0b10100000 // 7 5 SyncAddress rising edge +#define RADIOLIB_RF69_ENTER_COND_PACKET_SENT 0b11000000 // 7 5 PacketSent rising edge +#define RADIOLIB_RF69_ENTER_COND_FIFO_EMPTY 0b11100000 // 7 5 FifoNotEmpty falling edge +#define RADIOLIB_RF69_EXIT_COND_NONE 0b00000000 // 4 2 condition for exiting intermediate mode: none, AutoModes disabled (default) +#define RADIOLIB_RF69_EXIT_COND_FIFO_EMPTY 0b00100000 // 4 2 FifoNotEmpty falling edge +#define RADIOLIB_RF69_EXIT_COND_FIFO_LEVEL 0b01000000 // 4 2 FifoLevel rising edge +#define RADIOLIB_RF69_EXIT_COND_CRC_OK 0b01100000 // 4 2 CrcOk rising edge +#define RADIOLIB_RF69_EXIT_COND_PAYLOAD_READY 0b10000000 // 4 2 PayloadReady rising edge +#define RADIOLIB_RF69_EXIT_COND_SYNC_ADDRESS 0b10100000 // 4 2 SyncAddress rising edge +#define RADIOLIB_RF69_EXIT_COND_PACKET_SENT 0b11000000 // 4 2 PacketSent rising edge +#define RADIOLIB_RF69_EXIT_COND_TIMEOUT 0b11100000 // 4 2 timeout rising edge +#define RADIOLIB_RF69_INTER_MODE_SLEEP 0b00000000 // 1 0 intermediate mode: sleep (default) +#define RADIOLIB_RF69_INTER_MODE_STANDBY 0b00000001 // 1 0 standby +#define RADIOLIB_RF69_INTER_MODE_RX 0b00000010 // 1 0 Rx +#define RADIOLIB_RF69_INTER_MODE_TX 0b00000011 // 1 0 Tx + +// RADIOLIB_RF69_REG_FIFO_THRESH +#define RADIOLIB_RF69_TX_START_CONDITION_FIFO_LEVEL 0b00000000 // 7 7 packet transmission start condition: FifoLevel +#define RADIOLIB_RF69_TX_START_CONDITION_FIFO_NOT_EMPTY 0b10000000 // 7 7 FifoNotEmpty (default) +#define RADIOLIB_RF69_FIFO_THRESH 0x1F // 6 0 default threshold to trigger FifoLevel interrupt + +// RADIOLIB_RF69_REG_PACKET_CONFIG_2 +#define RADIOLIB_RF69_INTER_PACKET_RX_DELAY 0b00000000 // 7 4 delay between FIFO empty and start of new RSSI phase +#define RADIOLIB_RF69_RESTART_RX 0b00000100 // 2 2 force receiver into wait mode +#define RADIOLIB_RF69_AUTO_RX_RESTART_OFF 0b00000000 // 1 1 auto Rx restart disabled +#define RADIOLIB_RF69_AUTO_RX_RESTART_ON 0b00000010 // 1 1 auto Rx restart enabled (default) +#define RADIOLIB_RF69_AES_OFF 0b00000000 // 0 0 AES encryption disabled (default) +#define RADIOLIB_RF69_AES_ON 0b00000001 // 0 0 AES encryption enabled, payload size limited to 66 bytes + +// RADIOLIB_RF69_REG_TEST_LNA +#define RADIOLIB_RF69_TEST_LNA_BOOST_NORMAL 0x1B // 7 0 +#define RADIOLIB_RF69_TEST_LNA_BOOST_HIGH 0x2D // 7 0 + +// RADIOLIB_RF69_REG_TEMP_1 +#define RADIOLIB_RF69_TEMP_MEAS_START 0b00001000 // 3 3 trigger temperature measurement +#define RADIOLIB_RF69_TEMP_MEAS_RUNNING 0b00000100 // 2 2 temperature measurement status: on-going +#define RADIOLIB_RF69_TEMP_MEAS_DONE 0b00000000 // 2 2 done + +// RADIOLIB_RF69_REG_TEST_DAGC +#define RADIOLIB_RF69_CONTINUOUS_DAGC_NORMAL 0x00 // 7 0 fading margin improvement: normal mode +#define RADIOLIB_RF69_CONTINUOUS_DAGC_LOW_BETA_ON 0x20 // 7 0 improved mode for AfcLowBetaOn +#define RADIOLIB_RF69_CONTINUOUS_DAGC_LOW_BETA_OFF 0x30 // 7 0 improved mode for AfcLowBetaOff (default) + +// RADIOLIB_RF69_REG_TEST_PA1 +#define RADIOLIB_RF69_PA1_NORMAL 0x55 // 7 0 PA_BOOST: none +#define RADIOLIB_RF69_PA1_20_DBM 0x5D // 7 0 +20 dBm + +// RADIOLIB_RF69_REG_TEST_PA2 +#define RADIOLIB_RF69_PA2_NORMAL 0x70 // 7 0 PA_BOOST: none +#define RADIOLIB_RF69_PA2_20_DBM 0x7C // 7 0 +20 dBm + +// RadioLib defaults +#define RADIOLIB_RF69_DEFAULT_FREQ 434.0 +#define RADIOLIB_RF69_DEFAULT_BR 4.8 +#define RADIOLIB_RF69_DEFAULT_FREQDEV 5.0 +#define RADIOLIB_RF69_DEFAULT_RXBW 125.0 +#define RADIOLIB_RF69_DEFAULT_POWER 10 +#define RADIOLIB_RF69_DEFAULT_PREAMBLELEN 16 +#define RADIOLIB_RF69_DEFAULT_SW {0x12, 0xAD} +#define RADIOLIB_RF69_DEFAULT_SW_LEN 2 + +/*! + \class RF69 + \brief Control class for %RF69 module. Also serves as base class for SX1231. +*/ +class RF69: public PhysicalLayer { + public: + // introduce PhysicalLayer overloads + using PhysicalLayer::transmit; + using PhysicalLayer::receive; + using PhysicalLayer::startTransmit; + using PhysicalLayer::readData; + + /*! + \brief Default constructor. + \param module Instance of Module that will be used to communicate with the radio. + */ + RF69(Module* module); // cppcheck-suppress noExplicitConstructor + + // basic methods + + /*! + \brief Initialization method. + \param freq Carrier frequency in MHz. Defaults to 434.0 MHz. + \param br Bit rate to be used in kbps. Defaults to 4.8 kbps. + \param freqDev Frequency deviation from carrier frequency in kHz Defaults to 5.0 kHz. + \param rxBw Receiver bandwidth in kHz. Defaults to 125.0 kHz. + \param pwr Output power in dBm. Defaults to 10 dBm. + \param preambleLen Preamble Length in bits. Defaults to 16 bits. + \returns \ref status_codes + */ + int16_t begin( + float freq = RADIOLIB_RF69_DEFAULT_FREQ, + float br = RADIOLIB_RF69_DEFAULT_BR, + float freqDev = RADIOLIB_RF69_DEFAULT_FREQDEV, + float rxBw = RADIOLIB_RF69_DEFAULT_RXBW, + int8_t pwr = RADIOLIB_RF69_DEFAULT_POWER, + uint8_t preambleLen = RADIOLIB_RF69_DEFAULT_PREAMBLELEN); + + /*! + \brief Reset method. Will reset the chip to the default state using RST pin. + */ + void reset(); + + /*! + \brief Blocking binary transmit method. + Overloads for string-based transmissions are implemented in PhysicalLayer. + \param data Binary data to be sent. + \param len Number of bytes to send. + \param addr Address to send the data to. Will only be added if address filtering was enabled. + \returns \ref status_codes + */ + int16_t transmit(const uint8_t* data, size_t len, uint8_t addr = 0) override; + + /*! + \brief Blocking binary receive method. + Overloads for string-based transmissions are implemented in PhysicalLayer. + \param data Binary data to be sent. + \param len Number of bytes to send. + \returns \ref status_codes + */ + int16_t receive(uint8_t* data, size_t len) override; + + /*! + \brief Sets the module to sleep mode. + \returns \ref status_codes + */ + int16_t sleep() override; + + /*! + \brief Sets the module to standby mode. + \returns \ref status_codes + */ + int16_t standby() override; + + /*! + \brief Sets the module to standby. + \param mode Standby mode to be used. No effect, implemented only for PhysicalLayer compatibility. + \returns \ref status_codes + */ + int16_t standby(uint8_t mode) override; + + /*! + \brief Starts direct mode transmission. + \param frf Raw RF frequency value. Defaults to 0, required for quick frequency shifts in RTTY. + \returns \ref status_codes + */ + int16_t transmitDirect(uint32_t frf = 0) override; + + /*! + \brief Starts direct mode reception. + \returns \ref status_codes + */ + int16_t receiveDirect() override; + + /*! + \brief Stops direct mode. It is required to call this method to switch from direct transmissions to packet-based transmissions. + */ + int16_t packetMode(); + + // hardware AES support + + /*! + \brief Sets AES key. + \param key Key to be used for AES encryption. Must be exactly 16 bytes long. + */ + void setAESKey(uint8_t* key); + + /*! + \brief Enables AES encryption. + \returns \ref status_codes + */ + int16_t enableAES(); + + /*! + \brief Disables AES encryption. + \returns \ref status_codes + */ + int16_t disableAES(); + + // interrupt methods + + /*! + \brief Sets interrupt service routine to call when DIO0 activates. + \param func ISR to call. + */ + void setDio0Action(void (*func)(void)); + + /*! + \brief Clears interrupt service routine to call when DIO0 activates. + */ + void clearDio0Action(); + + /*! + \brief Sets interrupt service routine to call when DIO1 activates. + \param func ISR to call. + */ + void setDio1Action(void (*func)(void)); + + /*! + \brief Clears interrupt service routine to call when DIO1 activates. + */ + void clearDio1Action(); + + /*! + \brief Sets interrupt service routine to call when a packet is received. + \param func ISR to call. + */ + void setPacketReceivedAction(void (*func)(void)) override; + + /*! + \brief Clears interrupt service routine to call when a packet is received. + */ + void clearPacketReceivedAction() override; + + /*! + \brief Sets interrupt service routine to call when a packet is sent. + \param func ISR to call. + */ + void setPacketSentAction(void (*func)(void)) override; + + /*! + \brief Clears interrupt service routine to call when a packet is sent. + */ + void clearPacketSentAction() override; + + /*! + \brief Set interrupt service routine function to call when FIFO is empty. + \param func Pointer to interrupt service routine. + */ + void setFifoEmptyAction(void (*func)(void)); + + /*! + \brief Clears interrupt service routine to call when FIFO is empty. + */ + void clearFifoEmptyAction(); + + /*! + \brief Set interrupt service routine function to call when FIFO is full. + \param func Pointer to interrupt service routine. + */ + void setFifoFullAction(void (*func)(void)); + + /*! + \brief Clears interrupt service routine to call when FIFO is full. + */ + void clearFifoFullAction(); + + /*! + \brief Set interrupt service routine function to call when FIFO is empty. + \param data Pointer to the transmission buffer. + \param totalLen Total number of bytes to transmit. + \param remLen Pointer to a counter holding the number of bytes that have been transmitted so far. + \returns True when a complete packet is sent, false if more data is needed. + */ + bool fifoAdd(uint8_t* data, int totalLen, int* remLen); + + /*! + \brief Set interrupt service routine function to call when FIFO is sufficiently full to read. + \param data Pointer to a buffer that stores the receive data. + \param totalLen Total number of bytes to receive. + \param rcvLen Pointer to a counter holding the number of bytes that have been received so far. + \returns True when a complete packet is received, false if more data is needed. + */ + bool fifoGet(volatile uint8_t* data, int totalLen, volatile int* rcvLen); + + /*! + \brief Interrupt-driven binary transmit method. + Overloads for string-based transmissions are implemented in PhysicalLayer. + \param data Binary data to be sent. + \param len Number of bytes to send. + \param addr Address to send the data to. Will only be added if address filtering was enabled. + \returns \ref status_codes + */ + int16_t startTransmit(const uint8_t* data, size_t len, uint8_t addr = 0) override; + + /*! + \brief Clean up after transmission is done. + \returns \ref status_codes + */ + int16_t finishTransmit() override; + + /*! + \brief Interrupt-driven receive method. GDO0 will be activated when full packet is received. + \returns \ref status_codes + */ + int16_t startReceive() override; + + /*! + \brief Interrupt-driven receive method, implemented for compatibility with PhysicalLayer. + \param timeout Ignored. + \param irqFlags Ignored. + \param irqMask Ignored. + \param len Ignored. + \returns \ref status_codes + */ + int16_t startReceive(uint32_t timeout, uint32_t irqFlags, uint32_t irqMask, size_t len) override; + + /*! + \brief Reads data received after calling startReceive method. When the packet length is not known in advance, + getPacketLength method must be called BEFORE calling readData! + \param data Pointer to array to save the received binary data. + \param len Number of bytes that will be read. When set to 0, the packet length will be retrieved automatically. + When more bytes than received are requested, only the number of bytes requested will be returned. + \returns \ref status_codes + */ + int16_t readData(uint8_t* data, size_t len) override; + + // configuration methods + + /*! + \brief Sets carrier frequency. Allowed values are in bands 290.0 to 340.0 MHz, 431.0 to 510.0 MHz + and 862.0 to 1020.0 MHz. + \param freq Carrier frequency to be set in MHz. + \returns \ref status_codes + */ + int16_t setFrequency(float freq) override; + + /*! + \brief Gets carrier frequency. + \param[out] freq Variable to write carrier frequency currently set, in MHz. + \returns \ref status_codes + */ + int16_t getFrequency(float *freq); + + /*! + \brief Sets bit rate. Allowed values range from 0.5 to 300.0 kbps. + \param br Bit rate to be set in kbps. + \returns \ref status_codes + */ + int16_t setBitRate(float br) override; + + /*! + \brief Sets receiver bandwidth. Allowed values are 2.6, 3.1, 3.9, 5.2, 6.3, 7.8, 10.4, 12.5, 15.6, + 20.8, 25.0, 31.3, 41.7, 50.0, 62.5, 83.3, 100.0, 125.0, 166.7, 200.0, 250.0, 333.3, 400.0 and 500.0 kHz. + \param rxBw Receiver bandwidth to be set in kHz. + \returns \ref status_codes + */ + int16_t setRxBandwidth(float rxBw); + + /*! + \brief Sets frequency deviation. + \param freqDev Frequency deviation to be set in kHz. + \returns \ref status_codes + */ + int16_t setFrequencyDeviation(float freqDev) override; + + /*! + \brief Gets frequency deviation. + \param[out] freqDev Where to write the frequency deviation currently set, in kHz. + \returns \ref status_codes + */ + int16_t getFrequencyDeviation(float *freqDev); + + /*! + \brief Sets output power. Allowed values range from -18 to 13 dBm for + low power modules (RF69C/CW) or -2 to 20 dBm (RF69H/HC/HCW). + \param pwr Output power to be set in dBm. + \param highPower Set to true when using modules high power port (RF69H/HC/HCW). + Defaults to false (models without high power port - RF69C/CW). + \returns \ref status_codes + */ + int16_t setOutputPower(int8_t pwr, bool highPower = false); + + /*! + \brief Sets sync word. Up to 8 bytes can be set as sync word. + \param syncWord Pointer to the array of sync word bytes. + \param len Sync word length in bytes. + \param maxErrBits Maximum allowed number of bit errors in received sync word. Defaults to 0. + */ + int16_t setSyncWord(uint8_t* syncWord, size_t len, uint8_t maxErrBits = 0); + + /*! + \brief Sets preamble length. + \param preambleLen Preamble length to be set (in bits), allowed values: 16, 24, 32, 48, 64, 96, 128 and 192. + \returns \ref status_codes + */ + int16_t setPreambleLength(uint8_t preambleLen); + + /*! + \brief Sets node address. Calling this method will also enable address filtering for node address only. + \param nodeAddr Node address to be set. + \returns \ref status_codes + */ + int16_t setNodeAddress(uint8_t nodeAddr); + + /*! + \brief Sets broadcast address. Calling this method will also enable address filtering for node and broadcast address. + \param broadAddr Node address to be set. + \returns \ref status_codes + */ + int16_t setBroadcastAddress(uint8_t broadAddr); + + /*! + \brief Disables address filtering. Calling this method will also erase previously set addresses. + \returns \ref status_codes + */ + int16_t disableAddressFiltering(); + + // measurement methods + + /*! + \brief Sets ambient temperature. Required to correct values from on-board temperature sensor. + \param tempAmbient Ambient temperature in degrees Celsius. + */ + void setAmbientTemperature(int16_t tempAmbient); + + /*! + \brief Measures temperature. + \returns Measured temperature in degrees Celsius. + */ + int16_t getTemperature(); + + /*! + \brief Query modem for the packet length of received payload. + \param update Update received packet length. Will return cached value when set to false. + \returns Length of last received packet in bytes. + */ + size_t getPacketLength(bool update = true) override; + + /*! + \brief Enables/disables OOK modulation instead of FSK. + Note: This function calls setRxBandwidth again, since register values differ based on OOK mode being enabled/disabled. + \param enable Enable (true) or disable (false) OOK. + \returns \ref status_codes + */ + int16_t setOOK(bool enable); + + /*! + \brief Selects the type of threshold in the OOK data slicer + \param type Threshold type: RADIOLIB_RF69_OOK_THRESH_PEAK(default), RADIOLIB_RF69_OOK_THRESH_FIXED or + RADIOLIB_RF69_OOK_THRESH_AVERAGE + \returns \ref status_codes + */ + int16_t setOokThresholdType(uint8_t type); + + /*! + \brief Fixed threshold for the Data Slicer in OOK mode or floor threshold for the Data Slicer + in OOK when Peak mode is used. + \param value Fixed threshold value (in dB) in the OOK demodulator. + Used when OokThresType = RADIOLIB_RF69_OOK_THRESH_FIXED. + \returns \ref status_codes + */ + int16_t setOokFixedThreshold(uint8_t value); + + /*! + \brief Period of decrement of the RSSI threshold in the OOK demodulator. + \param value Use defines RADIOLIB_RF69_OOK_PEAK_THRESH_DEC_X_X_CHIP + \returns \ref status_codes + */ + int16_t setOokPeakThresholdDecrement(uint8_t value); + + /*! + \brief Set modem in fixed packet length mode. + \param len Packet length. + \returns \ref status_codes + */ + int16_t fixedPacketLengthMode(uint8_t len = RADIOLIB_RF69_MAX_PACKET_LENGTH); + + /*! + \brief Set modem in variable packet length mode. + \param maxLen Maximum packet length. + \returns \ref status_codes + */ + int16_t variablePacketLengthMode(uint8_t maxLen = RADIOLIB_RF69_MAX_PACKET_LENGTH); + + /*! + \brief Enable sync word filtering and generation. + \param maxErrBits Maximum allowed number of error bits in sync word. + \returns \ref status_codes + */ + int16_t enableSyncWordFiltering(uint8_t maxErrBits = 0); + + /*! + \brief Disable preamble and sync word filtering and generation. + \returns \ref status_codes + */ + int16_t disableSyncWordFiltering(); + + /*! + \brief Enable Bit synchronization in continuous mode. + \returns \ref status_codes + */ + int16_t enableContinuousModeBitSync(); + + /*! + \brief Disable Bit synchronization in continuous mode. + \returns \ref status_codes + */ + int16_t disableContinuousModeBitSync(); + + /*! + \brief Enable CRC filtering and generation. + \param crcOn Set or unset CRC filtering. + \returns \ref status_codes + */ + int16_t setCrcFiltering(bool crcOn = true); + + /*! + \brief Set modem in "sniff" mode: no packet filtering (e.g., no preamble, sync word, address, CRC). + \param enable Set or unset promiscuous mode. + \returns \ref status_codes + */ + int16_t setPromiscuousMode(bool enable = true); + + /*! + \brief Sets Gaussian filter bandwidth-time product that will be used for data shaping. + Allowed values are RADIOLIB_SHAPING_0_3, RADIOLIB_SHAPING_0_5 or RADIOLIB_SHAPING_1_0. + Set to RADIOLIB_SHAPING_NONE to disable data shaping. + \param sh Gaussian shaping bandwidth-time product that will be used for data shaping + \returns \ref status_codes + */ + int16_t setDataShaping(uint8_t sh) override; + + /*! + \brief Sets transmission encoding. + Allowed values are RADIOLIB_ENCODING_NRZ, RADIOLIB_ENCODING_MANCHESTER and RADIOLIB_ENCODING_WHITENING. + \param encoding Encoding to be used. + \returns \ref status_codes + */ + int16_t setEncoding(uint8_t encoding) override; + + /*! + \brief Enable/disable LNA Boost mode (disabled by default). + \param value True to enable, false to disable. + \returns \ref status_codes + */ + int16_t setLnaTestBoost(bool value); + + /*! + \brief Gets RSSI (Recorded Signal Strength Indicator) of the last received packet. + \returns Last packet RSSI in dBm. + */ + float getRSSI() override; + + /*! + \brief Sets the RSSI value above which the RSSI interrupt is signaled + \param dbm A dBm value between -127.5 and 0 inclusive + \returns \ref status_codes + */ + int16_t setRSSIThreshold(float dbm); + + /*! \copydoc Module::setRfSwitchPins */ + void setRfSwitchPins(uint32_t rxEn, uint32_t txEn); + + /*! \copydoc Module::setRfSwitchTable */ + void setRfSwitchTable(const uint32_t (&pins)[Module::RFSWITCH_MAX_PINS], const Module::RfSwitchMode_t table[]); + + /*! + \brief Get one truly random byte from RSSI noise. + \returns TRNG byte. + */ + uint8_t randomByte() override; + + /*! + \brief Read version SPI register. Should return RF69_CHIP_VERSION (0x24) if SX127x is connected and working. + \returns Version register contents or \ref status_codes + */ + int16_t getChipVersion(); + + #if !RADIOLIB_EXCLUDE_DIRECT_RECEIVE + /*! + \brief Set interrupt service routine function to call when data bit is received in direct mode. + \param func Pointer to interrupt service routine. + */ + void setDirectAction(void (*func)(void)) override; + + /*! + \brief Function to read and process data bit in direct reception mode. + \param pin Pin on which to read. + */ + void readBit(uint32_t pin) override; + #endif + + /*! + \brief Configure DIO pin mapping to get a given signal on a DIO pin (if available). + \param pin Pin number onto which a signal is to be placed. + \param value The value that indicates which function to place on that pin. See chip datasheet for details. + \returns \ref status_codes + */ + int16_t setDIOMapping(uint32_t pin, uint32_t value) override; + +#if !RADIOLIB_GODMODE && !RADIOLIB_LOW_LEVEL + protected: +#endif + Module* getMod() override; + +#if !RADIOLIB_GODMODE + protected: +#endif + float bitRate = RADIOLIB_RF69_DEFAULT_BR; + float rxBandwidth = RADIOLIB_RF69_DEFAULT_RXBW; + + int16_t config(); + int16_t setMode(uint8_t mode); + +#if !RADIOLIB_GODMODE + private: +#endif + Module* mod; + + float frequency = RADIOLIB_RF69_DEFAULT_FREQ; + bool ookEnabled = false; + int16_t tempOffset = 0; + int8_t power = RADIOLIB_RF69_DEFAULT_POWER; + + size_t packetLength = 0; + bool packetLengthQueried = false; + uint8_t packetLengthConfig = RADIOLIB_RF69_PACKET_FORMAT_VARIABLE; + + bool promiscuous = false; + + uint8_t syncWordLength = RADIOLIB_RF69_DEFAULT_SW_LEN; + + bool bitSync = true; + + int16_t directMode(); + int16_t setPacketMode(uint8_t mode, uint8_t len); + void clearIRQFlags(); + void clearFIFO(size_t count); +}; + +#endif + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/RFM2x/RFM22.h b/software/firmware/source/libraries/RadioLib/src/modules/RFM2x/RFM22.h new file mode 100644 index 000000000..95a8177af --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/RFM2x/RFM22.h @@ -0,0 +1,20 @@ +#if !defined(_RADIOLIB_RFM22_H) +#define _RADIOLIB_RFM22_H + +#include "../../TypeDef.h" + +#if !RADIOLIB_EXCLUDE_RFM2X + +#include "../../Module.h" +#include "../Si443x/Si443x.h" +#include "../Si443x/Si4432.h" + +/*! + \class RFM22 + \brief Only exists as alias for Si4432, since there seems to be no difference between %RFM22 and %Si4432 modules. +*/ +RADIOLIB_TYPE_ALIAS(Si4432, RFM22) + +#endif + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/RFM2x/RFM23.h b/software/firmware/source/libraries/RadioLib/src/modules/RFM2x/RFM23.h new file mode 100644 index 000000000..fa28a07e0 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/RFM2x/RFM23.h @@ -0,0 +1,20 @@ +#if !defined(_RADIOLIB_RFM23_H) +#define _RADIOLIB_RFM23_H + +#include "../../TypeDef.h" + +#if !RADIOLIB_EXCLUDE_RFM2X + +#include "../../Module.h" +#include "../Si443x/Si443x.h" +#include "../Si443x/Si4431.h" + +/*! + \class RFM23 + \brief Only exists as alias for Si4431, since there seems to be no difference between %RFM23 and %Si4431 modules. +*/ +RADIOLIB_TYPE_ALIAS(Si4431, RFM23) + +#endif + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/SX123x/SX1231.cpp b/software/firmware/source/libraries/RadioLib/src/modules/SX123x/SX1231.cpp new file mode 100644 index 000000000..11424eed3 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/SX123x/SX1231.cpp @@ -0,0 +1,92 @@ +#include "SX1231.h" +#if !RADIOLIB_EXCLUDE_SX1231 + +SX1231::SX1231(Module* mod) : RF69(mod) { + +} + +int16_t SX1231::begin(float freq, float br, float freqDev, float rxBw, int8_t power, uint8_t preambleLen) { + // set module properties + Module* mod = this->getMod(); + mod->init(); + mod->hal->pinMode(mod->getIrq(), mod->hal->GpioModeInput); + mod->hal->pinMode(mod->getRst(), mod->hal->GpioModeOutput); + + // try to find the SX1231 chip + uint8_t i = 0; + bool flagFound = false; + while((i < 10) && !flagFound) { + int16_t version = getChipVersion(); + if((version == RADIOLIB_SX123X_CHIP_REVISION_2_A) || (version == RADIOLIB_SX123X_CHIP_REVISION_2_B) || (version == RADIOLIB_SX123X_CHIP_REVISION_2_C)) { + flagFound = true; + this->chipRevision = version; + } else { + RADIOLIB_DEBUG_BASIC_PRINTLN("SX1231 not found! (%d of 10 tries) RF69_REG_VERSION == 0x%04X, expected 0x0021 / 0x0022 / 0x0023", i + 1, version); + mod->hal->delay(10); + i++; + } + } + + if(!flagFound) { + RADIOLIB_DEBUG_BASIC_PRINTLN("No SX1231 found!"); + mod->term(); + return(RADIOLIB_ERR_CHIP_NOT_FOUND); + } + RADIOLIB_DEBUG_BASIC_PRINTLN("M\tSX1231"); + + // configure settings not accessible by API + int16_t state = config(); + RADIOLIB_ASSERT(state); + RADIOLIB_DEBUG_BASIC_PRINTLN("M\tRF69"); + + // configure publicly accessible settings + state = setFrequency(freq); + RADIOLIB_ASSERT(state); + + // configure bitrate + this->rxBandwidth = 125.0; + state = setBitRate(br); + RADIOLIB_ASSERT(state); + + // configure default RX bandwidth + state = setRxBandwidth(rxBw); + RADIOLIB_ASSERT(state); + + // configure default frequency deviation + state = setFrequencyDeviation(freqDev); + RADIOLIB_ASSERT(state); + + // configure default TX output power + state = setOutputPower(power); + RADIOLIB_ASSERT(state); + + // configure default preamble length + state = setPreambleLength(preambleLen); + RADIOLIB_ASSERT(state); + + // default sync word values 0x2D01 is the same as the default in LowPowerLab RFM69 library + uint8_t syncWord[] = {0x2D, 0x01}; + state = setSyncWord(syncWord, 2); + RADIOLIB_ASSERT(state); + + // set default packet length mode + state = variablePacketLengthMode(); + if (state != RADIOLIB_ERR_NONE) { + return(state); + } + + // SX123x V2a only + if(this->chipRevision == RADIOLIB_SX123X_CHIP_REVISION_2_A) { + // modify default OOK threshold value + state = mod->SPIsetRegValue(RADIOLIB_SX1231_REG_TEST_OOK, RADIOLIB_SX1231_OOK_DELTA_THRESHOLD); + RADIOLIB_ASSERT(state); + + // enable OCP with 95 mA limit + state = mod->SPIsetRegValue(RADIOLIB_RF69_REG_OCP, RADIOLIB_RF69_OCP_ON | RADIOLIB_RF69_OCP_TRIM, 4, 0); + RADIOLIB_ASSERT(state); + } + + return(RADIOLIB_ERR_NONE); +} + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/SX123x/SX1231.h b/software/firmware/source/libraries/RadioLib/src/modules/SX123x/SX1231.h new file mode 100644 index 000000000..6dc373576 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/SX123x/SX1231.h @@ -0,0 +1,121 @@ +#if !defined(_RADIOLIB_SX1231_H) +#define _RADIOLIB_SX1231_H + +#include "../../TypeDef.h" + +#if !RADIOLIB_EXCLUDE_SX1231 + +#include "../../Module.h" +#include "../RF69/RF69.h" + +#define RADIOLIB_SX123X_CHIP_REVISION_2_A 0x21 +#define RADIOLIB_SX123X_CHIP_REVISION_2_B 0x22 +#define RADIOLIB_SX123X_CHIP_REVISION_2_C 0x23 + +// RADIOLIB_SX1231 specific register map +#define RADIOLIB_SX1231_REG_TEST_OOK 0x6E + +// RADIOLIB_SX1231_REG_TEST_OOK +#define RADIOLIB_SX1231_OOK_DELTA_THRESHOLD 0x0C + +// RADIOLIB_SX1231_REG_DIO_MAPPING_1 +#define RADIOLIB_SX1231_DIO0_CONT_LOW_BAT 0b10000000 // 7 6 +#define RADIOLIB_SX1231_DIO0_CONT_MODE_READY 0b11000000 // 7 6 +#define RADIOLIB_SX1231_DIO0_CONT_PLL_LOCK 0b00000000 // 7 6 +#define RADIOLIB_SX1231_DIO0_CONT_SYNC_ADDRESS 0b00000000 // 7 6 +#define RADIOLIB_SX1231_DIO0_CONT_TIMEOUT 0b01000000 // 7 6 +#define RADIOLIB_SX1231_DIO0_CONT_RSSI 0b10000000 // 7 6 +#define RADIOLIB_SX1231_DIO0_CONT_MODE_READY 0b11000000 // 7 6 +#define RADIOLIB_SX1231_DIO0_CONT_TX_READY 0b01000000 // 7 6 +#define RADIOLIB_SX1231_DIO0_PACK_LOW_BAT 0b10000000 // 7 6 +#define RADIOLIB_SX1231_DIO0_PACK_PLL_LOCK 0b11000000 // 7 6 +#define RADIOLIB_SX1231_DIO0_PACK_CRC_OK 0b00000000 // 7 6 +#define RADIOLIB_SX1231_DIO0_PACK_PAYLOAD_READY 0b01000000 // 7 6 +#define RADIOLIB_SX1231_DIO0_PACK_SYNC_ADDRESS 0b10000000 // 7 6 +#define RADIOLIB_SX1231_DIO0_PACK_RSSI 0b11000000 // 7 6 +#define RADIOLIB_SX1231_DIO0_PACK_PACKET_SENT 0b00000000 // 7 6 +#define RADIOLIB_SX1231_DIO0_PACK_TX_READY 0b01000000 // 7 6 +#define RADIOLIB_SX1231_DIO1_CONT_LOW_BAT 0b00100000 // 5 4 +#define RADIOLIB_SX1231_DIO1_CONT_PLL_LOCK 0b00110000 // 5 4 +#define RADIOLIB_SX1231_DIO1_CONT_DCLK 0b00000000 // 5 4 +#define RADIOLIB_SX1231_DIO1_CONT_RX_READY 0b00010000 // 5 4 +#define RADIOLIB_SX1231_DIO1_CONT_SYNC_ADDRESS 0b00110000 // 5 4 +#define RADIOLIB_SX1231_DIO1_CONT_TX_READY 0b00010000 // 5 4 +#define RADIOLIB_SX1231_DIO1_PACK_FIFO_LEVEL 0b00000000 // 5 4 +#define RADIOLIB_SX1231_DIO1_PACK_FIFO_FULL 0b00010000 // 5 4 +#define RADIOLIB_SX1231_DIO1_PACK_FIFO_NOT_EMPTY 0b00100000 // 5 4 +#define RADIOLIB_SX1231_DIO1_PACK_PLL_LOCK 0b00110000 // 5 4 +#define RADIOLIB_SX1231_DIO1_PACK_TIMEOUT 0b00110000 // 5 4 +#define RADIOLIB_SX1231_DIO2_CONT_DATA 0b00000000 // 3 2 +#define RADIOLIB_SX1231_DIO2_PACK_FIFO_NOT_EMPTY 0b00000000 // 3 2 +#define RADIOLIB_SX1231_DIO2_PACK_LOW_BAT 0b00001000 // 3 2 +#define RADIOLIB_SX1231_DIO2_PACK_AUTO_MODE 0b00001100 // 3 2 +#define RADIOLIB_SX1231_DIO2_PACK_DATA 0b00000100 // 3 2 +#define RADIOLIB_SX1231_DIO3_CONT_AUTO_MODE 0b00000010 // 0 1 +#define RADIOLIB_SX1231_DIO3_CONT_RSSI 0b00000000 // 0 1 +#define RADIOLIB_SX1231_DIO3_CONT_RX_READY 0b00000001 // 0 1 +#define RADIOLIB_SX1231_DIO3_CONT_TIMEOUT 0b00000011 // 0 1 +#define RADIOLIB_SX1231_DIO3_CONT_TX_READY 0b00000001 // 0 1 +#define RADIOLIB_SX1231_DIO3_PACK_FIFO_FULL 0b00000000 // 0 1 +#define RADIOLIB_SX1231_DIO3_PACK_LOW_BAT 0b00000010 // 0 1 +#define RADIOLIB_SX1231_DIO3_PACK_PLL_LOCK 0b00000011 // 0 1 +#define RADIOLIB_SX1231_DIO3_PACK_RSSI 0b00000001 // 0 1 +#define RADIOLIB_SX1231_DIO3_PACK_SYNC_ADDRESSS 0b00000010 // 0 1 +#define RADIOLIB_SX1231_DIO3_PACK_TX_READY 0b00000001 // 0 1 + +// RADIOLIB_SX1231_REG_DIO_MAPPING_2 +#define RADIOLIB_SX1231_DIO4_CONT_LOW_BAT 0b10000000 // 7 6 +#define RADIOLIB_SX1231_DIO4_CONT_PLL_LOCK 0b11000000 // 7 6 +#define RADIOLIB_SX1231_DIO4_CONT_TIMEOUT 0b00000000 // 7 6 +#define RADIOLIB_SX1231_DIO4_CONT_RX_READY 0b01000000 // 7 6 +#define RADIOLIB_SX1231_DIO4_CONT_SYNC_ADDRESS 0b10000000 // 7 6 +#define RADIOLIB_SX1231_DIO4_CONT_TX_READY 0b01000000 // 7 6 +#define RADIOLIB_SX1231_DIO4_PACK_LOW_BAT 0b10000000 // 7 6 +#define RADIOLIB_SX1231_DIO4_PACK_PLL_LOCK 0b11000000 // 7 6 +#define RADIOLIB_SX1231_DIO4_PACK_TIMEOUT 0b00000000 // 7 6 +#define RADIOLIB_SX1231_DIO4_PACK_RSSI 0b01000000 // 7 6 +#define RADIOLIB_SX1231_DIO4_PACK_RX_READY 0b10000000 // 7 6 +#define RADIOLIB_SX1231_DIO4_PACK_MODE_READY 0b00000000 // 7 6 +#define RADIOLIB_SX1231_DIO4_PACK_TX_READY 0b01000000 // 7 6 +#define RADIOLIB_SX1231_DIO5_CONT_LOW_BAT 0b00100000 // 5 4 +#define RADIOLIB_SX1231_DIO5_CONT_MODE_READY 0b00110000 // 5 4 +#define RADIOLIB_SX1231_DIO5_CONT_CLK_OUT 0b00000000 // 5 4 +#define RADIOLIB_SX1231_DIO5_CONT_RSSI 0b00010000 // 5 4 +#define RADIOLIB_SX1231_DIO5_PACK_LOW_BAT 0b00100000 // 5 4 +#define RADIOLIB_SX1231_DIO5_PACK_MODE_READY 0b00110000 // 5 4 +#define RADIOLIB_SX1231_DIO5_PACK_CLK_OUT 0b00000000 // 5 4 +#define RADIOLIB_SX1231_DIO5_PACK_DATA 0b00010000 // 5 4 + +/*! + \class SX1231 + \brief Control class for %SX1231 module. Overrides some methods from RF69 due to different register values. +*/ +class SX1231: public RF69 { + public: + /*! + \brief Default constructor. + \param mod Instance of Module that will be used to communicate with the radio. + */ + SX1231(Module* mod); // cppcheck-suppress noExplicitConstructor + + /*! + \brief Initialization method. + \param freq Carrier frequency in MHz. Defaults to 434.0 MHz. + \param br Bit rate to be used in kbps. Defaults to 4.8 kbps. + \param freqDev Frequency deviation from carrier frequency in kHz Defaults to 5.0 kHz. + \param rxBw Receiver bandwidth in kHz. Defaults to 125.0 kHz. + \param power Output power in dBm. Defaults to 10 dBm. + \param preambleLen Preamble Length in bits. Defaults to 16 bits. + \returns \ref status_codes + */ + int16_t begin(float freq = 434.0, float br = 4.8, float freqDev = 5.0, float rxBw = 125.0, int8_t power = 10, uint8_t preambleLen = 16); + +#if !RADIOLIB_GODMODE + protected: +#endif + uint8_t chipRevision = 0; +}; + +#endif + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/SX123x/SX1233.cpp b/software/firmware/source/libraries/RadioLib/src/modules/SX123x/SX1233.cpp new file mode 100644 index 000000000..23eb72c6f --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/SX123x/SX1233.cpp @@ -0,0 +1,127 @@ +#include "SX1233.h" +#include +#if !RADIOLIB_EXCLUDE_SX1231 + +SX1233::SX1233(Module* mod) : SX1231(mod) { + +} + +int16_t SX1233::begin(float freq, float br, float freqDev, float rxBw, int8_t power, uint8_t preambleLen) { + // set module properties + Module* mod = this->getMod(); + mod->init(); + mod->hal->pinMode(mod->getIrq(), mod->hal->GpioModeInput); + mod->hal->pinMode(mod->getRst(), mod->hal->GpioModeOutput); + + // try to find the SX1233 chip + uint8_t i = 0; + bool flagFound = false; + while((i < 10) && !flagFound) { + int16_t version = getChipVersion(); + if((version == RADIOLIB_SX123X_CHIP_REVISION_2_A) || (version == RADIOLIB_SX123X_CHIP_REVISION_2_B) || (version == RADIOLIB_SX123X_CHIP_REVISION_2_C)) { + flagFound = true; + this->chipRevision = version; + } else { + RADIOLIB_DEBUG_BASIC_PRINTLN("SX1231 not found! (%d of 10 tries) RF69_REG_VERSION == 0x%04X, expected 0x0021 / 0x0022 / 0x0023", i + 1, version); + mod->hal->delay(10); + i++; + } + } + + if(!flagFound) { + RADIOLIB_DEBUG_BASIC_PRINTLN("No SX1233 found!"); + mod->term(); + return(RADIOLIB_ERR_CHIP_NOT_FOUND); + } + RADIOLIB_DEBUG_BASIC_PRINTLN("M\tSX1233"); + + // configure settings not accessible by API + int16_t state = config(); + RADIOLIB_ASSERT(state); + RADIOLIB_DEBUG_BASIC_PRINTLN("M\tRF69"); + + // configure publicly accessible settings + state = setFrequency(freq); + RADIOLIB_ASSERT(state); + + // configure bitrate + this->rxBandwidth = 125.0; + state = setBitRate(br); + RADIOLIB_ASSERT(state); + + // configure default RX bandwidth + state = setRxBandwidth(rxBw); + RADIOLIB_ASSERT(state); + + // configure default frequency deviation + state = setFrequencyDeviation(freqDev); + RADIOLIB_ASSERT(state); + + // configure default TX output power + state = setOutputPower(power); + RADIOLIB_ASSERT(state); + + // configure default preamble length + state = setPreambleLength(preambleLen); + RADIOLIB_ASSERT(state); + + // default sync word values 0x2D01 is the same as the default in LowPowerLab RFM69 library + uint8_t syncWord[] = {0x2D, 0x01}; + state = setSyncWord(syncWord, 2); + RADIOLIB_ASSERT(state); + + // set default packet length mode + state = variablePacketLengthMode(); + if (state != RADIOLIB_ERR_NONE) { + return(state); + } + + // SX123x V2a only + if(this->chipRevision == RADIOLIB_SX123X_CHIP_REVISION_2_A) { + // modify default OOK threshold value + state = mod->SPIsetRegValue(RADIOLIB_SX1231_REG_TEST_OOK, RADIOLIB_SX1231_OOK_DELTA_THRESHOLD); + RADIOLIB_ASSERT(state); + + // enable OCP with 95 mA limit + state = mod->SPIsetRegValue(RADIOLIB_RF69_REG_OCP, RADIOLIB_RF69_OCP_ON | RADIOLIB_RF69_OCP_TRIM, 4, 0); + RADIOLIB_ASSERT(state); + } + + return(RADIOLIB_ERR_NONE); +} + +int16_t SX1233::setBitRate(float br) { + // check high bit-rate operation + uint8_t pllBandwidth = RADIOLIB_SX1233_PLL_BW_LOW_BIT_RATE; + if((fabsf(br - 500.0f) < 0.1) || (fabsf(br - 600.0f) < 0.1)) { + pllBandwidth = RADIOLIB_SX1233_PLL_BW_HIGH_BIT_RATE; + } else { + // datasheet says 1.2 kbps should be the smallest possible, but 0.512 works fine + RADIOLIB_CHECK_RANGE(br, 0.5, 300.0, RADIOLIB_ERR_INVALID_BIT_RATE); + } + + + // check bitrate-bandwidth ratio + if(!(br < 2000 * this->rxBandwidth)) { + return(RADIOLIB_ERR_INVALID_BIT_RATE_BW_RATIO); + } + + // set mode to standby + setMode(RADIOLIB_RF69_STANDBY); + + // set PLL bandwidth + Module* mod = this->getMod(); + int16_t state = mod->SPIsetRegValue(RADIOLIB_SX1233_REG_TEST_PLL, pllBandwidth, 7, 0); + RADIOLIB_ASSERT(state); + + // set bit rate + uint16_t bitRate = 32000 / br; + state = mod->SPIsetRegValue(RADIOLIB_RF69_REG_BITRATE_MSB, (bitRate & 0xFF00) >> 8, 7, 0); + state |= mod->SPIsetRegValue(RADIOLIB_RF69_REG_BITRATE_LSB, bitRate & 0x00FF, 7, 0); + if(state == RADIOLIB_ERR_NONE) { + this->bitRate = br; + } + return(state); +} + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/SX123x/SX1233.h b/software/firmware/source/libraries/RadioLib/src/modules/SX123x/SX1233.h new file mode 100644 index 000000000..bf9bb1481 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/SX123x/SX1233.h @@ -0,0 +1,61 @@ +#if !defined(_RADIOLIB_SX1233_H) +#define _RADIOLIB_SX1233_H + +#include "../../TypeDef.h" + +#if !RADIOLIB_EXCLUDE_SX1231 + +#include "../../Module.h" +#include "../RF69/RF69.h" +#include "SX1231.h" + +// RADIOLIB_SX1233 specific register map +#define RADIOLIB_SX1233_REG_TEST_PLL 0x5F + +// RADIOLIB_SX1233_REG_TEST_PLL +#define RADIOLIB_SX1233_PLL_BW_HIGH_BIT_RATE 0x0C +#define RADIOLIB_SX1233_PLL_BW_LOW_BIT_RATE 0x08 + +/*! + \class SX1233 + \brief Control class for %SX1233 module. Overrides some methods from SX1231/RF69 due to different register values. +*/ +class SX1233: public SX1231 { + public: + /*! + \brief Default constructor. + \param mod Instance of Module that will be used to communicate with the radio. + */ + SX1233(Module* mod); // cppcheck-suppress noExplicitConstructor + + /*! + \brief Initialization method. + \param freq Carrier frequency in MHz. Defaults to 434.0 MHz. + \param br Bit rate to be used in kbps. Defaults to 4.8 kbps. + \param freqDev Frequency deviation from carrier frequency in kHz Defaults to 5.0 kHz. + \param rxBw Receiver bandwidth in kHz. Defaults to 125.0 kHz. + \param power Output power in dBm. Defaults to 10 dBm. + \param preambleLen Preamble Length in bits. Defaults to 16 bits. + \returns \ref status_codes + */ + int16_t begin(float freq = 434.0, float br = 4.8, float freqDev = 5.0, float rxBw = 125.0, int8_t power = 10, uint8_t preambleLen = 16); + + /*! + \brief Sets bit rate. Allowed values range from 0.5 to 300.0 kbps. + SX1233 also allows 500 kbps and 600 kbps operation. + NOTE: For 500 kbps rate, the receiver frequency should be offset by 50 kHz from the transmitter. + For 600 kbps rate, the receiver frequency should be offset by 40 kHz from the transmitter. + \param br Bit rate to be set in kbps. + \returns \ref status_codes + */ + int16_t setBitRate(float br) override; + +#if !RADIOLIB_GODMODE + private: +#endif + +}; + +#endif + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/SX126x/STM32WLx.cpp b/software/firmware/source/libraries/RadioLib/src/modules/SX126x/STM32WLx.cpp new file mode 100644 index 000000000..e9f47167d --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/SX126x/STM32WLx.cpp @@ -0,0 +1,154 @@ +/* +Copyright (c) 2018 Jan Gromeš +Copyright (c) 2022 STMicroelectronics + +This file is licensed under the MIT License: https://opensource.org/licenses/MIT +*/ + +#include "STM32WLx.h" +#if !RADIOLIB_EXCLUDE_STM32WLX + +STM32WLx::STM32WLx(STM32WLx_Module* mod) : SX1262(mod) { } + +int16_t STM32WLx::begin(float freq, float bw, uint8_t sf, uint8_t cr, uint8_t syncWord, int8_t power, uint16_t preambleLength, float tcxoVoltage, bool useRegulatorLDO) { + // Execute common part + int16_t state = SX1262::begin(freq, bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage, useRegulatorLDO); + RADIOLIB_ASSERT(state); + + // This overrides the value in SX126x::begin() + // On STM32WL, DIO2 is hardwired to the radio IRQ on the MCU, so it + // should really not be used as RfSwitch control output. + state = setDio2AsRfSwitch(false); + RADIOLIB_ASSERT(state); + + return(state); +} + +int16_t STM32WLx::beginFSK(float freq, float br, float freqDev, float rxBw, int8_t power, uint16_t preambleLength, float tcxoVoltage, bool useRegulatorLDO) { + // Execute common part + int16_t state = SX1262::beginFSK(freq, br, freqDev, rxBw, power, preambleLength, tcxoVoltage, useRegulatorLDO); + RADIOLIB_ASSERT(state); + + // This overrides the value in SX126x::beginFSK() + // On STM32WL, DIO2 is hardwired to the radio IRQ on the MCU, so it + // should really not be used as RfSwitch control output. + state = setDio2AsRfSwitch(false); + RADIOLIB_ASSERT(state); + + return(state); +} + +int16_t STM32WLx::setOutputPower(int8_t power) { + // get current OCP configuration + uint8_t ocp = 0; + int16_t state = readRegister(RADIOLIB_SX126X_REG_OCP_CONFIGURATION, &ocp, 1); + RADIOLIB_ASSERT(state); + + // check the user did not request power output that is not possible + Module* mod = this->getMod(); + bool hp_supported = mod->findRfSwitchMode(MODE_TX_HP); + bool lp_supported = mod->findRfSwitchMode(MODE_TX_LP); + if((!lp_supported && (power < -9)) || (!hp_supported && (power > 14))) { + // LP not supported but requested power is below HP low bound or + // HP not supported but requested power is above LP high bound + return(RADIOLIB_ERR_INVALID_OUTPUT_POWER); + } + + // set PA config based on which PAs are supported + bool use_hp = false; + if(hp_supported && lp_supported) { + // both PAs supported, use HP when above 14 dBm + if(power > 14) { + RADIOLIB_CHECK_RANGE(power, -9, 22, RADIOLIB_ERR_INVALID_OUTPUT_POWER); + state = SX126x::setPaConfig(0x04, 0x00, 0x07); // HP output up to 22dBm + this->txMode = MODE_TX_HP; + use_hp = true; + } else { + RADIOLIB_CHECK_RANGE(power, -17, 14, RADIOLIB_ERR_INVALID_OUTPUT_POWER); + state = SX126x::setPaConfig(0x04, 0x01, 0x00); // LP output up to 14dBm + this->txMode = MODE_TX_LP; + } + + } else if(!hp_supported && lp_supported) { + // only LP supported + RADIOLIB_CHECK_RANGE(power, -17, 14, RADIOLIB_ERR_INVALID_OUTPUT_POWER); + state = SX126x::setPaConfig(0x04, 0x01, 0x00); + this->txMode = MODE_TX_LP; + + } else if(hp_supported && !lp_supported) { + // only HP supported + RADIOLIB_CHECK_RANGE(power, -9, 22, RADIOLIB_ERR_INVALID_OUTPUT_POWER); + state = SX126x::setPaConfig(0x04, 0x00, 0x07); + this->txMode = MODE_TX_HP; + use_hp = true; + + } else { + // neither PA is supported + return(RADIOLIB_ERR_INVALID_OUTPUT_POWER); + + } + RADIOLIB_ASSERT(state); + + // Apply workaround for HP only + state = SX126x::fixPaClamping(use_hp); + RADIOLIB_ASSERT(state); + + // set output power with default 200us ramp + state = SX126x::setTxParams(power, RADIOLIB_SX126X_PA_RAMP_200U); + RADIOLIB_ASSERT(state); + + // restore OCP configuration + return(writeRegister(RADIOLIB_SX126X_REG_OCP_CONFIGURATION, &ocp, 1)); +} + +int16_t STM32WLx::clearIrqStatus(uint16_t clearIrqParams) { + int16_t res = SX126x::clearIrqStatus(clearIrqParams); + // The NVIC interrupt is level-sensitive, so clear away any pending + // flag that is only set because the radio IRQ status was not cleared + // in the interrupt (to prevent each IRQ triggering twice and allow + // reading the irq status through the pending flag). + SubGhz.clearPendingInterrupt(); + if(SubGhz.hasInterrupt()) + SubGhz.enableInterrupt(); + return(res); +} + +void STM32WLx::setDio1Action(void (*func)(void)) { + SubGhz.attachInterrupt([func]() { + // Because the interrupt is level-triggered, we disable it in the + // NVIC (otherwise we would need an SPI command to clear the IRQ in + // the radio, or it would trigger over and over again). + SubGhz.disableInterrupt(); + func(); + }); +} + +void STM32WLx::clearDio1Action() { + SubGhz.detachInterrupt(); +} + +void STM32WLx::setPacketReceivedAction(void (*func)(void)) { + this->setDio1Action(func); +} + +void STM32WLx::clearPacketReceivedAction() { + this->clearDio1Action(); +} + +void STM32WLx::setPacketSentAction(void (*func)(void)) { + this->setDio1Action(func); +} + +void STM32WLx::clearPacketSentAction() { + this->clearDio1Action(); +} + +void STM32WLx::setChannelScanAction(void (*func)(void)) { + this->setDio1Action(func); +} + +void STM32WLx::clearChannelScanAction() { + this->clearDio1Action(); +} + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/SX126x/STM32WLx.h b/software/firmware/source/libraries/RadioLib/src/modules/SX126x/STM32WLx.h new file mode 100644 index 000000000..993791aae --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/SX126x/STM32WLx.h @@ -0,0 +1,168 @@ +/* +Copyright (c) 2018 Jan Gromeš +Copyright (c) 2022 STMicroelectronics + +This file is licensed under the MIT License: https://opensource.org/licenses/MIT +*/ + +#if !defined(_RADIOLIB_STM32WLx_H) +#define _RADIOLIB_STM32WLx_H + +#include "../../TypeDef.h" + +#if !RADIOLIB_EXCLUDE_STM32WLX + +#include "../../Module.h" +#include "SX1262.h" +#include "STM32WLx_Module.h" + +/*! + \class STM32WLx + + \brief Derived class for STM32WL modules. + + The radio integrated into these modules is essentially the same as the + Semtech %SX126x external radio chips, so most of the documentation for + those also applies here. + + One notable difference with the %SX126x radios is that this radio + essentially combines the %SX1261 and %SX1262 by integrating both the + low-power (LP) and high-power (HP) amplifier. See setOutputPower() and + setRfSwitchTable() for details on how this is handled. +*/ +class STM32WLx : public SX1262 { + // NOTE: This class could not be named STM32WL (or STM32WLxx), since + // those are macros defined by + // system/Drivers/CMSIS/Device/ST/STM32WLxxx/Include/stm32wlxx.h + public: + /*! + \brief Default constructor. + \param mod Instance of STM32WLx_Module that will be used to communicate with the radio. + */ + STM32WLx(STM32WLx_Module* mod); // cppcheck-suppress noExplicitConstructor + + /*! + \brief Custom operation modes for STMWLx. + + This splits the TX mode into two modes: Low-power and high-power. + These constants can be used with the setRfSwitchTable() method, + instead of the Module::OpMode_t constants. + */ + enum OpMode_t { + /*! End of table marker, use \ref END_OF_MODE_TABLE constant instead */ + MODE_END_OF_TABLE = Module::MODE_END_OF_TABLE, + /*! Idle mode */ + MODE_IDLE = Module::MODE_IDLE, + /*! Receive mode */ + MODE_RX = Module::MODE_RX, + /*! Low power transmission mode */ + MODE_TX_LP = Module::MODE_TX, + /*! High power transmission mode */ + MODE_TX_HP, + }; + + // basic methods + + /*! + \copydoc SX1262::begin + */ + int16_t begin(float freq = 434.0, float bw = 125.0, uint8_t sf = 9, uint8_t cr = 7, uint8_t syncWord = RADIOLIB_SX126X_SYNC_WORD_PRIVATE, int8_t power = 10, uint16_t preambleLength = 8, float tcxoVoltage = 1.6, bool useRegulatorLDO = false); + + /*! + \copydoc SX1262::beginFSK + */ + 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); + + // configuration methods + + /*! + \brief Sets output power. Allowed values are in range from -17 to 22 dBm. + + This automatically switches between the low-power (LP) and high-power (HP) amplifier. + + LP is preferred and supports -17 to +14dBm. When a higher power is + requested (or the LP amplifier is marked as unavailable using + setRfSwitchTable()), HP is used, which supports -9 to +22dBm. If the LP is marked as unavailable, + HP output will be used instead. + + \param power Output power to be set in dBm. + + \returns \ref status_codes + */ + virtual int16_t setOutputPower(int8_t power) override; + + /*! + \copybrief Module::setRfSwitchTable + + This method works like Module::setRfSwitchTable(), except that you + should use STM32WLx::OpMode_t constants for modes, which + distinguishes between a low-power (LP) and high-power (HP) TX mode. + + For boards that do not support both modes, just omit the + unsupported mode from the table and it will not be used (and the + valid power range is adjusted by setOutputPower() accordingly). + + Note that the setRfSwitchTable() method should be called *before* the + begin() method, to ensure the radio knows which modes are supported + during initialization. + */ + // Note: This explicitly inherits this method only to override docs + using SX126x::setRfSwitchTable; + + /*! + \brief Sets interrupt service routine to call when DIO1/2/3 activates. + \param func ISR to call. + */ + void setDio1Action(void (*func)(void)); + + /*! + \brief Clears interrupt service routine to call when DIO1/2/3 activates. + */ + void clearDio1Action(); + + /*! + \brief Sets interrupt service routine to call when a packet is received. + \param func ISR to call. + */ + void setPacketReceivedAction(void (*func)(void)) override; + + /*! + \brief Clears interrupt service routine to call when a packet is received. + */ + void clearPacketReceivedAction() override; + + /*! + \brief Sets interrupt service routine to call when a packet is sent. + \param func ISR to call. + */ + void setPacketSentAction(void (*func)(void)) override; + + /*! + \brief Clears interrupt service routine to call when a packet is sent. + */ + void clearPacketSentAction() override; + + /*! + \brief Sets interrupt service routine to call when a channel scan is finished. + \param func ISR to call. + */ + void setChannelScanAction(void (*func)(void)) override; + + /*! + \brief Clears interrupt service routine to call when a channel scan is finished. + */ + void clearChannelScanAction() override; + +#if !RADIOLIB_GODMODE + protected: +#endif + virtual int16_t clearIrqStatus(uint16_t clearIrqParams) override; + +#if !RADIOLIB_GODMODE + private: +#endif +}; + +#endif + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/SX126x/STM32WLx_Module.cpp b/software/firmware/source/libraries/RadioLib/src/modules/SX126x/STM32WLx_Module.cpp new file mode 100644 index 000000000..d2dd47d09 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/SX126x/STM32WLx_Module.cpp @@ -0,0 +1,125 @@ +/* + +Copyright (c) 2022 STMicroelectronics + +This file is licensed under the MIT License: https://opensource.org/licenses/MIT +*/ + +#include "STM32WLx_Module.h" + +#if !RADIOLIB_EXCLUDE_STM32WLX + +#include "hal/Arduino/ArduinoHal.h" + +// This defines some dummy pin numbers (starting at NUM_DIGITAL_PINS to +// guarantee these are not valid regular pin numbers) that can be passed +// to the parent Module class, to be stored here and then passed back to +// the overridden callbacks when these are used. +enum { + RADIOLIB_STM32WLx_VIRTUAL_PIN_NSS = NUM_DIGITAL_PINS, + RADIOLIB_STM32WLx_VIRTUAL_PIN_BUSY, + RADIOLIB_STM32WLx_VIRTUAL_PIN_IRQ, + RADIOLIB_STM32WLx_VIRTUAL_PIN_RESET, +}; + +/*! + \class Stm32wlxHal + \brief Hardware Abstraction Layer for STM32WL. +*/ +class Stm32wlxHal : public ArduinoHal { + public: + Stm32wlxHal(): ArduinoHal(SubGhz.SPI, SubGhz.spi_settings) {} + + /*! + \brief Pin mode override to handle STM32WL virtual pins. + \param dwPin Pin to set. + \param dwMode Mode to set. + */ + void pinMode(uint32_t dwPin, uint32_t dwMode) { + switch(dwPin) { + case RADIOLIB_STM32WLx_VIRTUAL_PIN_NSS: + case RADIOLIB_STM32WLx_VIRTUAL_PIN_BUSY: + case RADIOLIB_STM32WLx_VIRTUAL_PIN_IRQ: + case RADIOLIB_STM32WLx_VIRTUAL_PIN_RESET: + // Nothing to do + break; + default: + ::pinMode(dwPin, dwMode); + break; + } + } + + /*! + \brief Digital write override to handle STM32WL virtual pins. + \param dwPin Pin to set. + \param dwVal Value to set. + */ + void digitalWrite(uint32_t dwPin, uint32_t dwVal) { + switch (dwPin) { + case RADIOLIB_STM32WLx_VIRTUAL_PIN_NSS: + SubGhz.setNssActive(dwVal == LOW); + break; + + case RADIOLIB_STM32WLx_VIRTUAL_PIN_RESET: + SubGhz.setResetActive(dwVal == LOW); + break; + + case RADIOLIB_STM32WLx_VIRTUAL_PIN_BUSY: + case RADIOLIB_STM32WLx_VIRTUAL_PIN_IRQ: + // Should not (and cannot) be written, just ignore + break; + + default: + ::digitalWrite(dwPin, dwVal); + break; + } + } + + /*! + \brief Digital read override to handle STM32WL virtual pins. + \param ulPin Pin to read. + \returns Value read on the pin. + */ + uint32_t digitalRead(uint32_t ulPin) { + switch (ulPin) { + case RADIOLIB_STM32WLx_VIRTUAL_PIN_BUSY: + return(SubGhz.isBusy() ? HIGH : LOW); + + case RADIOLIB_STM32WLx_VIRTUAL_PIN_IRQ: + // We cannot use the radio IRQ output directly, but since: + // - the pending flag will be set whenever the IRQ output is set, + // and + // - the pending flag will be cleared (by + // STM32WLx::clearIrqStatus()) whenever the radio IRQ output is + // cleared, + // the pending flag should always reflect the current radio IRQ + // output. There is one exception: when the ISR starts the pending + // flag is cleared by hardware and not set again until after the + // ISR finishes, so the value is incorrect *inside* the ISR, but + // running RadioLib code inside the ISR (especially code that + // polls the IRQ flag) is not supported and probably broken in + // other ways too. + return(SubGhz.isInterruptPending() ? HIGH : LOW); + + case RADIOLIB_STM32WLx_VIRTUAL_PIN_NSS: + return(SubGhz.isNssActive() ? LOW : HIGH); + + case RADIOLIB_STM32WLx_VIRTUAL_PIN_RESET: + return(SubGhz.isResetActive() ? LOW : HIGH); + + default: + return(::digitalRead(ulPin)); + } + } +}; + +STM32WLx_Module::STM32WLx_Module(): + Module( + new Stm32wlxHal, + RADIOLIB_STM32WLx_VIRTUAL_PIN_NSS, + RADIOLIB_STM32WLx_VIRTUAL_PIN_IRQ, + RADIOLIB_STM32WLx_VIRTUAL_PIN_RESET, + RADIOLIB_STM32WLx_VIRTUAL_PIN_BUSY + ) {} + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/SX126x/STM32WLx_Module.h b/software/firmware/source/libraries/RadioLib/src/modules/SX126x/STM32WLx_Module.h new file mode 100644 index 000000000..878ba775e --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/SX126x/STM32WLx_Module.h @@ -0,0 +1,38 @@ +/* + +Copyright (c) 2022 STMicroelectronics + +This file is licensed under the MIT License: https://opensource.org/licenses/MIT +*/ + +#if !defined(_RADIOLIB_STM32WLX_MODULE_H) +#define _RADIOLIB_STM32WLX_MODULE_H + +#include "../../TypeDef.h" + +#if !RADIOLIB_EXCLUDE_STM32WLX + +#include "../../Module.h" + +/*! + * \class STM32WLx_Module + * + * This is a subclass of Module to be used with the STM32WLx driver. + * + * It is used to override some callbacks, allowing access to some of the + * radio control signals that are wired to internal registers instead of + * actual GPIO pins. + */ +class STM32WLx_Module : public Module { + // Note: We cannot easily override any methods here, since most calls + // are non-virtual and made through a Module*, so they would not be + // calling any overridden methods. This means this class works by + // overriding some of the callbacks in its constructor. + + public: + STM32WLx_Module(); +}; + +#endif + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/SX126x/SX1261.cpp b/software/firmware/source/libraries/RadioLib/src/modules/SX126x/SX1261.cpp new file mode 100644 index 000000000..d6a90c342 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/SX126x/SX1261.cpp @@ -0,0 +1,38 @@ +#include "SX1261.h" +#if !RADIOLIB_EXCLUDE_SX126X + +SX1261::SX1261(Module* mod): SX1262(mod) { + chipType = RADIOLIB_SX1261_CHIP_TYPE; +} + +int16_t SX1261::setOutputPower(int8_t power) { + // check if power value is configurable + int16_t state = checkOutputPower(power, NULL); + RADIOLIB_ASSERT(state); + + // get current OCP configuration + uint8_t ocp = 0; + state = readRegister(RADIOLIB_SX126X_REG_OCP_CONFIGURATION, &ocp, 1); + RADIOLIB_ASSERT(state); + + // set PA config + state = SX126x::setPaConfig(0x04, RADIOLIB_SX126X_PA_CONFIG_SX1261, 0x00); + RADIOLIB_ASSERT(state); + + // set output power with default 200us ramp + state = SX126x::setTxParams(power, RADIOLIB_SX126X_PA_RAMP_200U); + RADIOLIB_ASSERT(state); + + // restore OCP configuration + return(writeRegister(RADIOLIB_SX126X_REG_OCP_CONFIGURATION, &ocp, 1)); +} + +int16_t SX1261::checkOutputPower(int8_t power, int8_t* clipped) { + if(clipped) { + *clipped = RADIOLIB_MAX(-17, RADIOLIB_MIN(14, power)); + } + RADIOLIB_CHECK_RANGE(power, -17, 14, RADIOLIB_ERR_INVALID_OUTPUT_POWER); + return(RADIOLIB_ERR_NONE); +} + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/SX126x/SX1261.h b/software/firmware/source/libraries/RadioLib/src/modules/SX126x/SX1261.h new file mode 100644 index 000000000..196cc21bc --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/SX126x/SX1261.h @@ -0,0 +1,53 @@ +#if !defined(_RADIOLIB_SX1261_H) +#define _RADIOLIB_SX1261_H + +#include "../../TypeDef.h" + +#if !RADIOLIB_EXCLUDE_SX126X + +#include "../../Module.h" +#include "SX126x.h" +#include "SX1262.h" + +//RADIOLIB_SX126X_CMD_SET_PA_CONFIG +#define RADIOLIB_SX126X_PA_CONFIG_SX1261 0x01 + +//RADIOLIB_SX126X_REG_VERSION_STRING +#define RADIOLIB_SX1261_CHIP_TYPE "SX1261" + +/*! + \class SX1261 + \brief Derived class for %SX1261 modules. +*/ +class SX1261 : public SX1262 { + public: + /*! + \brief Default constructor. + \param mod Instance of Module that will be used to communicate with the radio. + */ + SX1261(Module* mod); // cppcheck-suppress noExplicitConstructor + + /*! + \brief Sets output power. Allowed values are in range from -17 to 14 dBm. + \param power Output power to be set in dBm. + \returns \ref status_codes + */ + int16_t setOutputPower(int8_t power) override; + + /*! + \brief Check if output power is configurable. + \param power Output power in dBm. + \param clipped Clipped output power value to what is possible within the module's range. + \returns \ref status_codes + */ + int16_t checkOutputPower(int8_t power, int8_t* clipped) override; + +#if !RADIOLIB_GODMODE + private: +#endif + +}; + +#endif + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/SX126x/SX1262.cpp b/software/firmware/source/libraries/RadioLib/src/modules/SX126x/SX1262.cpp new file mode 100644 index 000000000..426c2d054 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/SX126x/SX1262.cpp @@ -0,0 +1,133 @@ +#include "SX1262.h" +#include + +#if !RADIOLIB_EXCLUDE_SX126X + +SX1262::SX1262(Module* mod) : SX126x(mod) { + chipType = RADIOLIB_SX1262_CHIP_TYPE; +} + +int16_t SX1262::begin(float freq, float bw, uint8_t sf, uint8_t cr, uint8_t syncWord, int8_t power, uint16_t preambleLength, float tcxoVoltage, bool useRegulatorLDO) { + // execute common part + int16_t state = SX126x::begin(cr, syncWord, preambleLength, tcxoVoltage, useRegulatorLDO); + RADIOLIB_ASSERT(state); + + // configure publicly accessible settings + state = setSpreadingFactor(sf); + RADIOLIB_ASSERT(state); + + state = setBandwidth(bw); + RADIOLIB_ASSERT(state); + + state = setFrequency(freq); + RADIOLIB_ASSERT(state); + + state = SX126x::fixPaClamping(); + RADIOLIB_ASSERT(state); + + state = setOutputPower(power); + RADIOLIB_ASSERT(state); + + return(state); +} + +int16_t SX1262::beginFSK(float freq, float br, float freqDev, float rxBw, int8_t power, uint16_t preambleLength, float tcxoVoltage, bool useRegulatorLDO) { + // execute common part + int16_t state = SX126x::beginFSK(br, freqDev, rxBw, preambleLength, 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::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, false)); +} + +int16_t SX1262::setFrequency(float freq, bool skipCalibration) { + RADIOLIB_CHECK_RANGE(freq, 150.0, 960.0, RADIOLIB_ERR_INVALID_FREQUENCY); + + // check if we need to recalibrate image + if(!skipCalibration && (fabsf(freq - this->freqMHz) >= RADIOLIB_SX126X_CAL_IMG_FREQ_TRIG_MHZ)) { + int16_t state = this->calibrateImage(freq); + RADIOLIB_ASSERT(state); + } + + // set frequency + return(SX126x::setFrequencyRaw(freq)); +} + +int16_t SX1262::setOutputPower(int8_t power) { + // check if power value is configurable + int16_t state = checkOutputPower(power, NULL); + RADIOLIB_ASSERT(state); + + // get current OCP configuration + uint8_t ocp = 0; + state = readRegister(RADIOLIB_SX126X_REG_OCP_CONFIGURATION, &ocp, 1); + RADIOLIB_ASSERT(state); + + // set PA config + state = SX126x::setPaConfig(0x04, RADIOLIB_SX126X_PA_CONFIG_SX1262); + RADIOLIB_ASSERT(state); + + // set output power with default 200us ramp + state = SX126x::setTxParams(power, RADIOLIB_SX126X_PA_RAMP_200U); + RADIOLIB_ASSERT(state); + + // restore OCP configuration + return(writeRegister(RADIOLIB_SX126X_REG_OCP_CONFIGURATION, &ocp, 1)); +} + +int16_t SX1262::checkOutputPower(int8_t power, int8_t* clipped) { + if(clipped) { + *clipped = RADIOLIB_MAX(-9, RADIOLIB_MIN(22, power)); + } + RADIOLIB_CHECK_RANGE(power, -9, 22, RADIOLIB_ERR_INVALID_OUTPUT_POWER); + return(RADIOLIB_ERR_NONE); +} + +int16_t SX1262::setModem(ModemType_t modem) { + switch(modem) { + case(ModemType_t::RADIOLIB_MODEM_LORA): { + return(this->begin()); + } break; + case(ModemType_t::RADIOLIB_MODEM_FSK): { + return(this->beginFSK()); + } break; + case(ModemType_t::RADIOLIB_MODEM_LRFHSS): { + return(this->beginLRFHSS()); + } break; + default: + return(RADIOLIB_ERR_WRONG_MODEM); + } +} + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/SX126x/SX1262.h b/software/firmware/source/libraries/RadioLib/src/modules/SX126x/SX1262.h new file mode 100644 index 000000000..a7e0b677a --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/SX126x/SX1262.h @@ -0,0 +1,133 @@ +#if !defined(_RADIOLIB_SX1262_H) +#define _RADIOLIB_SX1262_H + +#include "../../TypeDef.h" + +#if !RADIOLIB_EXCLUDE_SX126X + +#include "../../Module.h" +#include "SX126x.h" + +//RADIOLIB_SX126X_CMD_SET_PA_CONFIG +#define RADIOLIB_SX126X_PA_CONFIG_SX1262 0x00 + +//RADIOLIB_SX126X_REG_VERSION_STRING +// Note: this should really be "2", however, it seems that all SX1262 devices report as SX1261 +#define RADIOLIB_SX1262_CHIP_TYPE "SX1261" + +/*! + \class SX1262 + \brief Derived class for %SX1262 modules. +*/ +class SX1262: public SX126x { + public: + /*! + \brief Default constructor. + \param mod Instance of Module that will be used to communicate with the radio. + */ + SX1262(Module* mod); // cppcheck-suppress noExplicitConstructor + + // basic methods + + /*! + \brief Initialization method for LoRa modem. + \param freq Carrier frequency in MHz. Defaults to 434.0 MHz. + \param bw LoRa bandwidth in kHz. Defaults to 125.0 kHz. + \param sf LoRa spreading factor. Defaults to 9. + \param cr LoRa coding rate denominator. Defaults to 7 (coding rate 4/7). + \param syncWord 1-byte LoRa sync word. Defaults to RADIOLIB_SX126X_SYNC_WORD_PRIVATE (0x12). + \param power Output power in dBm. Defaults to 10 dBm. + \param preambleLength LoRa preamble length in symbols. Defaults to 8 symbols. + \param tcxoVoltage TCXO reference voltage to be set on DIO3. 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 begin(float freq = 434.0, float bw = 125.0, uint8_t sf = 9, uint8_t cr = 7, uint8_t syncWord = RADIOLIB_SX126X_SYNC_WORD_PRIVATE, int8_t power = 10, uint16_t preambleLength = 8, float tcxoVoltage = 1.6, bool useRegulatorLDO = false); + + /*! + \brief Initialization method for FSK modem. + \param freq Carrier frequency in MHz. Defaults to 434.0 MHz. + \param br FSK bit rate in kbps. Defaults to 4.8 kbps. + \param freqDev Frequency deviation from carrier frequency in kHz. Defaults to 5.0 kHz. + \param rxBw Receiver bandwidth in kHz. Defaults to 156.2 kHz. + \param power Output power in dBm. Defaults to 10 dBm. + \param preambleLength FSK preamble length in bits. Defaults to 16 bits. + \param tcxoVoltage TCXO reference voltage to be set on DIO3. 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 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 + + /*! + \brief Sets carrier frequency. Allowed values are in range from 150.0 to 960.0 MHz. + Will automatically perform image calibration if the frequency changes by + more than RADIOLIB_SX126X_CAL_IMG_FREQ_TRIG MHz. + \param freq Carrier frequency to be set in MHz. + \returns \ref status_codes + */ + int16_t setFrequency(float freq) override; + + /*! + \brief Sets carrier frequency. Allowed values are in range from 150.0 to 960.0 MHz. + Will automatically perform image calibration if the frequency changes by + more than RADIOLIB_SX126X_CAL_IMG_FREQ_TRIG_MHZ. + \param freq Carrier frequency to be set in MHz. + \param skipCalibration Skip automated image calibration. + \returns \ref status_codes + */ + int16_t setFrequency(float freq, bool skipCalibration); + + /*! + \brief Sets output power. Allowed values are in range from -9 to 22 dBm. + This method is virtual to allow override from the SX1261 class. + \param power Output power to be set in dBm. + \returns \ref status_codes + */ + virtual int16_t setOutputPower(int8_t power) override; + + /*! + \brief Check if output power is configurable. + \param power Output power in dBm. + \param clipped Clipped output power value to what is possible within the module's range. + \returns \ref status_codes + */ + int16_t checkOutputPower(int8_t power, int8_t* clipped) override; + + /*! + \brief Set modem for the radio to use. Will perform full reset and reconfigure the radio + using its default parameters. + \param modem Modem type to set - FSK, LoRa or LR-FHSS. + \returns \ref status_codes + */ + int16_t setModem(ModemType_t modem) override; + +#if !RADIOLIB_GODMODE + private: +#endif + +}; + +#endif + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/SX126x/SX1268.cpp b/software/firmware/source/libraries/RadioLib/src/modules/SX126x/SX1268.cpp new file mode 100644 index 000000000..7de82a224 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/SX126x/SX1268.cpp @@ -0,0 +1,134 @@ +#include "SX1268.h" +#include + +#if !RADIOLIB_EXCLUDE_SX126X + +SX1268::SX1268(Module* mod) : SX126x(mod) { + chipType = RADIOLIB_SX1268_CHIP_TYPE; +} + +int16_t SX1268::begin(float freq, float bw, uint8_t sf, uint8_t cr, uint8_t syncWord, int8_t power, uint16_t preambleLength, float tcxoVoltage, bool useRegulatorLDO) { + // execute common part + int16_t state = SX126x::begin(cr, syncWord, preambleLength, tcxoVoltage, useRegulatorLDO); + RADIOLIB_ASSERT(state); + + // configure publicly accessible settings + state = setFrequency(freq); + RADIOLIB_ASSERT(state); + + state = setSpreadingFactor(sf); + RADIOLIB_ASSERT(state); + + state = setBandwidth(bw); + RADIOLIB_ASSERT(state); + + state = setOutputPower(power); + RADIOLIB_ASSERT(state); + + state = SX126x::fixPaClamping(); + RADIOLIB_ASSERT(state); + + return(state); +} + +int16_t SX1268::beginFSK(float freq, float br, float freqDev, float rxBw, int8_t power, uint16_t preambleLength, float tcxoVoltage, bool useRegulatorLDO) { + // execute common part + int16_t state = SX126x::beginFSK(br, freqDev, rxBw, preambleLength, tcxoVoltage, useRegulatorLDO); + RADIOLIB_ASSERT(state); + + // configure publicly accessible settings + state = setFrequency(freq); + RADIOLIB_ASSERT(state); + + state = setOutputPower(power); + RADIOLIB_ASSERT(state); + + state = SX126x::fixPaClamping(); + RADIOLIB_ASSERT(state); + + 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, false)); +} + +/// \todo integers only (all modules - frequency, data rate, bandwidth etc.) +int16_t SX1268::setFrequency(float freq, bool skipCalibration) { + RADIOLIB_CHECK_RANGE(freq, 410.0, 810.0, RADIOLIB_ERR_INVALID_FREQUENCY); + + // check if we need to recalibrate image + if(!skipCalibration && (fabsf(freq - this->freqMHz) >= RADIOLIB_SX126X_CAL_IMG_FREQ_TRIG_MHZ)) { + int16_t state = this->calibrateImage(freq); + RADIOLIB_ASSERT(state); + } + + // set frequency + return(SX126x::setFrequencyRaw(freq)); +} + +int16_t SX1268::setOutputPower(int8_t power) { + // check if power value is configurable + int16_t state = checkOutputPower(power, NULL); + RADIOLIB_ASSERT(state); + + // get current OCP configuration + uint8_t ocp = 0; + state = readRegister(RADIOLIB_SX126X_REG_OCP_CONFIGURATION, &ocp, 1); + RADIOLIB_ASSERT(state); + + // set PA config + state = SX126x::setPaConfig(0x04, RADIOLIB_SX126X_PA_CONFIG_SX1268); + RADIOLIB_ASSERT(state); + + // set output power with default 200us ramp + state = SX126x::setTxParams(power, RADIOLIB_SX126X_PA_RAMP_200U); + RADIOLIB_ASSERT(state); + + // restore OCP configuration + return(writeRegister(RADIOLIB_SX126X_REG_OCP_CONFIGURATION, &ocp, 1)); +} + +int16_t SX1268::checkOutputPower(int8_t power, int8_t* clipped) { + if(clipped) { + *clipped = RADIOLIB_MAX(-9, RADIOLIB_MIN(22, power)); + } + RADIOLIB_CHECK_RANGE(power, -9, 22, RADIOLIB_ERR_INVALID_OUTPUT_POWER); + return(RADIOLIB_ERR_NONE); +} + +int16_t SX1268::setModem(ModemType_t modem) { + switch(modem) { + case(ModemType_t::RADIOLIB_MODEM_LORA): { + return(this->begin()); + } break; + case(ModemType_t::RADIOLIB_MODEM_FSK): { + return(this->beginFSK()); + } break; + case(ModemType_t::RADIOLIB_MODEM_LRFHSS): { + return(this->beginLRFHSS()); + } break; + default: + return(RADIOLIB_ERR_WRONG_MODEM); + } +} + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/SX126x/SX1268.h b/software/firmware/source/libraries/RadioLib/src/modules/SX126x/SX1268.h new file mode 100644 index 000000000..c34bec8dd --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/SX126x/SX1268.h @@ -0,0 +1,131 @@ +#if !defined(_RADIOLIB_SX1268_H) +#define _RADIOLIB_SX1268_H + +#include "../../TypeDef.h" + +#if !RADIOLIB_EXCLUDE_SX126X + +#include "../../Module.h" +#include "SX126x.h" + +//RADIOLIB_SX126X_CMD_SET_PA_CONFIG +#define RADIOLIB_SX126X_PA_CONFIG_SX1268 0x00 + +//RADIOLIB_SX126X_REG_VERSION_STRING +#define RADIOLIB_SX1268_CHIP_TYPE "SX1268" + +/*! + \class SX1268 + \brief Derived class for %SX1268 modules. +*/ +class SX1268: public SX126x { + public: + /*! + \brief Default constructor. + \param mod Instance of Module that will be used to communicate with the radio. + */ + SX1268(Module* mod); // cppcheck-suppress noExplicitConstructor + + // basic methods + + /*! + \brief Initialization method for LoRa modem. + \param freq Carrier frequency in MHz. Defaults to 434.0 MHz. + \param bw LoRa bandwidth in kHz. Defaults to 125.0 kHz. + \param sf LoRa spreading factor. Defaults to 9. + \param cr LoRa coding rate denominator. Defaults to 7 (coding rate 4/7). + \param syncWord 1-byte LoRa sync word. Defaults to RADIOLIB_SX126X_SYNC_WORD_PRIVATE (0x12). + \param power Output power in dBm. Defaults to 10 dBm. + \param preambleLength LoRa preamble length in symbols. Defaults to 8 symbols. + \param tcxoVoltage TCXO reference voltage to be set on DIO3. 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 begin(float freq = 434.0, float bw = 125.0, uint8_t sf = 9, uint8_t cr = 7, uint8_t syncWord = RADIOLIB_SX126X_SYNC_WORD_PRIVATE, int8_t power = 10, uint16_t preambleLength = 8, float tcxoVoltage = 1.6, bool useRegulatorLDO = false); + + /*! + \brief Initialization method for FSK modem. + \param freq Carrier frequency in MHz. Defaults to 434.0 MHz. + \param br FSK bit rate in kbps. Defaults to 4.8 kbps. + \param freqDev Frequency deviation from carrier frequency in kHz. Defaults to 5.0 kHz. + \param rxBw Receiver bandwidth in kHz. Defaults to 156.2 kHz. + \param power Output power in dBm. Defaults to 10 dBm. + \param preambleLength FSK preamble length in bits. Defaults to 16 bits. + \param tcxoVoltage TCXO reference voltage to be set on DIO3. 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 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 + + /*! + \brief Sets carrier frequency. Allowed values are in range from 410.0 to 810.0 MHz. + Will automatically perform image calibration if the frequency changes by + more than RADIOLIB_SX126X_CAL_IMG_FREQ_TRIG MHz. + \param freq Carrier frequency to be set in MHz. + \returns \ref status_codes + */ + int16_t setFrequency(float freq) override; + + /*! + \brief Sets carrier frequency. Allowed values are in range from 150.0 to 960.0 MHz. + Will automatically perform image calibration if the frequency changes by + more than RADIOLIB_SX126X_CAL_IMG_FREQ_TRIG_MHZ. + \param freq Carrier frequency to be set in MHz. + \param skipCalibration Skip automated image calibration. + \returns \ref status_codes + */ + int16_t setFrequency(float freq, bool skipCalibration); + + /*! + \brief Sets output power. Allowed values are in range from -9 to 22 dBm. + \param power Output power to be set in dBm. + \returns \ref status_codes + */ + int16_t setOutputPower(int8_t power) override; + + /*! + \brief Check if output power is configurable. + \param power Output power in dBm. + \param clipped Clipped output power value to what is possible within the module's range. + \returns \ref status_codes + */ + int16_t checkOutputPower(int8_t power, int8_t* clipped) override; + + /*! + \brief Set modem for the radio to use. Will perform full reset and reconfigure the radio + using its default parameters. + \param modem Modem type to set - FSK, LoRa or LR-FHSS. + \returns \ref status_codes + */ + int16_t setModem(ModemType_t modem) override; + +#if !RADIOLIB_GODMODE + private: +#endif + +}; + +#endif + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/SX126x/SX126x.cpp b/software/firmware/source/libraries/RadioLib/src/modules/SX126x/SX126x.cpp new file mode 100644 index 000000000..2670a2038 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/SX126x/SX126x.cpp @@ -0,0 +1,2419 @@ +#include "SX126x.h" +#include +#include +#if !RADIOLIB_EXCLUDE_SX126X + +SX126x::SX126x(Module* mod) : PhysicalLayer(RADIOLIB_SX126X_FREQUENCY_STEP_SIZE, RADIOLIB_SX126X_MAX_PACKET_LENGTH) { + this->mod = mod; + this->XTAL = false; + this->standbyXOSC = false; + this->irqMap[RADIOLIB_IRQ_TX_DONE] = RADIOLIB_SX126X_IRQ_TX_DONE; + this->irqMap[RADIOLIB_IRQ_RX_DONE] = RADIOLIB_SX126X_IRQ_RX_DONE; + this->irqMap[RADIOLIB_IRQ_PREAMBLE_DETECTED] = RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED; + this->irqMap[RADIOLIB_IRQ_SYNC_WORD_VALID] = RADIOLIB_SX126X_IRQ_SYNC_WORD_VALID; + this->irqMap[RADIOLIB_IRQ_HEADER_VALID] = RADIOLIB_SX126X_IRQ_HEADER_VALID; + this->irqMap[RADIOLIB_IRQ_HEADER_ERR] = RADIOLIB_SX126X_IRQ_HEADER_ERR; + this->irqMap[RADIOLIB_IRQ_CRC_ERR] = RADIOLIB_SX126X_IRQ_CRC_ERR; + this->irqMap[RADIOLIB_IRQ_CAD_DONE] = RADIOLIB_SX126X_IRQ_CAD_DONE; + this->irqMap[RADIOLIB_IRQ_CAD_DETECTED] = RADIOLIB_SX126X_IRQ_CAD_DETECTED; + this->irqMap[RADIOLIB_IRQ_TIMEOUT] = RADIOLIB_SX126X_IRQ_TIMEOUT; +} + +int16_t SX126x::begin(uint8_t cr, uint8_t syncWord, uint16_t preambleLength, float tcxoVoltage, bool useRegulatorLDO) { + // BW in kHz and SF are required in order to calculate LDRO for setModulationParams + // set the defaults, this will get overwritten later anyway + this->bandwidthKhz = 500.0; + this->spreadingFactor = 9; + + // initialize configuration variables (will be overwritten during public settings configuration) + this->bandwidth = RADIOLIB_SX126X_LORA_BW_500_0; // initialized to 500 kHz, since lower values will interfere with LLCC68 + this->codingRate = RADIOLIB_SX126X_LORA_CR_4_7; + this->ldrOptimize = 0x00; + this->crcTypeLoRa = RADIOLIB_SX126X_LORA_CRC_ON; + this->preambleLengthLoRa = preambleLength; + this->tcxoDelay = 0; + this->headerType = RADIOLIB_SX126X_LORA_HEADER_EXPLICIT; + this->implicitLen = 0xFF; + + // set module properties and perform initial setup + int16_t state = this->modSetup(tcxoVoltage, useRegulatorLDO, RADIOLIB_SX126X_PACKET_TYPE_LORA); + RADIOLIB_ASSERT(state); + + // configure publicly accessible settings + state = setCodingRate(cr); + RADIOLIB_ASSERT(state); + + state = setSyncWord(syncWord); + RADIOLIB_ASSERT(state); + + state = setPreambleLength(preambleLength); + 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); + + state = setCRC(2); + RADIOLIB_ASSERT(state); + + state = invertIQ(false); + RADIOLIB_ASSERT(state); + + return(state); +} + +int16_t SX126x::beginFSK(float br, float freqDev, float rxBw, uint16_t preambleLength, float tcxoVoltage, bool useRegulatorLDO) { + // initialize configuration variables (will be overwritten during public settings configuration) + this->bitRate = 21333; // 48.0 kbps + this->frequencyDev = 52428; // 50.0 kHz + this->rxBandwidth = RADIOLIB_SX126X_GFSK_RX_BW_156_2; + this->rxBandwidthKhz = 156.2; + this->pulseShape = RADIOLIB_SX126X_GFSK_FILTER_GAUSS_0_5; + this->crcTypeFSK = RADIOLIB_SX126X_GFSK_CRC_2_BYTE_INV; // CCITT CRC configuration + this->preambleLengthFSK = preambleLength; + this->addrComp = RADIOLIB_SX126X_GFSK_ADDRESS_FILT_OFF; + + // set module properties and perform initial setup + int16_t state = this->modSetup(tcxoVoltage, useRegulatorLDO, RADIOLIB_SX126X_PACKET_TYPE_GFSK); + RADIOLIB_ASSERT(state); + + // configure publicly accessible settings + state = setBitRate(br); + RADIOLIB_ASSERT(state); + + state = setFrequencyDeviation(freqDev); + RADIOLIB_ASSERT(state); + + state = setRxBandwidth(rxBw); + RADIOLIB_ASSERT(state); + + state = setCurrentLimit(60.0); + RADIOLIB_ASSERT(state); + + state = setPreambleLength(preambleLength); + RADIOLIB_ASSERT(state); + + // set publicly accessible settings that are not a part of begin method + uint8_t sync[] = {0x12, 0xAD}; + state = setSyncWord(sync, 2); + RADIOLIB_ASSERT(state); + + state = setDataShaping(RADIOLIB_SHAPING_NONE); + RADIOLIB_ASSERT(state); + + state = setEncoding(RADIOLIB_ENCODING_NRZ); + RADIOLIB_ASSERT(state); + + state = variablePacketLengthMode(RADIOLIB_SX126X_MAX_PACKET_LENGTH); + RADIOLIB_ASSERT(state); + + state = setCRC(2); + RADIOLIB_ASSERT(state); + + state = setDio2AsRfSwitch(true); + RADIOLIB_ASSERT(state); + + 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); + this->mod->hal->digitalWrite(this->mod->getRst(), this->mod->hal->GpioLevelLow); + this->mod->hal->delay(1); + this->mod->hal->digitalWrite(this->mod->getRst(), this->mod->hal->GpioLevelHigh); + + // return immediately when verification is disabled + if(!verify) { + return(RADIOLIB_ERR_NONE); + } + + // set mode to standby - SX126x often refuses first few commands after reset + RadioLibTime_t start = this->mod->hal->millis(); + while(true) { + // try to set mode to standby + int16_t state = standby(); + if(state == RADIOLIB_ERR_NONE) { + // standby command successful + return(RADIOLIB_ERR_NONE); + } + + // standby command failed, check timeout and try again + if(this->mod->hal->millis() - start >= 1000) { + // timed out, possibly incorrect wiring + return(state); + } + + // wait a bit to not spam the module + this->mod->hal->delay(10); + } +} + +int16_t SX126x::transmit(const uint8_t* data, size_t len, uint8_t addr) { + // set mode to standby + int16_t state = standby(); + RADIOLIB_ASSERT(state); + + // check packet length + if(len > RADIOLIB_SX126X_MAX_PACKET_LENGTH) { + return(RADIOLIB_ERR_PACKET_TOO_LONG); + } + + // calculate timeout in ms (500% of expected time-on-air) + RadioLibTime_t timeout = (getTimeOnAir(len) * 5) / 1000; + RADIOLIB_DEBUG_BASIC_PRINTLN("Timeout in %lu ms", timeout); + + // start transmission + state = startTransmit(data, len, addr); + RADIOLIB_ASSERT(state); + + // wait for packet transmission or timeout + uint8_t modem = getPacketType(); + RadioLibTime_t start = this->mod->hal->millis(); + 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 + RadioLibTime_t elapsed = this->mod->hal->millis() - start; + this->dataRateMeasured = (len*8.0)/((float)elapsed/1000.0); + + return(finishTransmit()); +} + +int16_t SX126x::receive(uint8_t* data, size_t len) { + // set mode to standby + int16_t state = standby(); + RADIOLIB_ASSERT(state); + + RadioLibTime_t timeout = 0; + + // get currently active modem + uint8_t modem = getPacketType(); + if(modem == RADIOLIB_SX126X_PACKET_TYPE_LORA) { + // calculate timeout (100 LoRa symbols, the default for SX127x series) + float symbolLength = (float)(uint32_t(1) << this->spreadingFactor) / (float)this->bandwidthKhz; + timeout = (RadioLibTime_t)(symbolLength * 100.0); + + } else if(modem == RADIOLIB_SX126X_PACKET_TYPE_GFSK) { + // calculate timeout (500 % of expected time-one-air) + size_t maxLen = len; + if(len == 0) { + maxLen = 0xFF; + } + float brBps = ((float)(RADIOLIB_SX126X_CRYSTAL_FREQ) * 1000000.0 * 32.0) / (float)this->bitRate; + timeout = (RadioLibTime_t)(((maxLen * 8.0) / brBps) * 1000.0 * 5.0); + + } else { + return(RADIOLIB_ERR_UNKNOWN); + + } + + RADIOLIB_DEBUG_BASIC_PRINTLN("Timeout in %lu ms", timeout); + + // start reception + uint32_t timeoutValue = (uint32_t)(((float)timeout * 1000.0) / 15.625); + state = startReceive(timeoutValue); + RADIOLIB_ASSERT(state); + + // wait for packet reception or timeout + bool softTimeout = false; + RadioLibTime_t start = this->mod->hal->millis(); + while(!this->mod->hal->digitalRead(this->mod->getIrq())) { + this->mod->hal->yield(); + // safety check, the timeout should be done by the radio + if(this->mod->hal->millis() - start > timeout) { + softTimeout = true; + break; + } + } + + // if it was a timeout, this will return an error code + state = standby(); + if((state != RADIOLIB_ERR_NONE) && (state != RADIOLIB_ERR_SPI_CMD_TIMEOUT)) { + return(state); + } + + // check whether this was a timeout or not + if((getIrqFlags() & RADIOLIB_SX126X_IRQ_TIMEOUT) || softTimeout) { + standby(); + fixImplicitTimeout(); + clearIrqStatus(); + return(RADIOLIB_ERR_RX_TIMEOUT); + } + + // fix timeout in implicit LoRa mode + if(((this->headerType == RADIOLIB_SX126X_LORA_HEADER_IMPLICIT) && (getPacketType() == RADIOLIB_SX126X_PACKET_TYPE_LORA))) { + state = fixImplicitTimeout(); + RADIOLIB_ASSERT(state); + } + + // read the received data + return(readData(data, len)); +} + +int16_t SX126x::transmitDirect(uint32_t frf) { + // set RF switch (if present) + this->mod->setRfSwitchState(this->txMode); + + // user requested to start transmitting immediately (required for RTTY) + int16_t state = RADIOLIB_ERR_NONE; + if(frf != 0) { + state = setRfFrequency(frf); + } + RADIOLIB_ASSERT(state); + + // direct mode activation intentionally skipped here, as it seems to lead to much worse results + uint8_t data[] = { RADIOLIB_SX126X_CMD_NOP }; + return(this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_SET_TX_CONTINUOUS_WAVE, data, 1)); +} + +int16_t SX126x::receiveDirect() { + // set RF switch (if present) + this->mod->setRfSwitchState(Module::MODE_RX); + + // SX126x is unable to output received data directly + return(RADIOLIB_ERR_UNKNOWN); +} + +int16_t SX126x::directMode() { + // check modem + if(getPacketType() != RADIOLIB_SX126X_PACKET_TYPE_GFSK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // set mode to standby + int16_t state = standby(); + RADIOLIB_ASSERT(state); + + // disable DIO2 RF switch + state = setDio2AsRfSwitch(false); + RADIOLIB_ASSERT(state); + + // set DIO2 to clock output and DIO3 to data input + // this is done exclusively by writing magic values to even more magic registers + state = this->mod->SPIsetRegValue(RADIOLIB_SX126X_REG_TX_BITBANG_ENABLE_1, RADIOLIB_SX126X_TX_BITBANG_1_ENABLED, 6, 4); + RADIOLIB_ASSERT(state); + state = this->mod->SPIsetRegValue(RADIOLIB_SX126X_REG_TX_BITBANG_ENABLE_0, RADIOLIB_SX126X_TX_BITBANG_0_ENABLED, 3, 0); + RADIOLIB_ASSERT(state); + state = this->mod->SPIsetRegValue(RADIOLIB_SX126X_REG_DIOX_OUT_ENABLE, RADIOLIB_SX126X_DIO3_OUT_DISABLED, 3, 3); + RADIOLIB_ASSERT(state); + state = this->mod->SPIsetRegValue(RADIOLIB_SX126X_REG_DIOX_IN_ENABLE, RADIOLIB_SX126X_DIO3_IN_ENABLED, 3, 3); + RADIOLIB_ASSERT(state); + + // enable TxDone interrupt + state = setDioIrqParams(RADIOLIB_SX126X_IRQ_TX_DONE, RADIOLIB_SX126X_IRQ_TX_DONE); + RADIOLIB_ASSERT(state); + + // set preamble length to the maximum to prevent SX126x from exiting Tx mode for a while + state = setPreambleLength(0xFFFF); + RADIOLIB_ASSERT(state); + + return(state); +} + +int16_t SX126x::packetMode() { + // set mode to standby + int16_t state = standby(); + RADIOLIB_ASSERT(state); + + // set preamble length to the default + state = setPreambleLength(16); + RADIOLIB_ASSERT(state); + + // disable TxDone interrupt + state = setDioIrqParams(RADIOLIB_SX126X_IRQ_NONE, RADIOLIB_SX126X_IRQ_NONE); + RADIOLIB_ASSERT(state); + + // restore the magic registers + state = this->mod->SPIsetRegValue(RADIOLIB_SX126X_REG_DIOX_IN_ENABLE, RADIOLIB_SX126X_DIO3_IN_DISABLED, 3, 3); + RADIOLIB_ASSERT(state); + state = this->mod->SPIsetRegValue(RADIOLIB_SX126X_REG_DIOX_OUT_ENABLE, RADIOLIB_SX126X_DIO3_OUT_ENABLED, 3, 3); + RADIOLIB_ASSERT(state); + state = this->mod->SPIsetRegValue(RADIOLIB_SX126X_REG_TX_BITBANG_ENABLE_0, RADIOLIB_SX126X_TX_BITBANG_0_DISABLED, 3, 0); + RADIOLIB_ASSERT(state); + state = this->mod->SPIsetRegValue(RADIOLIB_SX126X_REG_TX_BITBANG_ENABLE_1, RADIOLIB_SX126X_TX_BITBANG_1_DISABLED, 6, 4); + RADIOLIB_ASSERT(state); + + // enable DIO2 RF switch + state = setDio2AsRfSwitch(true); + RADIOLIB_ASSERT(state); + + return(state); +} + +int16_t SX126x::scanChannel() { + ChannelScanConfig_t cfg = { + .cad = { + .symNum = RADIOLIB_SX126X_CAD_PARAM_DEFAULT, + .detPeak = RADIOLIB_SX126X_CAD_PARAM_DEFAULT, + .detMin = RADIOLIB_SX126X_CAD_PARAM_DEFAULT, + .exitMode = RADIOLIB_SX126X_CAD_PARAM_DEFAULT, + .timeout = 0, + .irqFlags = RADIOLIB_IRQ_CAD_DEFAULT_FLAGS, + .irqMask = RADIOLIB_IRQ_CAD_DEFAULT_MASK, + }, + }; + return(this->scanChannel(cfg)); +} + +int16_t SX126x::scanChannel(const ChannelScanConfig_t &config) { + // set mode to CAD + int state = startChannelScan(config); + RADIOLIB_ASSERT(state); + + // wait for channel activity detected or timeout + while(!this->mod->hal->digitalRead(this->mod->getIrq())) { + this->mod->hal->yield(); + } + + // check CAD result + return(getChannelScanResult()); +} + +int16_t SX126x::sleep() { + return(SX126x::sleep(true)); +} + +int16_t SX126x::sleep(bool retainConfig) { + // set RF switch (if present) + this->mod->setRfSwitchState(Module::MODE_IDLE); + + uint8_t sleepMode = RADIOLIB_SX126X_SLEEP_START_WARM | RADIOLIB_SX126X_SLEEP_RTC_OFF; + if(!retainConfig) { + sleepMode = RADIOLIB_SX126X_SLEEP_START_COLD | RADIOLIB_SX126X_SLEEP_RTC_OFF; + } + int16_t state = this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_SET_SLEEP, &sleepMode, 1, false, false); + + // wait for SX126x to safely enter sleep mode + this->mod->hal->delay(1); + + return(state); +} + +int16_t SX126x::standby() { + return(SX126x::standby(this->standbyXOSC ? RADIOLIB_SX126X_STANDBY_XOSC : RADIOLIB_SX126X_STANDBY_RC)); +} + +int16_t SX126x::standby(uint8_t mode, bool wakeup) { + // set RF switch (if present) + this->mod->setRfSwitchState(Module::MODE_IDLE); + + if(wakeup) { + // pull NSS low to wake up + this->mod->hal->digitalWrite(this->mod->getCs(), this->mod->hal->GpioLevelLow); + } + + uint8_t data[] = { mode }; + return(this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_SET_STANDBY, data, 1)); +} + +void SX126x::setDio1Action(void (*func)(void)) { + this->mod->hal->attachInterrupt(this->mod->hal->pinToInterrupt(this->mod->getIrq()), func, this->mod->hal->GpioInterruptRising); +} + +void SX126x::clearDio1Action() { + this->mod->hal->detachInterrupt(this->mod->hal->pinToInterrupt(this->mod->getIrq())); +} + +void SX126x::setPacketReceivedAction(void (*func)(void)) { + this->setDio1Action(func); +} + +void SX126x::clearPacketReceivedAction() { + this->clearDio1Action(); +} + +void SX126x::setPacketSentAction(void (*func)(void)) { + this->setDio1Action(func); +} + +void SX126x::clearPacketSentAction() { + this->clearDio1Action(); +} + +void SX126x::setChannelScanAction(void (*func)(void)) { + this->setDio1Action(func); +} + +void SX126x::clearChannelScanAction() { + this->clearDio1Action(); +} + +// Datasheet defins typical times until busy goes low. Most are < 200us, +// except when waking up from sleep, which typically takes 3500us. Since +// we cannot know here if we are in sleep, we'll have to assume we are. +// Since 3500 is typical, not maximum, wait a bit more than that. +static unsigned long MAX_BUSY_TIME = 5000; + +int16_t SX126x::startTransmit(const uint8_t* data, size_t len, uint8_t addr) { + // check packet length + if(len > RADIOLIB_SX126X_MAX_PACKET_LENGTH) { + return(RADIOLIB_ERR_PACKET_TOO_LONG); + } + + // maximum packet length is decreased by 1 when address filtering is active + if((this->addrComp != RADIOLIB_SX126X_GFSK_ADDRESS_FILT_OFF) && (len > RADIOLIB_SX126X_MAX_PACKET_LENGTH - 1)) { + return(RADIOLIB_ERR_PACKET_TOO_LONG); + } + + // set packet Length + int16_t state = RADIOLIB_ERR_NONE; + uint8_t modem = getPacketType(); + if(modem == RADIOLIB_SX126X_PACKET_TYPE_LORA) { + 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); + + // address is taken from the register + if(this->addrComp != RADIOLIB_SX126X_GFSK_ADDRESS_FILT_OFF) { + RADIOLIB_ASSERT(state); + state = writeRegister(RADIOLIB_SX126X_REG_NODE_ADDRESS, &addr, 1); + } + + } else if(modem != RADIOLIB_SX126X_PACKET_TYPE_LR_FHSS) { + return(RADIOLIB_ERR_UNKNOWN); + + } + RADIOLIB_ASSERT(state); + + // set DIO mapping + 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 + state = setBufferBaseAddress(); + RADIOLIB_ASSERT(state); + + // write packet to buffer + if(modem != RADIOLIB_SX126X_PACKET_TYPE_LR_FHSS) { + state = writeBuffer(const_cast(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(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 + state = clearIrqStatus(); + RADIOLIB_ASSERT(state); + + // fix sensitivity + state = fixSensitivity(); + RADIOLIB_ASSERT(state); + + // set RF switch (if present) + this->mod->setRfSwitchState(this->txMode); + + // start transmission + state = setTx(RADIOLIB_SX126X_TX_TIMEOUT_NONE); + RADIOLIB_ASSERT(state); + + if (this->mod->getGpio() == RADIOLIB_NC) { + // TODO: We could probably keep some state so we know the chip + // is in sleep, since otherwise the delay can be much shorter. + // Also, all delays after commands (rather than waking up from + // sleep) are measured from the *end* of the previous SPI + // transaction, so we could wait shorter if we remember when + // that was. + this->mod->hal->delayMicroseconds(MAX_BUSY_TIME); + } else { + // wait for BUSY to go low (= PA ramp up done) + while(this->mod->hal->digitalRead(this->mod->getGpio())) { + this->mod->hal->yield(); + } + } + + return(state); +} + +int16_t SX126x::finishTransmit() { + // clear interrupt flags + int16_t state = clearIrqStatus(); + RADIOLIB_ASSERT(state); + + // restore the original node address + if(getPacketType() == RADIOLIB_SX126X_PACKET_TYPE_GFSK) { + state = writeRegister(RADIOLIB_SX126X_REG_NODE_ADDRESS, &this->nodeAddr, 1); + RADIOLIB_ASSERT(state); + } + + // set mode to standby to disable transmitter/RF switch + return(standby()); +} + +int16_t SX126x::startReceive() { + return(this->startReceive(RADIOLIB_SX126X_RX_TIMEOUT_INF, RADIOLIB_IRQ_RX_DEFAULT_FLAGS, RADIOLIB_IRQ_RX_DEFAULT_MASK, 0)); +} + +int16_t SX126x::startReceive(uint32_t timeout, RadioLibIrqFlags_t irqFlags, RadioLibIrqFlags_t irqMask, size_t len) { + (void)len; + int16_t state = startReceiveCommon(timeout, irqFlags, irqMask); + RADIOLIB_ASSERT(state); + + // set RF switch (if present) + this->mod->setRfSwitchState(Module::MODE_RX); + + // set mode to receive + state = setRx(timeout); + + return(state); +} + +int16_t SX126x::startReceiveDutyCycle(uint32_t rxPeriod, uint32_t sleepPeriod, RadioLibIrqFlags_t irqFlags, RadioLibIrqFlags_t irqMask) { + // datasheet claims time to go to sleep is ~500us, same to wake up, compensate for that with 1 ms + TCXO delay + uint32_t transitionTime = this->tcxoDelay + 1000; + sleepPeriod -= transitionTime; + + // divide by 15.625 + uint32_t rxPeriodRaw = (rxPeriod * 8) / 125; + uint32_t sleepPeriodRaw = (sleepPeriod * 8) / 125; + + // check 24 bit limit and zero value (likely not intended) + if((rxPeriodRaw & 0xFF000000) || (rxPeriodRaw == 0)) { + return(RADIOLIB_ERR_INVALID_RX_PERIOD); + } + + // this check of the high byte also catches underflow when we subtracted transitionTime + if((sleepPeriodRaw & 0xFF000000) || (sleepPeriodRaw == 0)) { + return(RADIOLIB_ERR_INVALID_SLEEP_PERIOD); + } + + int16_t state = startReceiveCommon(RADIOLIB_SX126X_RX_TIMEOUT_INF, irqFlags, irqMask); + RADIOLIB_ASSERT(state); + + uint8_t data[6] = {(uint8_t)((rxPeriodRaw >> 16) & 0xFF), (uint8_t)((rxPeriodRaw >> 8) & 0xFF), (uint8_t)(rxPeriodRaw & 0xFF), + (uint8_t)((sleepPeriodRaw >> 16) & 0xFF), (uint8_t)((sleepPeriodRaw >> 8) & 0xFF), (uint8_t)(sleepPeriodRaw & 0xFF)}; + return(this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_SET_RX_DUTY_CYCLE, data, 6)); +} + +int16_t SX126x::startReceiveDutyCycleAuto(uint16_t senderPreambleLength, uint16_t minSymbols, RadioLibIrqFlags_t irqFlags, RadioLibIrqFlags_t irqMask) { + if(senderPreambleLength == 0) { + senderPreambleLength = this->preambleLengthLoRa; + } + + // worst case is that the sender starts transmitting when we're just less than minSymbols from going back to sleep. + // in this case, we don't catch minSymbols before going to sleep, + // so we must be awake for at least that long before the sender stops transmitting. + uint16_t sleepSymbols = senderPreambleLength - 2 * minSymbols; + + // if we're not to sleep at all, just use the standard startReceive. + if(2 * minSymbols > senderPreambleLength) { + return(startReceive(RADIOLIB_SX126X_RX_TIMEOUT_INF, irqFlags, irqMask)); + } + + uint32_t symbolLength = ((uint32_t)(10 * 1000) << this->spreadingFactor) / (10 * this->bandwidthKhz); + uint32_t sleepPeriod = symbolLength * sleepSymbols; + RADIOLIB_DEBUG_BASIC_PRINTLN("Auto sleep period: %lu", (long unsigned int)sleepPeriod); + + // when the unit detects a preamble, it starts a timer that will timeout if it doesn't receive a header in time. + // the duration is sleepPeriod + 2 * wakePeriod. + // The sleepPeriod doesn't take into account shutdown and startup time for the unit (~1ms) + // We need to ensure that the timeout is longer than senderPreambleLength. + // So we must satisfy: wakePeriod > (preamblePeriod - (sleepPeriod - 1000)) / 2. (A) + // we also need to ensure the unit is awake to see at least minSymbols. (B) + uint32_t wakePeriod = RADIOLIB_MAX( + (symbolLength * (senderPreambleLength + 1) - (sleepPeriod - 1000)) / 2, // (A) + symbolLength * (minSymbols + 1)); //(B) + RADIOLIB_DEBUG_BASIC_PRINTLN("Auto wake period: %lu", (long unsigned int)wakePeriod); + + // If our sleep period is shorter than our transition time, just use the standard startReceive + if(sleepPeriod < this->tcxoDelay + 1016) { + return(startReceive(RADIOLIB_SX126X_RX_TIMEOUT_INF, irqFlags, irqMask)); + } + + return(startReceiveDutyCycle(wakePeriod, sleepPeriod, irqFlags, irqMask)); +} + +int16_t SX126x::startReceiveCommon(uint32_t timeout, RadioLibIrqFlags_t irqFlags, RadioLibIrqFlags_t irqMask) { + // ensure we are in standby + int16_t state = standby(); + RADIOLIB_ASSERT(state); + + // set DIO mapping + if(timeout != RADIOLIB_SX126X_RX_TIMEOUT_INF) { + irqMask |= (1UL << RADIOLIB_IRQ_TIMEOUT); + } + state = setDioIrqParams(getIrqMapped(irqFlags), getIrqMapped(irqMask)); + RADIOLIB_ASSERT(state); + + // set buffer pointers + state = setBufferBaseAddress(); + RADIOLIB_ASSERT(state); + + // clear interrupt flags + state = clearIrqStatus(); + + // restore original packet length + uint8_t modem = getPacketType(); + if(modem == RADIOLIB_SX126X_PACKET_TYPE_LORA) { + state = setPacketParams(this->preambleLengthLoRa, this->crcTypeLoRa, this->implicitLen, 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); + } else { + return(RADIOLIB_ERR_UNKNOWN); + } + + return(state); +} + +int16_t SX126x::readData(uint8_t* data, size_t len) { + // this method may get called from receive() after Rx timeout + // if that's the case, the first call will return "SPI command timeout error" + // check the IRQ to be sure this really originated from timeout event + int16_t state = this->mod->SPIcheckStream(); + uint16_t irq = getIrqFlags(); + if((state == RADIOLIB_ERR_SPI_CMD_TIMEOUT) && (irq & RADIOLIB_SX126X_IRQ_TIMEOUT)) { + // this is definitely Rx timeout + return(RADIOLIB_ERR_RX_TIMEOUT); + } + RADIOLIB_ASSERT(state); + + // check integrity CRC + int16_t crcState = RADIOLIB_ERR_NONE; + // Report CRC mismatch when there's a payload CRC error, or a header error and no valid header (to avoid false alarm from previous packet) + if((irq & RADIOLIB_SX126X_IRQ_CRC_ERR) || ((irq & RADIOLIB_SX126X_IRQ_HEADER_ERR) && !(irq & RADIOLIB_SX126X_IRQ_HEADER_VALID))) { + crcState = RADIOLIB_ERR_CRC_MISMATCH; + } + + // get packet length and Rx buffer offset + uint8_t offset = 0; + size_t length = getPacketLength(true, &offset); + if((len != 0) && (len < length)) { + // user requested less data than we got, only return what was requested + length = len; + } + + // read packet data starting at offset + state = readBuffer(data, length, offset); + RADIOLIB_ASSERT(state); + + // clear interrupt flags + state = clearIrqStatus(); + + // check if CRC failed - this is done after reading data to give user the option to keep them + RADIOLIB_ASSERT(crcState); + + return(state); +} + +int16_t SX126x::startChannelScan() { + ChannelScanConfig_t cfg = { + .cad = { + .symNum = RADIOLIB_SX126X_CAD_PARAM_DEFAULT, + .detPeak = RADIOLIB_SX126X_CAD_PARAM_DEFAULT, + .detMin = RADIOLIB_SX126X_CAD_PARAM_DEFAULT, + .exitMode = RADIOLIB_SX126X_CAD_PARAM_DEFAULT, + .timeout = 0, + .irqFlags = RADIOLIB_IRQ_CAD_DEFAULT_FLAGS, + .irqMask = RADIOLIB_IRQ_CAD_DEFAULT_MASK, + }, + }; + return(this->startChannelScan(cfg)); +} + +int16_t SX126x::startChannelScan(const ChannelScanConfig_t &config) { + // check active modem + if(getPacketType() != RADIOLIB_SX126X_PACKET_TYPE_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // set mode to standby + int16_t state = standby(); + RADIOLIB_ASSERT(state); + + // set RF switch (if present) + this->mod->setRfSwitchState(Module::MODE_RX); + + // set DIO pin mapping + state = setDioIrqParams(getIrqMapped(config.cad.irqFlags), getIrqMapped(config.cad.irqMask)); + RADIOLIB_ASSERT(state); + + // clear interrupt flags + state = clearIrqStatus(); + RADIOLIB_ASSERT(state); + + // set mode to CAD + state = setCad(config.cad.symNum, config.cad.detPeak, config.cad.detMin, config.cad.exitMode, config.cad.timeout); + return(state); +} + +int16_t SX126x::getChannelScanResult() { + // check active modem + if(getPacketType() != RADIOLIB_SX126X_PACKET_TYPE_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // check CAD result + uint16_t cadResult = getIrqFlags(); + if(cadResult & RADIOLIB_SX126X_IRQ_CAD_DETECTED) { + // detected some LoRa activity + return(RADIOLIB_LORA_DETECTED); + } else if(cadResult & RADIOLIB_SX126X_IRQ_CAD_DONE) { + // channel is free + return(RADIOLIB_CHANNEL_FREE); + } + + return(RADIOLIB_ERR_UNKNOWN); +} + +int16_t SX126x::setBandwidth(float bw) { + // check active modem + if(getPacketType() != RADIOLIB_SX126X_PACKET_TYPE_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // ensure byte conversion doesn't overflow + RADIOLIB_CHECK_RANGE(bw, 0.0, 510.0, RADIOLIB_ERR_INVALID_BANDWIDTH); + + // check allowed bandwidth values + uint8_t bw_div2 = bw / 2 + 0.01; + switch (bw_div2) { + case 3: // 7.8: + this->bandwidth = RADIOLIB_SX126X_LORA_BW_7_8; + break; + case 5: // 10.4: + this->bandwidth = RADIOLIB_SX126X_LORA_BW_10_4; + break; + case 7: // 15.6: + this->bandwidth = RADIOLIB_SX126X_LORA_BW_15_6; + break; + case 10: // 20.8: + this->bandwidth = RADIOLIB_SX126X_LORA_BW_20_8; + break; + case 15: // 31.25: + this->bandwidth = RADIOLIB_SX126X_LORA_BW_31_25; + break; + case 20: // 41.7: + this->bandwidth = RADIOLIB_SX126X_LORA_BW_41_7; + break; + case 31: // 62.5: + this->bandwidth = RADIOLIB_SX126X_LORA_BW_62_5; + break; + case 62: // 125.0: + this->bandwidth = RADIOLIB_SX126X_LORA_BW_125_0; + break; + case 125: // 250.0 + this->bandwidth = RADIOLIB_SX126X_LORA_BW_250_0; + break; + case 250: // 500.0 + this->bandwidth = RADIOLIB_SX126X_LORA_BW_500_0; + break; + default: + return(RADIOLIB_ERR_INVALID_BANDWIDTH); + } + + // update modulation parameters + this->bandwidthKhz = bw; + return(setModulationParams(this->spreadingFactor, this->bandwidth, this->codingRate, this->ldrOptimize)); +} + +int16_t SX126x::setSpreadingFactor(uint8_t sf) { + // check active modem + if(getPacketType() != RADIOLIB_SX126X_PACKET_TYPE_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + RADIOLIB_CHECK_RANGE(sf, 5, 12, RADIOLIB_ERR_INVALID_SPREADING_FACTOR); + + // update modulation parameters + this->spreadingFactor = sf; + return(setModulationParams(this->spreadingFactor, this->bandwidth, this->codingRate, this->ldrOptimize)); +} + +int16_t SX126x::setCodingRate(uint8_t cr) { + // check active modem + if(getPacketType() != RADIOLIB_SX126X_PACKET_TYPE_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + RADIOLIB_CHECK_RANGE(cr, 5, 8, RADIOLIB_ERR_INVALID_CODING_RATE); + + // update modulation parameters + this->codingRate = cr - 4; + return(setModulationParams(this->spreadingFactor, this->bandwidth, this->codingRate, this->ldrOptimize)); +} + +int16_t SX126x::setSyncWord(uint8_t syncWord, uint8_t controlBits) { + // check active modem + if(getPacketType() != RADIOLIB_SX126X_PACKET_TYPE_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // update register + uint8_t data[2] = {(uint8_t)((syncWord & 0xF0) | ((controlBits & 0xF0) >> 4)), (uint8_t)(((syncWord & 0x0F) << 4) | (controlBits & 0x0F))}; + return(writeRegister(RADIOLIB_SX126X_REG_LORA_SYNC_WORD_MSB, data, 2)); +} + +int16_t SX126x::setCurrentLimit(float currentLimit) { + // check allowed range + if(!((currentLimit >= 0) && (currentLimit <= 140))) { + return(RADIOLIB_ERR_INVALID_CURRENT_LIMIT); + } + + // calculate raw value + uint8_t rawLimit = (uint8_t)(currentLimit / 2.5); + + // update register + return(writeRegister(RADIOLIB_SX126X_REG_OCP_CONFIGURATION, &rawLimit, 1)); +} + +float SX126x::getCurrentLimit() { + // get the raw value + uint8_t ocp = 0; + readRegister(RADIOLIB_SX126X_REG_OCP_CONFIGURATION, &ocp, 1); + + // return the actual value + return((float)ocp * 2.5); +} + +int16_t SX126x::setPreambleLength(size_t preambleLength) { + uint8_t modem = getPacketType(); + if(modem == RADIOLIB_SX126X_PACKET_TYPE_LORA) { + this->preambleLengthLoRa = preambleLength; + return(setPacketParams(this->preambleLengthLoRa, this->crcTypeLoRa, this->implicitLen, this->headerType, this->invertIQEnabled)); + } else if(modem == RADIOLIB_SX126X_PACKET_TYPE_GFSK) { + this->preambleLengthFSK = preambleLength; + this->preambleDetLength = preambleLength >= 32 ? RADIOLIB_SX126X_GFSK_PREAMBLE_DETECT_32 : + preambleLength >= 24 ? RADIOLIB_SX126X_GFSK_PREAMBLE_DETECT_24 : + preambleLength >= 16 ? RADIOLIB_SX126X_GFSK_PREAMBLE_DETECT_16 : + preambleLength > 0 ? RADIOLIB_SX126X_GFSK_PREAMBLE_DETECT_8 : + RADIOLIB_SX126X_GFSK_PREAMBLE_DETECT_OFF; + return(setPacketParamsFSK(this->preambleLengthFSK, this->preambleDetLength, this->crcTypeFSK, this->syncWordLength, this->addrComp, this->whitening, this->packetType)); + } + + return(RADIOLIB_ERR_UNKNOWN); +} + +int16_t SX126x::setFrequencyDeviation(float freqDev) { + // check active modem + if(getPacketType() != RADIOLIB_SX126X_PACKET_TYPE_GFSK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // set frequency deviation to lowest available setting (required for digimodes) + float newFreqDev = freqDev; + if(freqDev < 0.0) { + newFreqDev = 0.6; + } + + RADIOLIB_CHECK_RANGE(newFreqDev, 0.6, 200.0, RADIOLIB_ERR_INVALID_FREQUENCY_DEVIATION); + + // calculate raw frequency deviation value + uint32_t freqDevRaw = (uint32_t)(((newFreqDev * 1000.0) * (float)((uint32_t)(1) << 25)) / (RADIOLIB_SX126X_CRYSTAL_FREQ * 1000000.0)); + + // check modulation parameters + this->frequencyDev = freqDevRaw; + + // update modulation parameters + return(setModulationParamsFSK(this->bitRate, this->pulseShape, this->rxBandwidth, this->frequencyDev)); +} + +int16_t SX126x::setBitRate(float br) { + // check active modem + uint8_t modem = getPacketType(); + if((modem != RADIOLIB_SX126X_PACKET_TYPE_GFSK) && (modem != RADIOLIB_SX126X_PACKET_TYPE_LR_FHSS)) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + if(modem != RADIOLIB_SX126X_PACKET_TYPE_LR_FHSS) { + RADIOLIB_CHECK_RANGE(br, 0.6, 300.0, RADIOLIB_ERR_INVALID_BIT_RATE); + } + + // calculate raw bit rate value + uint32_t brRaw = (uint32_t)((RADIOLIB_SX126X_CRYSTAL_FREQ * 1000000.0 * 32.0) / (br * 1000.0)); + + // check modulation parameters + this->bitRate = brRaw; + + // update modulation parameters + return(setModulationParamsFSK(this->bitRate, this->pulseShape, this->rxBandwidth, this->frequencyDev)); +} + +int16_t SX126x::setDataRate(DataRate_t dr) { + int16_t state = RADIOLIB_ERR_UNKNOWN; + + // select interpretation based on active modem + uint8_t modem = this->getPacketType(); + if(modem == RADIOLIB_SX126X_PACKET_TYPE_GFSK) { + // set the bit rate + state = this->setBitRate(dr.fsk.bitRate); + RADIOLIB_ASSERT(state); + + // set the frequency deviation + state = this->setFrequencyDeviation(dr.fsk.freqDev); + + } else if(modem == RADIOLIB_SX126X_PACKET_TYPE_LORA) { + // set the spreading factor + state = this->setSpreadingFactor(dr.lora.spreadingFactor); + RADIOLIB_ASSERT(state); + + // set the bandwidth + state = this->setBandwidth(dr.lora.bandwidth); + RADIOLIB_ASSERT(state); + + // set the coding rate + state = this->setCodingRate(dr.lora.codingRate); + + } else if(modem == RADIOLIB_SX126X_PACKET_TYPE_LR_FHSS) { + // set the basic config + state = this->setLrFhssConfig(dr.lrFhss.bw, dr.lrFhss.cr); + RADIOLIB_ASSERT(state); + + // set hopping grid + this->lrFhssGridNonFcc = dr.lrFhss.narrowGrid ? RADIOLIB_SX126X_LR_FHSS_GRID_STEP_NON_FCC : RADIOLIB_SX126X_LR_FHSS_GRID_STEP_FCC; + + } + + return(state); +} + +int16_t SX126x::checkDataRate(DataRate_t dr) { + int16_t state = RADIOLIB_ERR_UNKNOWN; + + // select interpretation based on active modem + uint8_t modem = this->getPacketType(); + if(modem == RADIOLIB_SX126X_PACKET_TYPE_GFSK) { + RADIOLIB_CHECK_RANGE(dr.fsk.bitRate, 0.6, 300.0, RADIOLIB_ERR_INVALID_BIT_RATE); + RADIOLIB_CHECK_RANGE(dr.fsk.freqDev, 0.6, 200.0, RADIOLIB_ERR_INVALID_FREQUENCY_DEVIATION); + return(RADIOLIB_ERR_NONE); + + } else if(modem == RADIOLIB_SX126X_PACKET_TYPE_LORA) { + RADIOLIB_CHECK_RANGE(dr.lora.spreadingFactor, 5, 12, RADIOLIB_ERR_INVALID_SPREADING_FACTOR); + RADIOLIB_CHECK_RANGE(dr.lora.bandwidth, 0.0, 510.0, RADIOLIB_ERR_INVALID_BANDWIDTH); + RADIOLIB_CHECK_RANGE(dr.lora.codingRate, 5, 8, RADIOLIB_ERR_INVALID_CODING_RATE); + return(RADIOLIB_ERR_NONE); + + } + + return(state); +} + +int16_t SX126x::setRxBandwidth(float rxBw) { + // check active modem + if(getPacketType() != RADIOLIB_SX126X_PACKET_TYPE_GFSK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // check modulation parameters + /*if(2 * this->frequencyDev + this->bitRate > rxBw * 1000.0) { + return(RADIOLIB_ERR_INVALID_MODULATION_PARAMETERS); + }*/ + this->rxBandwidthKhz = rxBw; + + // check allowed receiver bandwidth values + if(fabsf(rxBw - 4.8) <= 0.001) { + this->rxBandwidth = RADIOLIB_SX126X_GFSK_RX_BW_4_8; + } else if(fabsf(rxBw - 5.8) <= 0.001) { + this->rxBandwidth = RADIOLIB_SX126X_GFSK_RX_BW_5_8; + } else if(fabsf(rxBw - 7.3) <= 0.001) { + this->rxBandwidth = RADIOLIB_SX126X_GFSK_RX_BW_7_3; + } else if(fabsf(rxBw - 9.7) <= 0.001) { + this->rxBandwidth = RADIOLIB_SX126X_GFSK_RX_BW_9_7; + } else if(fabsf(rxBw - 11.7) <= 0.001) { + this->rxBandwidth = RADIOLIB_SX126X_GFSK_RX_BW_11_7; + } else if(fabsf(rxBw - 14.6) <= 0.001) { + this->rxBandwidth = RADIOLIB_SX126X_GFSK_RX_BW_14_6; + } else if(fabsf(rxBw - 19.5) <= 0.001) { + this->rxBandwidth = RADIOLIB_SX126X_GFSK_RX_BW_19_5; + } else if(fabsf(rxBw - 23.4) <= 0.001) { + this->rxBandwidth = RADIOLIB_SX126X_GFSK_RX_BW_23_4; + } else if(fabsf(rxBw - 29.3) <= 0.001) { + this->rxBandwidth = RADIOLIB_SX126X_GFSK_RX_BW_29_3; + } else if(fabsf(rxBw - 39.0) <= 0.001) { + this->rxBandwidth = RADIOLIB_SX126X_GFSK_RX_BW_39_0; + } else if(fabsf(rxBw - 46.9) <= 0.001) { + this->rxBandwidth = RADIOLIB_SX126X_GFSK_RX_BW_46_9; + } else if(fabsf(rxBw - 58.6) <= 0.001) { + this->rxBandwidth = RADIOLIB_SX126X_GFSK_RX_BW_58_6; + } else if(fabsf(rxBw - 78.2) <= 0.001) { + this->rxBandwidth = RADIOLIB_SX126X_GFSK_RX_BW_78_2; + } else if(fabsf(rxBw - 93.8) <= 0.001) { + this->rxBandwidth = RADIOLIB_SX126X_GFSK_RX_BW_93_8; + } else if(fabsf(rxBw - 117.3) <= 0.001) { + this->rxBandwidth = RADIOLIB_SX126X_GFSK_RX_BW_117_3; + } else if(fabsf(rxBw - 156.2) <= 0.001) { + this->rxBandwidth = RADIOLIB_SX126X_GFSK_RX_BW_156_2; + } else if(fabsf(rxBw - 187.2) <= 0.001) { + this->rxBandwidth = RADIOLIB_SX126X_GFSK_RX_BW_187_2; + } else if(fabsf(rxBw - 234.3) <= 0.001) { + this->rxBandwidth = RADIOLIB_SX126X_GFSK_RX_BW_234_3; + } else if(fabsf(rxBw - 312.0) <= 0.001) { + this->rxBandwidth = RADIOLIB_SX126X_GFSK_RX_BW_312_0; + } else if(fabsf(rxBw - 373.6) <= 0.001) { + this->rxBandwidth = RADIOLIB_SX126X_GFSK_RX_BW_373_6; + } else if(fabsf(rxBw - 467.0) <= 0.001) { + this->rxBandwidth = RADIOLIB_SX126X_GFSK_RX_BW_467_0; + } else { + return(RADIOLIB_ERR_INVALID_RX_BANDWIDTH); + } + + // update modulation parameters + return(setModulationParamsFSK(this->bitRate, this->pulseShape, this->rxBandwidth, this->frequencyDev)); +} + +int16_t SX126x::setRxBoostedGainMode(bool rxbgm, bool persist) { + // update RX gain setting register + uint8_t rxGain = rxbgm ? RADIOLIB_SX126X_RX_GAIN_BOOSTED : RADIOLIB_SX126X_RX_GAIN_POWER_SAVING; + int16_t state = writeRegister(RADIOLIB_SX126X_REG_RX_GAIN, &rxGain, 1); + RADIOLIB_ASSERT(state); + + // add Rx Gain register to retention memory if requested + if(persist) { + // values and registers below are specified in SX126x datasheet v2.1 section 9.6, just below table 9-3 + uint8_t data[] = { 0x01, (uint8_t)((RADIOLIB_SX126X_REG_RX_GAIN >> 8) & 0xFF), (uint8_t)(RADIOLIB_SX126X_REG_RX_GAIN & 0xFF) }; + state = writeRegister(RADIOLIB_SX126X_REG_RX_GAIN_RETENTION_0, data, 3); + } + + return(state); +} + +int16_t SX126x::setDataShaping(uint8_t sh) { + // check active modem + uint8_t modem = getPacketType(); + if((modem != RADIOLIB_SX126X_PACKET_TYPE_GFSK) && (modem != RADIOLIB_SX126X_PACKET_TYPE_LR_FHSS)) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // set data shaping + switch(sh) { + case RADIOLIB_SHAPING_NONE: + this->pulseShape = RADIOLIB_SX126X_GFSK_FILTER_NONE; + break; + case RADIOLIB_SHAPING_0_3: + this->pulseShape = RADIOLIB_SX126X_GFSK_FILTER_GAUSS_0_3; + break; + case RADIOLIB_SHAPING_0_5: + this->pulseShape = RADIOLIB_SX126X_GFSK_FILTER_GAUSS_0_5; + break; + case RADIOLIB_SHAPING_0_7: + this->pulseShape = RADIOLIB_SX126X_GFSK_FILTER_GAUSS_0_7; + break; + case RADIOLIB_SHAPING_1_0: + this->pulseShape = RADIOLIB_SX126X_GFSK_FILTER_GAUSS_1; + break; + default: + return(RADIOLIB_ERR_INVALID_DATA_SHAPING); + } + + // update modulation parameters + return(setModulationParamsFSK(this->bitRate, this->pulseShape, this->rxBandwidth, this->frequencyDev)); +} + +int16_t SX126x::setSyncWord(uint8_t* syncWord, size_t len) { + // check active modem + uint8_t modem = getPacketType(); + if(modem == RADIOLIB_SX126X_PACKET_TYPE_GFSK) { + // check sync word Length + if(len > 8) { + return(RADIOLIB_ERR_INVALID_SYNC_WORD); + } + + // write sync word + int16_t state = writeRegister(RADIOLIB_SX126X_REG_SYNC_WORD_0, syncWord, len); + RADIOLIB_ASSERT(state); + + // update packet parameters + this->syncWordLength = len * 8; + state = setPacketParamsFSK(this->preambleLengthFSK, this->preambleDetLength, this->crcTypeFSK, this->syncWordLength, this->addrComp, this->whitening, this->packetType); + + return(state); + + } else if(modem == RADIOLIB_SX126X_PACKET_TYPE_LORA) { + // with length set to 1 and LoRa modem active, assume it is the LoRa sync word + if(len > 1) { + 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); +} + +int16_t SX126x::setSyncBits(uint8_t *syncWord, uint8_t bitsLen) { + // check active modem + if(getPacketType() != RADIOLIB_SX126X_PACKET_TYPE_GFSK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // check sync word Length + if(bitsLen > 0x40) { + return(RADIOLIB_ERR_INVALID_SYNC_WORD); + } + + uint8_t bytesLen = bitsLen / 8; + if ((bitsLen % 8) != 0) { + bytesLen++; + } + + return(setSyncWord(syncWord, bytesLen)); +} + +int16_t SX126x::setNodeAddress(uint8_t addr) { + // check active modem + if(getPacketType() != RADIOLIB_SX126X_PACKET_TYPE_GFSK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // enable address filtering (node only) + this->addrComp = RADIOLIB_SX126X_GFSK_ADDRESS_FILT_NODE; + int16_t state = setPacketParamsFSK(this->preambleLengthFSK, this->preambleDetLength, this->crcTypeFSK, this->syncWordLength, this->addrComp, this->whitening, this->packetType); + RADIOLIB_ASSERT(state); + + // set node address + this->nodeAddr = addr; + state = writeRegister(RADIOLIB_SX126X_REG_NODE_ADDRESS, &addr, 1); + + return(state); +} + +int16_t SX126x::setBroadcastAddress(uint8_t broadAddr) { + // check active modem + if(getPacketType() != RADIOLIB_SX126X_PACKET_TYPE_GFSK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // enable address filtering (node and broadcast) + this->addrComp = RADIOLIB_SX126X_GFSK_ADDRESS_FILT_NODE_BROADCAST; + int16_t state = setPacketParamsFSK(this->preambleLengthFSK, this->preambleDetLength, this->crcTypeFSK, this->syncWordLength, this->addrComp, this->whitening, this->packetType); + RADIOLIB_ASSERT(state); + + // set broadcast address + state = writeRegister(RADIOLIB_SX126X_REG_BROADCAST_ADDRESS, &broadAddr, 1); + + return(state); +} + +int16_t SX126x::disableAddressFiltering() { + // check active modem + if(getPacketType() != RADIOLIB_SX126X_PACKET_TYPE_GFSK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // disable address filtering + this->addrComp = RADIOLIB_SX126X_GFSK_ADDRESS_FILT_OFF; + return(setPacketParamsFSK(this->preambleLengthFSK, this->preambleDetLength, this->crcTypeFSK, this->syncWordLength, this->addrComp, this->whitening)); +} + +int16_t SX126x::setCRC(uint8_t len, uint16_t initial, uint16_t polynomial, bool inverted) { + // check active modem + uint8_t modem = getPacketType(); + + if(modem == RADIOLIB_SX126X_PACKET_TYPE_GFSK) { + // update packet parameters + switch(len) { + case 0: + this->crcTypeFSK = RADIOLIB_SX126X_GFSK_CRC_OFF; + break; + case 1: + if(inverted) { + this->crcTypeFSK = RADIOLIB_SX126X_GFSK_CRC_1_BYTE_INV; + } else { + this->crcTypeFSK = RADIOLIB_SX126X_GFSK_CRC_1_BYTE; + } + break; + case 2: + if(inverted) { + this->crcTypeFSK = RADIOLIB_SX126X_GFSK_CRC_2_BYTE_INV; + } else { + this->crcTypeFSK = RADIOLIB_SX126X_GFSK_CRC_2_BYTE; + } + break; + default: + return(RADIOLIB_ERR_INVALID_CRC_CONFIGURATION); + } + + int16_t state = setPacketParamsFSK(this->preambleLengthFSK, this->preambleDetLength, this->crcTypeFSK, this->syncWordLength, this->addrComp, this->whitening, this->packetType); + RADIOLIB_ASSERT(state); + + // write initial CRC value + uint8_t data[2] = {(uint8_t)((initial >> 8) & 0xFF), (uint8_t)(initial & 0xFF)}; + state = writeRegister(RADIOLIB_SX126X_REG_CRC_INITIAL_MSB, data, 2); + RADIOLIB_ASSERT(state); + + // write CRC polynomial value + data[0] = (uint8_t)((polynomial >> 8) & 0xFF); + data[1] = (uint8_t)(polynomial & 0xFF); + state = writeRegister(RADIOLIB_SX126X_REG_CRC_POLYNOMIAL_MSB, data, 2); + + return(state); + + } else if(modem == RADIOLIB_SX126X_PACKET_TYPE_LORA) { + // LoRa CRC doesn't allow to set CRC polynomial, initial value, or inversion + + // update packet parameters + if(len) { + this->crcTypeLoRa = RADIOLIB_SX126X_LORA_CRC_ON; + } else { + this->crcTypeLoRa = RADIOLIB_SX126X_LORA_CRC_OFF; + } + + return(setPacketParams(this->preambleLengthLoRa, this->crcTypeLoRa, this->implicitLen, this->headerType, this->invertIQEnabled)); + } + + return(RADIOLIB_ERR_UNKNOWN); +} + +int16_t SX126x::setWhitening(bool enabled, uint16_t initial) { + // check active modem + if(getPacketType() != RADIOLIB_SX126X_PACKET_TYPE_GFSK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + int16_t state = RADIOLIB_ERR_NONE; + if(!enabled) { + // disable whitening + this->whitening = RADIOLIB_SX126X_GFSK_WHITENING_OFF; + + state = setPacketParamsFSK(this->preambleLengthFSK, this->preambleDetLength, this->crcTypeFSK, this->syncWordLength, this->addrComp, this->whitening, this->packetType); + RADIOLIB_ASSERT(state); + + } else { + // enable whitening + this->whitening = RADIOLIB_SX126X_GFSK_WHITENING_ON; + + // write initial whitening value + // as per note on pg. 65 of datasheet v1.2: "The user should not change the value of the 7 MSB's of this register" + uint8_t data[2]; + // first read the actual value and mask 7 MSB which we can not change + // if different value is written in 7 MSB, the Rx won't even work (tested on HW) + state = readRegister(RADIOLIB_SX126X_REG_WHITENING_INITIAL_MSB, data, 1); + RADIOLIB_ASSERT(state); + + data[0] = (data[0] & 0xFE) | (uint8_t)((initial >> 8) & 0x01); + data[1] = (uint8_t)(initial & 0xFF); + state = writeRegister(RADIOLIB_SX126X_REG_WHITENING_INITIAL_MSB, data, 2); + RADIOLIB_ASSERT(state); + + state = setPacketParamsFSK(this->preambleLengthFSK, this->preambleDetLength, this->crcTypeFSK, this->syncWordLength, this->addrComp, this->whitening, this->packetType); + RADIOLIB_ASSERT(state); + } + return(state); +} + +float SX126x::getDataRate() const { + return(this->dataRateMeasured); +} + +float SX126x::getRSSI() { + return(this->getRSSI(true)); +} + +float SX126x::getRSSI(bool packet) { + if(packet) { + // get last packet RSSI from packet status + uint32_t packetStatus = getPacketStatus(); + uint8_t rssiPkt = packetStatus & 0xFF; + return(-1.0 * rssiPkt/2.0); + } else { + // get instantaneous RSSI value + uint8_t rssiRaw = 0; + this->mod->SPIreadStream(RADIOLIB_SX126X_CMD_GET_RSSI_INST, &rssiRaw, 1); + return((float)rssiRaw / (-2.0)); + } +} + +float SX126x::getSNR() { + // check active modem + if(getPacketType() != RADIOLIB_SX126X_PACKET_TYPE_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // get last packet SNR from packet status + uint32_t packetStatus = getPacketStatus(); + uint8_t snrPkt = (packetStatus >> 8) & 0xFF; + if(snrPkt < 128) { + return(snrPkt/4.0); + } else { + return((snrPkt - 256)/4.0); + } +} + +float SX126x::getFrequencyError() { + // check active modem + uint8_t modem = getPacketType(); + if(modem != RADIOLIB_SX126X_PACKET_TYPE_LORA) { + return(0.0); + } + + // read the raw frequency error register values + uint8_t efeRaw[3] = {0}; + int16_t state = readRegister(RADIOLIB_SX126X_REG_FREQ_ERROR, &efeRaw[0], 1); + RADIOLIB_ASSERT(state); + state = readRegister(RADIOLIB_SX126X_REG_FREQ_ERROR + 1, &efeRaw[1], 1); + RADIOLIB_ASSERT(state); + state = readRegister(RADIOLIB_SX126X_REG_FREQ_ERROR + 2, &efeRaw[2], 1); + RADIOLIB_ASSERT(state); + uint32_t efe = ((uint32_t) efeRaw[0] << 16) | ((uint32_t) efeRaw[1] << 8) | efeRaw[2]; + efe &= 0x0FFFFF; + + float error = 0; + + // check the first bit + if (efe & 0x80000) { + // frequency error is negative + efe |= (uint32_t) 0xFFF00000; + efe = ~efe + 1; + error = 1.55 * (float) efe / (1600.0 / (float) this->bandwidthKhz) * -1.0; + } else { + error = 1.55 * (float) efe / (1600.0 / (float) this->bandwidthKhz); + } + + return(error); +} + +size_t SX126x::getPacketLength(bool update) { + return(this->getPacketLength(update, NULL)); +} + +size_t SX126x::getPacketLength(bool update, uint8_t* offset) { + (void)update; + + // in implicit mode, return the cached value + if((getPacketType() == RADIOLIB_SX126X_PACKET_TYPE_LORA) && (this->headerType == RADIOLIB_SX126X_LORA_HEADER_IMPLICIT)) { + return(this->implicitLen); + } + + uint8_t rxBufStatus[2] = {0, 0}; + this->mod->SPIreadStream(RADIOLIB_SX126X_CMD_GET_RX_BUFFER_STATUS, rxBufStatus, 2); + + if(offset) { *offset = rxBufStatus[1]; } + + return((size_t)rxBufStatus[0]); +} + +int16_t SX126x::fixedPacketLengthMode(uint8_t len) { + return(setPacketMode(RADIOLIB_SX126X_GFSK_PACKET_FIXED, len)); +} + +int16_t SX126x::variablePacketLengthMode(uint8_t maxLen) { + return(setPacketMode(RADIOLIB_SX126X_GFSK_PACKET_VARIABLE, 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 + uint8_t modem = getPacketType(); + if(modem == RADIOLIB_SX126X_PACKET_TYPE_LORA) { + uint32_t symbolLength_us = ((uint32_t)(1000 * 10) << this->spreadingFactor) / (this->bandwidthKhz * 10) ; + uint8_t sfCoeff1_x4 = 17; // (4.25 * 4) + uint8_t sfCoeff2 = 8; + if(this->spreadingFactor == 5 || this->spreadingFactor == 6) { + sfCoeff1_x4 = 25; // 6.25 * 4 + sfCoeff2 = 0; + } + uint8_t sfDivisor = 4*this->spreadingFactor; + if(symbolLength_us >= 16000) { + sfDivisor = 4*(this->spreadingFactor - 2); + } + const int8_t bitsPerCrc = 16; + const int8_t N_symbol_header = this->headerType == RADIOLIB_SX126X_LORA_HEADER_EXPLICIT ? 20 : 0; + + // numerator of equation in section 6.1.4 of SX1268 datasheet v1.1 (might not actually be bitcount, but it has len * 8) + int16_t bitCount = (int16_t) 8 * len + this->crcTypeLoRa * bitsPerCrc - 4 * this->spreadingFactor + sfCoeff2 + N_symbol_header; + if(bitCount < 0) { + bitCount = 0; + } + // add (sfDivisor) - 1 to the numerator to give integer CEIL(...) + uint16_t nPreCodedSymbols = (bitCount + (sfDivisor - 1)) / (sfDivisor); + + // preamble can be 65k, therefore nSymbol_x4 needs to be 32 bit + uint32_t nSymbol_x4 = (this->preambleLengthLoRa + 8) * 4 + sfCoeff1_x4 + nPreCodedSymbols * (this->codingRate + 4) * 4; + + return((symbolLength_us * nSymbol_x4) / 4); + + } else if(modem == RADIOLIB_SX126X_PACKET_TYPE_GFSK) { + return(((uint32_t)len * 8 * this->bitRate) / (RADIOLIB_SX126X_CRYSTAL_FREQ * 32)); + + } else if(modem == RADIOLIB_SX126X_PACKET_TYPE_LR_FHSS) { + // calculate the number of bits based on coding rate + uint16_t N_bits; + switch(this->lrFhssCr) { + case RADIOLIB_SX126X_LR_FHSS_CR_5_6: + N_bits = ((len * 6) + 4) / 5; // this is from the official LR11xx driver, but why the extra +4? + break; + case RADIOLIB_SX126X_LR_FHSS_CR_2_3: + N_bits = (len * 3) / 2; + break; + case RADIOLIB_SX126X_LR_FHSS_CR_1_2: + N_bits = len * 2; + break; + case RADIOLIB_SX126X_LR_FHSS_CR_1_3: + N_bits = len * 3; + break; + default: + return(RADIOLIB_ERR_INVALID_CODING_RATE); + } + + // calculate number of bits when accounting for unaligned last block + uint16_t N_payBits = (N_bits / RADIOLIB_SX126X_LR_FHSS_FRAG_BITS) * RADIOLIB_SX126X_LR_FHSS_BLOCK_BITS; + uint16_t N_lastBlockBits = N_bits % RADIOLIB_SX126X_LR_FHSS_FRAG_BITS; + if(N_lastBlockBits) { + N_payBits += N_lastBlockBits + 2; + } + + // add header bits + uint16_t N_totalBits = (RADIOLIB_SX126X_LR_FHSS_HEADER_BITS * this->lrFhssHdrCount) + N_payBits; + return(((uint32_t)N_totalBits * 8 * 1000000UL) / 488.28215f); + + } + + return(RADIOLIB_ERR_UNKNOWN); +} + +RadioLibTime_t SX126x::calculateRxTimeout(RadioLibTime_t timeoutUs) { + // the timeout value is given in units of 15.625 microseconds + // the calling function should provide some extra width, as this number of units is truncated to integer + RadioLibTime_t timeout = timeoutUs / 15.625; + return(timeout); +} + +uint32_t SX126x::getIrqFlags() { + uint8_t data[] = { 0x00, 0x00 }; + this->mod->SPIreadStream(RADIOLIB_SX126X_CMD_GET_IRQ_STATUS, data, 2); + return(((uint32_t)(data[0]) << 8) | data[1]); +} + +int16_t SX126x::setIrqFlags(uint32_t irq) { + return(this->setDioIrqParams(irq, irq)); +} + +int16_t SX126x::clearIrqFlags(uint32_t irq) { + return(this->clearIrqStatus(irq)); +} + +int16_t SX126x::implicitHeader(size_t len) { + return(setHeaderType(RADIOLIB_SX126X_LORA_HEADER_IMPLICIT, len)); +} + +int16_t SX126x::explicitHeader() { + return(setHeaderType(RADIOLIB_SX126X_LORA_HEADER_EXPLICIT)); +} + +int16_t SX126x::setRegulatorLDO() { + return(setRegulatorMode(RADIOLIB_SX126X_REGULATOR_LDO)); +} + +int16_t SX126x::setRegulatorDCDC() { + return(setRegulatorMode(RADIOLIB_SX126X_REGULATOR_DC_DC)); +} + +int16_t SX126x::setEncoding(uint8_t encoding) { + return(setWhitening(encoding)); +} + +void SX126x::setRfSwitchPins(uint32_t rxEn, uint32_t txEn) { + this->mod->setRfSwitchPins(rxEn, txEn); +} + +void SX126x::setRfSwitchTable(const uint32_t (&pins)[Module::RFSWITCH_MAX_PINS], const Module::RfSwitchMode_t table[]) { + this->mod->setRfSwitchTable(pins, table); +} + +int16_t SX126x::forceLDRO(bool enable) { + // check active modem + if(getPacketType() != RADIOLIB_SX126X_PACKET_TYPE_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // update modulation parameters + this->ldroAuto = false; + this->ldrOptimize = (uint8_t)enable; + return(setModulationParams(this->spreadingFactor, this->bandwidth, this->codingRate, this->ldrOptimize)); +} + +int16_t SX126x::autoLDRO() { + if(getPacketType() != RADIOLIB_SX126X_PACKET_TYPE_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + this->ldroAuto = true; + return(RADIOLIB_ERR_NONE); +} + +uint8_t SX126x::randomByte() { + // set some magic registers + this->mod->SPIsetRegValue(RADIOLIB_SX126X_REG_ANA_LNA, RADIOLIB_SX126X_LNA_RNG_ENABLED, 0, 0); + this->mod->SPIsetRegValue(RADIOLIB_SX126X_REG_ANA_MIXER, RADIOLIB_SX126X_MIXER_RNG_ENABLED, 0, 0); + + // set mode to Rx + setRx(RADIOLIB_SX126X_RX_TIMEOUT_INF); + + // wait a bit for the RSSI reading to stabilise + this->mod->hal->delay(10); + + // read RSSI value 8 times, always keep just the least significant bit + uint8_t randByte = 0x00; + for(uint8_t i = 0; i < 8; i++) { + uint8_t val = 0x00; + readRegister(RADIOLIB_SX126X_REG_RANDOM_NUMBER_0, &val, sizeof(uint8_t)); + randByte |= ((val & 0x01) << i); + } + + // set mode to standby + standby(); + + // restore the magic registers + this->mod->SPIsetRegValue(RADIOLIB_SX126X_REG_ANA_LNA, RADIOLIB_SX126X_LNA_RNG_DISABLED, 0, 0); + this->mod->SPIsetRegValue(RADIOLIB_SX126X_REG_ANA_MIXER, RADIOLIB_SX126X_MIXER_RNG_DISABLED, 0, 0); + + return(randByte); +} + +int16_t SX126x::invertIQ(bool enable) { + if(getPacketType() != RADIOLIB_SX126X_PACKET_TYPE_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + if(enable) { + this->invertIQEnabled = RADIOLIB_SX126X_LORA_IQ_INVERTED; + } else { + this->invertIQEnabled = RADIOLIB_SX126X_LORA_IQ_STANDARD; + } + + return(setPacketParams(this->preambleLengthLoRa, this->crcTypeLoRa, this->implicitLen, this->headerType, this->invertIQEnabled)); +} + +int16_t SX126x::getModem(ModemType_t* modem) { + RADIOLIB_ASSERT_PTR(modem); + + uint8_t packetType = getPacketType(); + switch(packetType) { + case(RADIOLIB_SX126X_PACKET_TYPE_LORA): + *modem = ModemType_t::RADIOLIB_MODEM_LORA; + return(RADIOLIB_ERR_NONE); + case(RADIOLIB_SX126X_PACKET_TYPE_GFSK): + *modem = ModemType_t::RADIOLIB_MODEM_FSK; + return(RADIOLIB_ERR_NONE); + case(RADIOLIB_SX126X_PACKET_TYPE_LR_FHSS): + *modem = ModemType_t::RADIOLIB_MODEM_LRFHSS; + return(RADIOLIB_ERR_NONE); + } + + return(RADIOLIB_ERR_WRONG_MODEM); +} + +#if !RADIOLIB_EXCLUDE_DIRECT_RECEIVE +void SX126x::setDirectAction(void (*func)(void)) { + setDio1Action(func); +} + +void SX126x::readBit(uint32_t pin) { + updateDirectBuffer((uint8_t)this->mod->hal->digitalRead(pin)); +} +#endif + +int16_t SX126x::uploadPatch(const uint32_t* patch, size_t len, bool nonvolatile) { + // set to standby RC mode + int16_t state = standby(RADIOLIB_SX126X_STANDBY_RC); + RADIOLIB_ASSERT(state); + + // check the version + #if RADIOLIB_DEBUG_BASIC + char ver_pre[16]; + this->mod->SPIreadRegisterBurst(RADIOLIB_SX126X_REG_VERSION_STRING, 16, reinterpret_cast(ver_pre)); + RADIOLIB_DEBUG_BASIC_PRINTLN("Pre-update version string: %s", ver_pre); + #endif + + // enable patch update + this->mod->SPIwriteRegister(RADIOLIB_SX126X_REG_PATCH_UPDATE_ENABLE, RADIOLIB_SX126X_PATCH_UPDATE_ENABLED); + + // upload the patch + uint8_t data[4]; + for(uint32_t i = 0; i < len / sizeof(uint32_t); i++) { + uint32_t bin = 0; + if(nonvolatile) { + bin = RADIOLIB_NONVOLATILE_READ_DWORD(patch + i); + } else { + bin = patch[i]; + } + data[0] = (bin >> 24) & 0xFF; + data[1] = (bin >> 16) & 0xFF; + data[2] = (bin >> 8) & 0xFF; + data[3] = bin & 0xFF; + this->mod->SPIwriteRegisterBurst(RADIOLIB_SX126X_REG_PATCH_MEMORY_BASE + i*sizeof(uint32_t), data, sizeof(uint32_t)); + } + + // disable patch update + this->mod->SPIwriteRegister(RADIOLIB_SX126X_REG_PATCH_UPDATE_ENABLE, RADIOLIB_SX126X_PATCH_UPDATE_DISABLED); + + // update + this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_PRAM_UPDATE, NULL, 0); + + // check the version again + #if RADIOLIB_DEBUG_BASIC + char ver_post[16]; + this->mod->SPIreadRegisterBurst(RADIOLIB_SX126X_REG_VERSION_STRING, 16, reinterpret_cast(ver_post)); + RADIOLIB_DEBUG_BASIC_PRINTLN("Post-update version string: %s", ver_post); + #endif + + return(state); +} + +int16_t SX126x::spectralScanStart(uint16_t numSamples, uint8_t window, uint8_t interval) { + // abort first - not sure if this is strictly needed, but the example code does this + spectralScanAbort(); + + // set the RSSI window size + this->mod->SPIwriteRegister(RADIOLIB_SX126X_REG_RSSI_AVG_WINDOW, window); + + // start Rx with infinite timeout + int16_t state = setRx(RADIOLIB_SX126X_RX_TIMEOUT_INF); + RADIOLIB_ASSERT(state); + + // now set the actual spectral scan parameters + uint8_t data[3] = { (uint8_t)((numSamples >> 8) & 0xFF), (uint8_t)(numSamples & 0xFF), interval }; + return(this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_SET_SPECTR_SCAN_PARAMS, data, 3)); +} + +void SX126x::spectralScanAbort() { + this->mod->SPIwriteRegister(RADIOLIB_SX126X_REG_RSSI_AVG_WINDOW, 0x00); +} + +int16_t SX126x::spectralScanGetStatus() { + uint8_t status = this->mod->SPIreadRegister(RADIOLIB_SX126X_REG_SPECTRAL_SCAN_STATUS); + if(status == RADIOLIB_SX126X_SPECTRAL_SCAN_COMPLETED) { + return(RADIOLIB_ERR_NONE); + } + return(RADIOLIB_ERR_RANGING_TIMEOUT); +} + +int16_t SX126x::spectralScanGetResult(uint16_t* results) { + // read the raw results + uint8_t data[2*RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE]; + this->mod->SPIreadRegisterBurst(RADIOLIB_SX126X_REG_SPECTRAL_SCAN_RESULT, 2*RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE, data); + + // convert it + for(uint8_t i = 0; i < RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE; i++) { + results[i] = ((uint16_t)data[i*2] << 8) | ((uint16_t)data[i*2 + 1]); + } + return(RADIOLIB_ERR_NONE); +} + +int16_t SX126x::setTCXO(float voltage, uint32_t delay) { + // check if TCXO is enabled at all + if(this->XTAL) { + return(RADIOLIB_ERR_INVALID_TCXO_VOLTAGE); + } + + // set mode to standby + standby(); + + // check RADIOLIB_SX126X_XOSC_START_ERR flag and clear it + if(getDeviceErrors() & RADIOLIB_SX126X_XOSC_START_ERR) { + clearDeviceErrors(); + } + + // check 0 V disable + if(fabsf(voltage - 0.0) <= 0.001) { + return(reset(true)); + } + + // check alowed voltage values + uint8_t data[4]; + if(fabsf(voltage - 1.6) <= 0.001) { + data[0] = RADIOLIB_SX126X_DIO3_OUTPUT_1_6; + } else if(fabsf(voltage - 1.7) <= 0.001) { + data[0] = RADIOLIB_SX126X_DIO3_OUTPUT_1_7; + } else if(fabsf(voltage - 1.8) <= 0.001) { + data[0] = RADIOLIB_SX126X_DIO3_OUTPUT_1_8; + } else if(fabsf(voltage - 2.2) <= 0.001) { + data[0] = RADIOLIB_SX126X_DIO3_OUTPUT_2_2; + } else if(fabsf(voltage - 2.4) <= 0.001) { + data[0] = RADIOLIB_SX126X_DIO3_OUTPUT_2_4; + } else if(fabsf(voltage - 2.7) <= 0.001) { + data[0] = RADIOLIB_SX126X_DIO3_OUTPUT_2_7; + } else if(fabsf(voltage - 3.0) <= 0.001) { + data[0] = RADIOLIB_SX126X_DIO3_OUTPUT_3_0; + } else if(fabsf(voltage - 3.3) <= 0.001) { + data[0] = RADIOLIB_SX126X_DIO3_OUTPUT_3_3; + } else { + return(RADIOLIB_ERR_INVALID_TCXO_VOLTAGE); + } + + // calculate delay + uint32_t delayValue = (float)delay / 15.625; + data[1] = (uint8_t)((delayValue >> 16) & 0xFF); + data[2] = (uint8_t)((delayValue >> 8) & 0xFF); + data[3] = (uint8_t)(delayValue & 0xFF); + + this->tcxoDelay = delay; + + // enable TCXO control on DIO3 + return(this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_SET_DIO3_AS_TCXO_CTRL, data, 4)); +} + +int16_t SX126x::setDio2AsRfSwitch(bool enable) { + uint8_t data = 0; + if(enable) { + data = RADIOLIB_SX126X_DIO2_AS_RF_SWITCH; + } else { + data = RADIOLIB_SX126X_DIO2_AS_IRQ; + } + return(this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_SET_DIO2_AS_RF_SWITCH_CTRL, &data, 1)); +} + +int16_t SX126x::setFs() { + return(this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_SET_FS, NULL, 0)); +} + +int16_t SX126x::setTx(uint32_t timeout) { + uint8_t data[] = { (uint8_t)((timeout >> 16) & 0xFF), (uint8_t)((timeout >> 8) & 0xFF), (uint8_t)(timeout & 0xFF)} ; + return(this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_SET_TX, data, 3)); +} + +int16_t SX126x::setRx(uint32_t timeout) { + uint8_t data[] = { (uint8_t)((timeout >> 16) & 0xFF), (uint8_t)((timeout >> 8) & 0xFF), (uint8_t)(timeout & 0xFF) }; + return(this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_SET_RX, data, 3, true, false)); +} + +int16_t SX126x::setCad(uint8_t symbolNum, uint8_t detPeak, uint8_t detMin, uint8_t exitMode, RadioLibTime_t timeout) { + // default CAD parameters are shown in Semtech AN1200.48, page 41. + const uint8_t detPeakValues[6] = { 22, 22, 24, 25, 26, 30}; + + // CAD parameters aren't available for SF-6. Just to be safe. + if(this->spreadingFactor < 7) { + this->spreadingFactor = 7; + } else if(this->spreadingFactor > 12) { + this->spreadingFactor = 12; + } + + // build the packet with default configuration + uint8_t data[7]; + data[0] = RADIOLIB_SX126X_CAD_ON_2_SYMB; + data[1] = detPeakValues[this->spreadingFactor - 7]; + data[2] = RADIOLIB_SX126X_CAD_PARAM_DET_MIN; + data[3] = RADIOLIB_SX126X_CAD_GOTO_STDBY; + uint32_t timeout_raw = (float)timeout / 15.625f; + data[4] = (uint8_t)((timeout_raw >> 16) & 0xFF); + data[5] = (uint8_t)((timeout_raw >> 8) & 0xFF); + data[6] = (uint8_t)(timeout_raw & 0xFF); + + // set user-provided values + if(symbolNum != RADIOLIB_SX126X_CAD_PARAM_DEFAULT) { + data[0] = symbolNum; + } + + if(detPeak != RADIOLIB_SX126X_CAD_PARAM_DEFAULT) { + data[1] = detPeak; + } + + if(detMin != RADIOLIB_SX126X_CAD_PARAM_DEFAULT) { + data[2] = detMin; + } + + if(exitMode != RADIOLIB_SX126X_CAD_PARAM_DEFAULT) { + data[3] = exitMode; + } + + // configure parameters + int16_t state = this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_SET_CAD_PARAMS, data, 7); + RADIOLIB_ASSERT(state); + + // start CAD + return(this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_SET_CAD, NULL, 0)); +} + +int16_t SX126x::setPaConfig(uint8_t paDutyCycle, uint8_t deviceSel, uint8_t hpMax, uint8_t paLut) { + uint8_t data[] = { paDutyCycle, hpMax, deviceSel, paLut }; + return(this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_SET_PA_CONFIG, data, 4)); +} + +int16_t SX126x::writeRegister(uint16_t addr, uint8_t* data, uint8_t numBytes) { + this->mod->SPIwriteRegisterBurst(addr, data, numBytes); + return(RADIOLIB_ERR_NONE); +} + +int16_t SX126x::readRegister(uint16_t addr, uint8_t* data, uint8_t numBytes) { + // send the command + this->mod->SPIreadRegisterBurst(addr, numBytes, data); + + // check the status + int16_t state = this->mod->SPIcheckStream(); + return(state); +} + +int16_t SX126x::writeBuffer(uint8_t* data, uint8_t numBytes, uint8_t offset) { + uint8_t cmd[] = { RADIOLIB_SX126X_CMD_WRITE_BUFFER, offset }; + return(this->mod->SPIwriteStream(cmd, 2, data, numBytes)); +} + +int16_t SX126x::readBuffer(uint8_t* data, uint8_t numBytes, uint8_t offset) { + uint8_t cmd[] = { RADIOLIB_SX126X_CMD_READ_BUFFER, offset }; + return(this->mod->SPIreadStream(cmd, 2, data, numBytes)); +} + +int16_t SX126x::setDioIrqParams(uint16_t irqMask, uint16_t dio1Mask, uint16_t dio2Mask, uint16_t dio3Mask) { + uint8_t data[8] = {(uint8_t)((irqMask >> 8) & 0xFF), (uint8_t)(irqMask & 0xFF), + (uint8_t)((dio1Mask >> 8) & 0xFF), (uint8_t)(dio1Mask & 0xFF), + (uint8_t)((dio2Mask >> 8) & 0xFF), (uint8_t)(dio2Mask & 0xFF), + (uint8_t)((dio3Mask >> 8) & 0xFF), (uint8_t)(dio3Mask & 0xFF)}; + return(this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_SET_DIO_IRQ_PARAMS, data, 8)); +} + +int16_t SX126x::clearIrqStatus(uint16_t clearIrqParams) { + uint8_t data[] = { (uint8_t)((clearIrqParams >> 8) & 0xFF), (uint8_t)(clearIrqParams & 0xFF) }; + return(this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_CLEAR_IRQ_STATUS, data, 2)); +} + +int16_t SX126x::setRfFrequency(uint32_t frf) { + uint8_t data[] = { (uint8_t)((frf >> 24) & 0xFF), (uint8_t)((frf >> 16) & 0xFF), (uint8_t)((frf >> 8) & 0xFF), (uint8_t)(frf & 0xFF) }; + return(this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_SET_RF_FREQUENCY, data, 4)); +} + +int16_t SX126x::calibrateImage(float freq) { + uint8_t data[2] = { 0, 0 }; + + // try to match the frequency ranges + int freqBand = (int)freq; + if((freqBand >= 902) && (freqBand <= 928)) { + data[0] = RADIOLIB_SX126X_CAL_IMG_902_MHZ_1; + data[1] = RADIOLIB_SX126X_CAL_IMG_902_MHZ_2; + } else if((freqBand >= 863) && (freqBand <= 870)) { + data[0] = RADIOLIB_SX126X_CAL_IMG_863_MHZ_1; + data[1] = RADIOLIB_SX126X_CAL_IMG_863_MHZ_2; + } else if((freqBand >= 779) && (freqBand <= 787)) { + data[0] = RADIOLIB_SX126X_CAL_IMG_779_MHZ_1; + data[1] = RADIOLIB_SX126X_CAL_IMG_779_MHZ_2; + } else if((freqBand >= 470) && (freqBand <= 510)) { + data[0] = RADIOLIB_SX126X_CAL_IMG_470_MHZ_1; + data[1] = RADIOLIB_SX126X_CAL_IMG_470_MHZ_2; + } else if((freqBand >= 430) && (freqBand <= 440)) { + data[0] = RADIOLIB_SX126X_CAL_IMG_430_MHZ_1; + data[1] = RADIOLIB_SX126X_CAL_IMG_430_MHZ_2; + } + + int16_t state; + if(data[0]) { + // matched with predefined ranges, do the calibration + state = SX126x::calibrateImage(data); + + } else { + // if nothing matched, try custom calibration - the may or may not work + RADIOLIB_DEBUG_BASIC_PRINTLN("Failed to match predefined frequency range, trying custom"); + state = SX126x::calibrateImageRejection(freq - 4.0f, freq + 4.0f); + + } + + return(state); +} + +int16_t SX126x::calibrateImageRejection(float freqMin, float freqMax) { + // calculate the calibration coefficients and calibrate image + uint8_t data[] = { (uint8_t)floor((freqMin - 1.0f) / 4.0f), (uint8_t)ceil((freqMax + 1.0f) / 4.0f) }; + data[0] = (data[0] % 2) ? data[0] : data[0] - 1; + data[1] = (data[1] % 2) ? data[1] : data[1] + 1; + return(this->calibrateImage(data)); +} + +int16_t SX126x::setPaRampTime(uint8_t rampTime) { + return(this->setTxParams(this->pwr, rampTime)); +} + +int16_t SX126x::calibrateImage(uint8_t* data) { + int16_t state = this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_CALIBRATE_IMAGE, data, 2); + + // if something failed, show the device errors + #if RADIOLIB_DEBUG_BASIC + if(state != RADIOLIB_ERR_NONE) { + // unless mode is forced to standby, device errors will be 0 + standby(); + uint16_t errors = getDeviceErrors(); + RADIOLIB_DEBUG_BASIC_PRINTLN("Calibration failed, device errors: 0x%X", errors); + } + #endif + return(state); +} + +uint8_t SX126x::getPacketType() { + uint8_t data = 0xFF; + this->mod->SPIreadStream(RADIOLIB_SX126X_CMD_GET_PACKET_TYPE, &data, 1); + return(data); +} + +int16_t SX126x::setTxParams(uint8_t pwr, uint8_t rampTime) { + uint8_t data[] = { pwr, rampTime }; + int16_t state = this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_SET_TX_PARAMS, data, 2); + if(state == RADIOLIB_ERR_NONE) { + this->pwr = pwr; + } + return(state); +} + +int16_t SX126x::setPacketMode(uint8_t mode, uint8_t len) { + // check active modem + if(getPacketType() != RADIOLIB_SX126X_PACKET_TYPE_GFSK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // set requested packet mode + int16_t state = setPacketParamsFSK(this->preambleLengthFSK, this->preambleDetLength, this->crcTypeFSK, this->syncWordLength, this->addrComp, this->whitening, mode, len); + RADIOLIB_ASSERT(state); + + // update cached value + this->packetType = mode; + return(state); +} + +int16_t SX126x::setHeaderType(uint8_t hdrType, size_t len) { + // check active modem + if(getPacketType() != RADIOLIB_SX126X_PACKET_TYPE_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // set requested packet mode + int16_t state = setPacketParams(this->preambleLengthLoRa, this->crcTypeLoRa, len, hdrType, this->invertIQEnabled); + RADIOLIB_ASSERT(state); + + // update cached value + this->headerType = hdrType; + this->implicitLen = len; + + return(state); +} + +int16_t SX126x::setModulationParams(uint8_t sf, uint8_t bw, uint8_t cr, uint8_t ldro) { + // calculate symbol length and enable low data rate optimization, if auto-configuration is enabled + if(this->ldroAuto) { + float symbolLength = (float)(uint32_t(1) << this->spreadingFactor) / (float)this->bandwidthKhz; + if(symbolLength >= 16.0) { + this->ldrOptimize = RADIOLIB_SX126X_LORA_LOW_DATA_RATE_OPTIMIZE_ON; + } else { + this->ldrOptimize = RADIOLIB_SX126X_LORA_LOW_DATA_RATE_OPTIMIZE_OFF; + } + } else { + this->ldrOptimize = ldro; + } + // 500/9/8 - 0x09 0x04 0x03 0x00 - SF9, BW125, 4/8 + // 500/11/8 - 0x0B 0x04 0x03 0x00 - SF11 BW125, 4/7 + uint8_t data[4] = {sf, bw, cr, this->ldrOptimize}; + return(this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_SET_MODULATION_PARAMS, data, 4)); +} + +int16_t SX126x::setModulationParamsFSK(uint32_t br, uint8_t sh, uint8_t rxBw, uint32_t freqDev) { + uint8_t data[8] = {(uint8_t)((br >> 16) & 0xFF), (uint8_t)((br >> 8) & 0xFF), (uint8_t)(br & 0xFF), + sh, rxBw, + (uint8_t)((freqDev >> 16) & 0xFF), (uint8_t)((freqDev >> 8) & 0xFF), (uint8_t)(freqDev & 0xFF)}; + return(this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_SET_MODULATION_PARAMS, data, 8)); +} + +int16_t SX126x::setPacketParams(uint16_t preambleLen, uint8_t crcType, uint8_t payloadLen, uint8_t hdrType, uint8_t invertIQ) { + int16_t state = fixInvertedIQ(invertIQ); + RADIOLIB_ASSERT(state); + uint8_t data[6] = {(uint8_t)((preambleLen >> 8) & 0xFF), (uint8_t)(preambleLen & 0xFF), hdrType, payloadLen, crcType, invertIQ}; + return(this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_SET_PACKET_PARAMS, data, 6)); +} + +int16_t SX126x::setPacketParamsFSK(uint16_t preambleLen, uint8_t preambleDetectorLen, uint8_t crcType, uint8_t syncWordLen, uint8_t addrCmp, uint8_t whiten, uint8_t packType, uint8_t payloadLen) { + uint8_t data[9] = {(uint8_t)((preambleLen >> 8) & 0xFF), (uint8_t)(preambleLen & 0xFF), + preambleDetectorLen, syncWordLen, addrCmp, + packType, payloadLen, crcType, whiten}; + return(this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_SET_PACKET_PARAMS, data, 9)); +} + +int16_t SX126x::setBufferBaseAddress(uint8_t txBaseAddress, uint8_t rxBaseAddress) { + uint8_t data[2] = {txBaseAddress, rxBaseAddress}; + return(this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_SET_BUFFER_BASE_ADDRESS, data, 2)); +} + +int16_t SX126x::setRegulatorMode(uint8_t mode) { + uint8_t data[1] = {mode}; + return(this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_SET_REGULATOR_MODE, data, 1)); +} + +uint8_t SX126x::getStatus() { + uint8_t data = 0; + this->mod->SPIreadStream(RADIOLIB_SX126X_CMD_GET_STATUS, &data, 0); + return(data); +} + +uint32_t SX126x::getPacketStatus() { + uint8_t data[3] = {0, 0, 0}; + this->mod->SPIreadStream(RADIOLIB_SX126X_CMD_GET_PACKET_STATUS, data, 3); + return((((uint32_t)data[0]) << 16) | (((uint32_t)data[1]) << 8) | (uint32_t)data[2]); +} + +uint16_t SX126x::getDeviceErrors() { + uint8_t data[2] = {0, 0}; + this->mod->SPIreadStream(RADIOLIB_SX126X_CMD_GET_DEVICE_ERRORS, data, 2); + uint16_t opError = (((uint16_t)data[0] & 0xFF) << 8) | ((uint16_t)data[1]); + return(opError); +} + +int16_t SX126x::clearDeviceErrors() { + uint8_t data[2] = {RADIOLIB_SX126X_CMD_NOP, RADIOLIB_SX126X_CMD_NOP}; + return(this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_CLEAR_DEVICE_ERRORS, data, 2)); +} + +int16_t SX126x::setFrequencyRaw(float freq) { + // calculate raw value + this->freqMHz = freq; + uint32_t frf = (this->freqMHz * (uint32_t(1) << RADIOLIB_SX126X_DIV_EXPONENT)) / RADIOLIB_SX126X_CRYSTAL_FREQ; + return(setRfFrequency(frf)); +} + +int16_t SX126x::fixSensitivity() { + // fix receiver sensitivity for 500 kHz LoRa + // see SX1262/SX1268 datasheet, chapter 15 Known Limitations, section 15.1 for details + + // read current sensitivity configuration + uint8_t sensitivityConfig = 0; + int16_t state = readRegister(RADIOLIB_SX126X_REG_SENSITIVITY_CONFIG, &sensitivityConfig, 1); + RADIOLIB_ASSERT(state); + + // fix the value for LoRa with 500 kHz bandwidth + if((getPacketType() == RADIOLIB_SX126X_PACKET_TYPE_LORA) && (fabsf(this->bandwidthKhz - 500.0) <= 0.001)) { + sensitivityConfig &= 0xFB; + } else { + sensitivityConfig |= 0x04; + } + return(writeRegister(RADIOLIB_SX126X_REG_SENSITIVITY_CONFIG, &sensitivityConfig, 1)); +} + +int16_t SX126x::fixPaClamping(bool enable) { + // fixes overly eager PA clamping + // see SX1262/SX1268 datasheet, chapter 15 Known Limitations, section 15.2 for details + + // read current clamping configuration + uint8_t clampConfig = 0; + int16_t state = readRegister(RADIOLIB_SX126X_REG_TX_CLAMP_CONFIG, &clampConfig, 1); + RADIOLIB_ASSERT(state); + + // apply or undo workaround + if (enable) + clampConfig |= 0x1E; + else + clampConfig = (clampConfig & ~0x1E) | 0x08; + + return(writeRegister(RADIOLIB_SX126X_REG_TX_CLAMP_CONFIG, &clampConfig, 1)); +} + +int16_t SX126x::fixImplicitTimeout() { + // fixes timeout in implicit header mode + // see SX1262/SX1268 datasheet, chapter 15 Known Limitations, section 15.3 for details + + //check if we're in implicit LoRa mode + if(!((this->headerType == RADIOLIB_SX126X_LORA_HEADER_IMPLICIT) && (getPacketType() == RADIOLIB_SX126X_PACKET_TYPE_LORA))) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // stop RTC counter + uint8_t rtcStop = 0x00; + int16_t state = writeRegister(RADIOLIB_SX126X_REG_RTC_CTRL, &rtcStop, 1); + RADIOLIB_ASSERT(state); + + // read currently active event + uint8_t rtcEvent = 0; + state = readRegister(RADIOLIB_SX126X_REG_EVENT_MASK, &rtcEvent, 1); + RADIOLIB_ASSERT(state); + + // clear events + rtcEvent |= 0x02; + return(writeRegister(RADIOLIB_SX126X_REG_EVENT_MASK, &rtcEvent, 1)); +} + +int16_t SX126x::fixInvertedIQ(uint8_t iqConfig) { + // fixes IQ configuration for inverted IQ + // see SX1262/SX1268 datasheet, chapter 15 Known Limitations, section 15.4 for details + + // read current IQ configuration + uint8_t iqConfigCurrent = 0; + int16_t state = readRegister(RADIOLIB_SX126X_REG_IQ_CONFIG, &iqConfigCurrent, 1); + RADIOLIB_ASSERT(state); + + // set correct IQ configuration + if(iqConfig == RADIOLIB_SX126X_LORA_IQ_INVERTED) { + iqConfigCurrent &= 0xFB; + } else { + iqConfigCurrent |= 0x04; + } + + // update with the new value + return(writeRegister(RADIOLIB_SX126X_REG_IQ_CONFIG, &iqConfigCurrent, 1)); +} + +Module* SX126x::getMod() { + return(this->mod); +} + +int16_t SX126x::modSetup(float tcxoVoltage, bool useRegulatorLDO, uint8_t modem) { + // set module properties + this->mod->init(); + this->mod->hal->pinMode(this->mod->getIrq(), this->mod->hal->GpioModeInput); + this->mod->hal->pinMode(this->mod->getGpio(), this->mod->hal->GpioModeInput); + this->mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_ADDR] = Module::BITS_16; + this->mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_CMD] = Module::BITS_8; + this->mod->spiConfig.statusPos = 1; + this->mod->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_READ] = RADIOLIB_SX126X_CMD_READ_REGISTER; + this->mod->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_WRITE] = RADIOLIB_SX126X_CMD_WRITE_REGISTER; + this->mod->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_NOP] = RADIOLIB_SX126X_CMD_NOP; + this->mod->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_STATUS] = RADIOLIB_SX126X_CMD_GET_STATUS; + this->mod->spiConfig.stream = true; + this->mod->spiConfig.parseStatusCb = SPIparseStatus; + + // try to find the SX126x chip + if(!SX126x::findChip(this->chipType)) { + RADIOLIB_DEBUG_BASIC_PRINTLN("No SX126x found!"); + this->mod->term(); + return(RADIOLIB_ERR_CHIP_NOT_FOUND); + } + RADIOLIB_DEBUG_BASIC_PRINTLN("M\tSX126x"); + + // reset the module and verify startup + int16_t state = reset(); + RADIOLIB_ASSERT(state); + + // set mode to standby + state = standby(); + RADIOLIB_ASSERT(state); + + // set TCXO control, if requested + if(!this->XTAL && tcxoVoltage > 0.0) { + state = setTCXO(tcxoVoltage); + RADIOLIB_ASSERT(state); + } + + // configure settings not accessible by API + state = config(modem); + RADIOLIB_ASSERT(state); + + if (useRegulatorLDO) { + state = setRegulatorLDO(); + } else { + state = setRegulatorDCDC(); + } + return(state); +} + +int16_t SX126x::config(uint8_t modem) { + // reset buffer base address + int16_t state = setBufferBaseAddress(); + RADIOLIB_ASSERT(state); + + // set modem + uint8_t data[7]; + data[0] = modem; + state = this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_SET_PACKET_TYPE, data, 1); + RADIOLIB_ASSERT(state); + + // set Rx/Tx fallback mode to STDBY_RC + data[0] = this->standbyXOSC ? RADIOLIB_SX126X_RX_TX_FALLBACK_MODE_STDBY_XOSC : RADIOLIB_SX126X_RX_TX_FALLBACK_MODE_STDBY_RC; + state = this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_SET_RX_TX_FALLBACK_MODE, data, 1); + RADIOLIB_ASSERT(state); + + // set some CAD parameters - will be overwritten when calling CAD anyway + data[0] = RADIOLIB_SX126X_CAD_ON_8_SYMB; + data[1] = this->spreadingFactor + 13; + data[2] = RADIOLIB_SX126X_CAD_PARAM_DET_MIN; + data[3] = RADIOLIB_SX126X_CAD_GOTO_STDBY; + data[4] = 0x00; + data[5] = 0x00; + data[6] = 0x00; + state = this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_SET_CAD_PARAMS, data, 7); + RADIOLIB_ASSERT(state); + + // clear IRQ + state = clearIrqStatus(); + state |= setDioIrqParams(RADIOLIB_SX126X_IRQ_NONE, RADIOLIB_SX126X_IRQ_NONE); + RADIOLIB_ASSERT(state); + + // calibrate all blocks + data[0] = RADIOLIB_SX126X_CALIBRATE_ALL; + state = this->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_CALIBRATE, data, 1, true, false); + RADIOLIB_ASSERT(state); + + // wait for calibration completion + this->mod->hal->delay(5); + if (this->mod->getGpio() == RADIOLIB_NC) { + // TODO: We could probably keep some state so we know the chip + // is in sleep, since otherwise the delay can be much shorter. + // Also, all delays after commands (rather than waking up from + // sleep) are measured from the *end* of the previous SPI + // transaction, so we could wait shorter if we remember when + // that was. + this->mod->hal->delayMicroseconds(MAX_BUSY_TIME); + } else { + while(this->mod->hal->digitalRead(this->mod->getGpio())) { + this->mod->hal->yield(); + } + } + + // check calibration result + state = this->mod->SPIcheckStream(); + + // if something failed, show the device errors + #if RADIOLIB_DEBUG_BASIC + if(state != RADIOLIB_ERR_NONE) { + // unless mode is forced to standby, device errors will be 0 + standby(); + uint16_t errors = getDeviceErrors(); + RADIOLIB_DEBUG_BASIC_PRINTLN("Calibration failed, device errors: 0x%X", errors); + } + #endif + + return(state); +} + +int16_t SX126x::SPIparseStatus(uint8_t in) { + if((in & 0b00001110) == RADIOLIB_SX126X_STATUS_CMD_TIMEOUT) { + return(RADIOLIB_ERR_SPI_CMD_TIMEOUT); + } else if((in & 0b00001110) == RADIOLIB_SX126X_STATUS_CMD_INVALID) { + return(RADIOLIB_ERR_SPI_CMD_INVALID); + } else if((in & 0b00001110) == RADIOLIB_SX126X_STATUS_CMD_FAILED) { + return(RADIOLIB_ERR_SPI_CMD_FAILED); + } else if((in == 0x00) || (in == 0xFF)) { + return(RADIOLIB_ERR_CHIP_NOT_FOUND); + } + return(RADIOLIB_ERR_NONE); +} + +bool SX126x::findChip(const char* verStr) { + uint8_t i = 0; + bool flagFound = false; + while((i < 10) && !flagFound) { + // reset the module + reset(); + + // read the version string + char version[16]; + this->mod->SPIreadRegisterBurst(RADIOLIB_SX126X_REG_VERSION_STRING, 16, reinterpret_cast(version)); + + // check version register + if(strncmp(verStr, version, 6) == 0) { + RADIOLIB_DEBUG_BASIC_PRINTLN("Found SX126x: RADIOLIB_SX126X_REG_VERSION_STRING:"); + RADIOLIB_DEBUG_BASIC_HEXDUMP(reinterpret_cast(version), 16, RADIOLIB_SX126X_REG_VERSION_STRING); + RADIOLIB_DEBUG_BASIC_PRINTLN(); + flagFound = true; + } else { + #if RADIOLIB_DEBUG_BASIC + RADIOLIB_DEBUG_BASIC_PRINTLN("SX126x not found! (%d of 10 tries) RADIOLIB_SX126X_REG_VERSION_STRING:", i + 1); + RADIOLIB_DEBUG_BASIC_HEXDUMP(reinterpret_cast(version), 16, RADIOLIB_SX126X_REG_VERSION_STRING); + RADIOLIB_DEBUG_BASIC_PRINTLN("Expected string: %s", verStr); + #endif + this->mod->hal->delay(10); + i++; + } + } + + return(flagFound); +} + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/SX126x/SX126x.h b/software/firmware/source/libraries/RadioLib/src/modules/SX126x/SX126x.h new file mode 100644 index 000000000..d1ca5451f --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/SX126x/SX126x.h @@ -0,0 +1,1337 @@ +#if !defined(_RADIOLIB_SX126X_H) +#define _RADIOLIB_SX126X_H + +#include "../../TypeDef.h" + +#if !RADIOLIB_EXCLUDE_SX126X + +#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 +#define RADIOLIB_SX126X_MAX_PACKET_LENGTH 255 +#define RADIOLIB_SX126X_CRYSTAL_FREQ 32.0 +#define RADIOLIB_SX126X_DIV_EXPONENT 25 + +// SX126X SPI commands +// operational modes commands +#define RADIOLIB_SX126X_CMD_NOP 0x00 +#define RADIOLIB_SX126X_CMD_SET_SLEEP 0x84 +#define RADIOLIB_SX126X_CMD_SET_STANDBY 0x80 +#define RADIOLIB_SX126X_CMD_SET_FS 0xC1 +#define RADIOLIB_SX126X_CMD_SET_TX 0x83 +#define RADIOLIB_SX126X_CMD_SET_RX 0x82 +#define RADIOLIB_SX126X_CMD_STOP_TIMER_ON_PREAMBLE 0x9F +#define RADIOLIB_SX126X_CMD_SET_RX_DUTY_CYCLE 0x94 +#define RADIOLIB_SX126X_CMD_SET_CAD 0xC5 +#define RADIOLIB_SX126X_CMD_SET_TX_CONTINUOUS_WAVE 0xD1 +#define RADIOLIB_SX126X_CMD_SET_TX_INFINITE_PREAMBLE 0xD2 +#define RADIOLIB_SX126X_CMD_SET_REGULATOR_MODE 0x96 +#define RADIOLIB_SX126X_CMD_CALIBRATE 0x89 +#define RADIOLIB_SX126X_CMD_CALIBRATE_IMAGE 0x98 +#define RADIOLIB_SX126X_CMD_SET_PA_CONFIG 0x95 +#define RADIOLIB_SX126X_CMD_SET_RX_TX_FALLBACK_MODE 0x93 + +// register and buffer access commands +#define RADIOLIB_SX126X_CMD_WRITE_REGISTER 0x0D +#define RADIOLIB_SX126X_CMD_READ_REGISTER 0x1D +#define RADIOLIB_SX126X_CMD_WRITE_BUFFER 0x0E +#define RADIOLIB_SX126X_CMD_READ_BUFFER 0x1E + +// DIO and IRQ control +#define RADIOLIB_SX126X_CMD_SET_DIO_IRQ_PARAMS 0x08 +#define RADIOLIB_SX126X_CMD_GET_IRQ_STATUS 0x12 +#define RADIOLIB_SX126X_CMD_CLEAR_IRQ_STATUS 0x02 +#define RADIOLIB_SX126X_CMD_SET_DIO2_AS_RF_SWITCH_CTRL 0x9D +#define RADIOLIB_SX126X_CMD_SET_DIO3_AS_TCXO_CTRL 0x97 + +// RF, modulation and packet commands +#define RADIOLIB_SX126X_CMD_SET_RF_FREQUENCY 0x86 +#define RADIOLIB_SX126X_CMD_SET_PACKET_TYPE 0x8A +#define RADIOLIB_SX126X_CMD_GET_PACKET_TYPE 0x11 +#define RADIOLIB_SX126X_CMD_SET_TX_PARAMS 0x8E +#define RADIOLIB_SX126X_CMD_SET_MODULATION_PARAMS 0x8B +#define RADIOLIB_SX126X_CMD_SET_PACKET_PARAMS 0x8C +#define RADIOLIB_SX126X_CMD_SET_CAD_PARAMS 0x88 +#define RADIOLIB_SX126X_CMD_SET_BUFFER_BASE_ADDRESS 0x8F +#define RADIOLIB_SX126X_CMD_SET_LORA_SYMB_NUM_TIMEOUT 0xA0 + +// status commands +#define RADIOLIB_SX126X_CMD_GET_STATUS 0xC0 +#define RADIOLIB_SX126X_CMD_GET_RSSI_INST 0x15 +#define RADIOLIB_SX126X_CMD_GET_RX_BUFFER_STATUS 0x13 +#define RADIOLIB_SX126X_CMD_GET_PACKET_STATUS 0x14 +#define RADIOLIB_SX126X_CMD_GET_DEVICE_ERRORS 0x17 +#define RADIOLIB_SX126X_CMD_CLEAR_DEVICE_ERRORS 0x07 +#define RADIOLIB_SX126X_CMD_GET_STATS 0x10 +#define RADIOLIB_SX126X_CMD_RESET_STATS 0x00 + +#define RADIOLIB_SX126X_CMD_PRAM_UPDATE 0xD9 +#define RADIOLIB_SX126X_CMD_SET_LBT_SCAN_PARAMS 0x9A +#define RADIOLIB_SX126X_CMD_SET_SPECTR_SCAN_PARAMS 0x9B + +// SX126X register map +#define RADIOLIB_SX126X_REG_RX_GAIN_RETENTION_0 0x029F // SX1268 datasheet v1.1, section 9.6 +#define RADIOLIB_SX126X_REG_RX_GAIN_RETENTION_1 0x02A0 // SX1268 datasheet v1.1, section 9.6 +#define RADIOLIB_SX126X_REG_RX_GAIN_RETENTION_2 0x02A1 // SX1268 datasheet v1.1, section 9.6 +#define RADIOLIB_SX126X_REG_VERSION_STRING 0x0320 +#define RADIOLIB_SX126X_REG_HOPPING_ENABLE 0x0385 +#define RADIOLIB_SX126X_REG_LR_FHSS_PACKET_LENGTH 0x0386 +#define RADIOLIB_SX126X_REG_LR_FHSS_NUM_HOPPING_BLOCKS 0x0387 +#define RADIOLIB_SX126X_REG_LR_FHSS_NUM_SYMBOLS_FREQX_MSB(X) (0x0388 + (X)*6) +#define RADIOLIB_SX126X_REG_LR_FHSS_NUM_SYMBOLS_FREQX_LSB(X) (0x0389 + (X)*6) +#define RADIOLIB_SX126X_REG_LR_FHSS_FREQX_0(X) (0x038A + (X)*6) +#define RADIOLIB_SX126X_REG_LR_FHSS_FREQX_1(X) (0x038B + (X)*6) +#define RADIOLIB_SX126X_REG_LR_FHSS_FREQX_2(X) (0x038C + (X)*6) +#define RADIOLIB_SX126X_REG_LR_FHSS_FREQX_3(X) (0x038D + (X)*6) +#define RADIOLIB_SX126X_REG_SPECTRAL_SCAN_RESULT 0x0401 +#define RADIOLIB_SX126X_REG_DIOX_OUT_ENABLE 0x0580 +#define RADIOLIB_SX126X_REG_DIOX_DRIVE_STRENGTH 0x0582 +#define RADIOLIB_SX126X_REG_DIOX_IN_ENABLE 0x0583 +#define RADIOLIB_SX126X_REG_DIOX_PULL_UP_CTRL 0x0584 +#define RADIOLIB_SX126X_REG_DIOX_PULL_DOWN_CTRL 0x0585 +#define RADIOLIB_SX126X_REG_TX_BITBANG_ENABLE_0 0x0587 +#define RADIOLIB_SX126X_REG_PATCH_UPDATE_ENABLE 0x0610 +#define RADIOLIB_SX126X_REG_TX_BITBANG_ENABLE_1 0x0680 +#define RADIOLIB_SX126X_REG_WHITENING_INITIAL_MSB 0x06B8 +#define RADIOLIB_SX126X_REG_WHITENING_INITIAL_LSB 0x06B9 +#define RADIOLIB_SX126X_REG_RX_TX_PLD_LEN 0x06BB +#define RADIOLIB_SX126X_REG_CRC_INITIAL_MSB 0x06BC +#define RADIOLIB_SX126X_REG_CRC_INITIAL_LSB 0x06BD +#define RADIOLIB_SX126X_REG_CRC_POLYNOMIAL_MSB 0x06BE +#define RADIOLIB_SX126X_REG_CRC_POLYNOMIAL_LSB 0x06BF +#define RADIOLIB_SX126X_REG_SYNC_WORD_0 0x06C0 +#define RADIOLIB_SX126X_REG_SYNC_WORD_1 0x06C1 +#define RADIOLIB_SX126X_REG_SYNC_WORD_2 0x06C2 +#define RADIOLIB_SX126X_REG_SYNC_WORD_3 0x06C3 +#define RADIOLIB_SX126X_REG_SYNC_WORD_4 0x06C4 +#define RADIOLIB_SX126X_REG_SYNC_WORD_5 0x06C5 +#define RADIOLIB_SX126X_REG_SYNC_WORD_6 0x06C6 +#define RADIOLIB_SX126X_REG_SYNC_WORD_7 0x06C7 +#define RADIOLIB_SX126X_REG_NODE_ADDRESS 0x06CD +#define RADIOLIB_SX126X_REG_BROADCAST_ADDRESS 0x06CE +#define RADIOLIB_SX126X_REG_PAYLOAD_LENGTH 0x0702 +#define RADIOLIB_SX126X_REG_PACKET_PARAMS 0x0704 +#define RADIOLIB_SX126X_REG_LORA_SYNC_TIMEOUT 0x0706 +#define RADIOLIB_SX126X_REG_IQ_CONFIG 0x0736 +#define RADIOLIB_SX126X_REG_LORA_SYNC_WORD_MSB 0x0740 +#define RADIOLIB_SX126X_REG_LORA_SYNC_WORD_LSB 0x0741 +#define RADIOLIB_SX126X_REG_FREQ_ERROR 0x076B +#define RADIOLIB_SX126X_REG_SPECTRAL_SCAN_STATUS 0x07CD +#define RADIOLIB_SX126X_REG_RX_ADDR_PTR 0x0803 +#define RADIOLIB_SX126X_REG_RANDOM_NUMBER_0 0x0819 +#define RADIOLIB_SX126X_REG_RANDOM_NUMBER_1 0x081A +#define RADIOLIB_SX126X_REG_RANDOM_NUMBER_2 0x081B +#define RADIOLIB_SX126X_REG_RANDOM_NUMBER_3 0x081C +#define RADIOLIB_SX126X_REG_SENSITIVITY_CONFIG 0x0889 // SX1268 datasheet v1.1, section 15.1 +#define RADIOLIB_SX126X_REG_RF_FREQUENCY_0 0x088B +#define RADIOLIB_SX126X_REG_RF_FREQUENCY_1 0x088C +#define RADIOLIB_SX126X_REG_RF_FREQUENCY_2 0x088D +#define RADIOLIB_SX126X_REG_RF_FREQUENCY_3 0x088E +#define RADIOLIB_SX126X_REG_RSSI_AVG_WINDOW 0x089B +#define RADIOLIB_SX126X_REG_RX_GAIN 0x08AC +#define RADIOLIB_SX126X_REG_TX_CLAMP_CONFIG 0x08D8 +#define RADIOLIB_SX126X_REG_ANA_LNA 0x08E2 +#define RADIOLIB_SX126X_REG_LNA_CAP_TUNE_N 0x08E3 +#define RADIOLIB_SX126X_REG_LNA_CAP_TUNE_P 0x08E4 +#define RADIOLIB_SX126X_REG_ANA_MIXER 0x08E5 +#define RADIOLIB_SX126X_REG_OCP_CONFIGURATION 0x08E7 +#define RADIOLIB_SX126X_REG_RTC_CTRL 0x0902 +#define RADIOLIB_SX126X_REG_XTA_TRIM 0x0911 +#define RADIOLIB_SX126X_REG_XTB_TRIM 0x0912 +#define RADIOLIB_SX126X_REG_DIO3_OUT_VOLTAGE_CTRL 0x0920 +#define RADIOLIB_SX126X_REG_EVENT_MASK 0x0944 +#define RADIOLIB_SX126X_REG_PATCH_MEMORY_BASE 0x8000 + +// SX126X SPI command variables +//RADIOLIB_SX126X_CMD_SET_SLEEP MSB LSB DESCRIPTION +#define RADIOLIB_SX126X_SLEEP_START_COLD 0b00000000 // 2 2 sleep mode: cold start, configuration is lost (default) +#define RADIOLIB_SX126X_SLEEP_START_WARM 0b00000100 // 2 2 warm start, configuration is retained +#define RADIOLIB_SX126X_SLEEP_RTC_OFF 0b00000000 // 0 0 wake on RTC timeout: disabled +#define RADIOLIB_SX126X_SLEEP_RTC_ON 0b00000001 // 0 0 enabled + +//RADIOLIB_SX126X_CMD_SET_STANDBY +#define RADIOLIB_SX126X_STANDBY_RC 0x00 // 7 0 standby mode: 13 MHz RC oscillator +#define RADIOLIB_SX126X_STANDBY_XOSC 0x01 // 7 0 32 MHz crystal oscillator + +//RADIOLIB_SX126X_CMD_SET_RX +#define RADIOLIB_SX126X_RX_TIMEOUT_NONE 0x000000 // 23 0 Rx timeout duration: no timeout (Rx single mode) +#define RADIOLIB_SX126X_RX_TIMEOUT_INF 0xFFFFFF // 23 0 infinite (Rx continuous mode) + +//RADIOLIB_SX126X_CMD_SET_TX +#define RADIOLIB_SX126X_TX_TIMEOUT_NONE 0x000000 // 23 0 Tx timeout duration: no timeout (Tx single mode) + +//RADIOLIB_SX126X_CMD_STOP_TIMER_ON_PREAMBLE +#define RADIOLIB_SX126X_STOP_ON_PREAMBLE_OFF 0x00 // 7 0 stop timer on: sync word or header (default) +#define RADIOLIB_SX126X_STOP_ON_PREAMBLE_ON 0x01 // 7 0 preamble detection + +//RADIOLIB_SX126X_CMD_SET_REGULATOR_MODE +#define RADIOLIB_SX126X_REGULATOR_LDO 0x00 // 7 0 set regulator mode: LDO (default) +#define RADIOLIB_SX126X_REGULATOR_DC_DC 0x01 // 7 0 DC-DC + +//RADIOLIB_SX126X_CMD_CALIBRATE +#define RADIOLIB_SX126X_CALIBRATE_IMAGE_OFF 0b00000000 // 6 6 image calibration: disabled +#define RADIOLIB_SX126X_CALIBRATE_IMAGE_ON 0b01000000 // 6 6 enabled +#define RADIOLIB_SX126X_CALIBRATE_ADC_BULK_P_OFF 0b00000000 // 5 5 ADC bulk P calibration: disabled +#define RADIOLIB_SX126X_CALIBRATE_ADC_BULK_P_ON 0b00100000 // 5 5 enabled +#define RADIOLIB_SX126X_CALIBRATE_ADC_BULK_N_OFF 0b00000000 // 4 4 ADC bulk N calibration: disabled +#define RADIOLIB_SX126X_CALIBRATE_ADC_BULK_N_ON 0b00010000 // 4 4 enabled +#define RADIOLIB_SX126X_CALIBRATE_ADC_PULSE_OFF 0b00000000 // 3 3 ADC pulse calibration: disabled +#define RADIOLIB_SX126X_CALIBRATE_ADC_PULSE_ON 0b00001000 // 3 3 enabled +#define RADIOLIB_SX126X_CALIBRATE_PLL_OFF 0b00000000 // 2 2 PLL calibration: disabled +#define RADIOLIB_SX126X_CALIBRATE_PLL_ON 0b00000100 // 2 2 enabled +#define RADIOLIB_SX126X_CALIBRATE_RC13M_OFF 0b00000000 // 1 1 13 MHz RC osc. calibration: disabled +#define RADIOLIB_SX126X_CALIBRATE_RC13M_ON 0b00000010 // 1 1 enabled +#define RADIOLIB_SX126X_CALIBRATE_RC64K_OFF 0b00000000 // 0 0 64 kHz RC osc. calibration: disabled +#define RADIOLIB_SX126X_CALIBRATE_RC64K_ON 0b00000001 // 0 0 enabled +#define RADIOLIB_SX126X_CALIBRATE_ALL 0b01111111 // 6 0 calibrate all blocks + +//RADIOLIB_SX126X_CMD_CALIBRATE_IMAGE +#define RADIOLIB_SX126X_CAL_IMG_430_MHZ_1 0x6B +#define RADIOLIB_SX126X_CAL_IMG_430_MHZ_2 0x6F +#define RADIOLIB_SX126X_CAL_IMG_470_MHZ_1 0x75 +#define RADIOLIB_SX126X_CAL_IMG_470_MHZ_2 0x81 +#define RADIOLIB_SX126X_CAL_IMG_779_MHZ_1 0xC1 +#define RADIOLIB_SX126X_CAL_IMG_779_MHZ_2 0xC5 +#define RADIOLIB_SX126X_CAL_IMG_863_MHZ_1 0xD7 +#define RADIOLIB_SX126X_CAL_IMG_863_MHZ_2 0xDB +#define RADIOLIB_SX126X_CAL_IMG_902_MHZ_1 0xE1 +#define RADIOLIB_SX126X_CAL_IMG_902_MHZ_2 0xE9 +#define RADIOLIB_SX126X_CAL_IMG_FREQ_TRIG_MHZ (20.0) + +//RADIOLIB_SX126X_CMD_SET_PA_CONFIG +#define RADIOLIB_SX126X_PA_CONFIG_HP_MAX 0x07 +#define RADIOLIB_SX126X_PA_CONFIG_PA_LUT 0x01 +#define RADIOLIB_SX126X_PA_CONFIG_SX1262_8 0x00 + +//RADIOLIB_SX126X_CMD_SET_RX_TX_FALLBACK_MODE +#define RADIOLIB_SX126X_RX_TX_FALLBACK_MODE_FS 0x40 // 7 0 after Rx/Tx go to: FS mode +#define RADIOLIB_SX126X_RX_TX_FALLBACK_MODE_STDBY_XOSC 0x30 // 7 0 standby with crystal oscillator +#define RADIOLIB_SX126X_RX_TX_FALLBACK_MODE_STDBY_RC 0x20 // 7 0 standby with RC oscillator (default) + +//RADIOLIB_SX126X_CMD_SET_DIO_IRQ_PARAMS +#define RADIOLIB_SX126X_IRQ_LR_FHSS_HOP 0b0100000000000000 // 14 14 PA ramped up during LR-FHSS hop +#define RADIOLIB_SX126X_IRQ_TIMEOUT 0b0000001000000000 // 9 9 Rx or Tx timeout +#define RADIOLIB_SX126X_IRQ_CAD_DETECTED 0b0000000100000000 // 8 8 channel activity detected +#define RADIOLIB_SX126X_IRQ_CAD_DONE 0b0000000010000000 // 7 7 channel activity detection finished +#define RADIOLIB_SX126X_IRQ_CRC_ERR 0b0000000001000000 // 6 6 wrong CRC received +#define RADIOLIB_SX126X_IRQ_HEADER_ERR 0b0000000000100000 // 5 5 LoRa header CRC error +#define RADIOLIB_SX126X_IRQ_HEADER_VALID 0b0000000000010000 // 4 4 valid LoRa header received +#define RADIOLIB_SX126X_IRQ_SYNC_WORD_VALID 0b0000000000001000 // 3 3 valid sync word detected +#define RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED 0b0000000000000100 // 2 2 preamble detected +#define RADIOLIB_SX126X_IRQ_RX_DONE 0b0000000000000010 // 1 1 packet received +#define RADIOLIB_SX126X_IRQ_TX_DONE 0b0000000000000001 // 0 0 packet transmission completed +#define RADIOLIB_SX126X_IRQ_ALL 0b0100001111111111 // 14 0 all interrupts +#define RADIOLIB_SX126X_IRQ_NONE 0b0000000000000000 // 14 0 no interrupts + +//RADIOLIB_SX126X_CMD_SET_DIO2_AS_RF_SWITCH_CTRL +#define RADIOLIB_SX126X_DIO2_AS_IRQ 0x00 // 7 0 DIO2 configuration: IRQ +#define RADIOLIB_SX126X_DIO2_AS_RF_SWITCH 0x01 // 7 0 RF switch control + +//RADIOLIB_SX126X_CMD_SET_DIO3_AS_TCXO_CTRL +#define RADIOLIB_SX126X_DIO3_OUTPUT_1_6 0x00 // 7 0 DIO3 voltage output for TCXO: 1.6 V +#define RADIOLIB_SX126X_DIO3_OUTPUT_1_7 0x01 // 7 0 1.7 V +#define RADIOLIB_SX126X_DIO3_OUTPUT_1_8 0x02 // 7 0 1.8 V +#define RADIOLIB_SX126X_DIO3_OUTPUT_2_2 0x03 // 7 0 2.2 V +#define RADIOLIB_SX126X_DIO3_OUTPUT_2_4 0x04 // 7 0 2.4 V +#define RADIOLIB_SX126X_DIO3_OUTPUT_2_7 0x05 // 7 0 2.7 V +#define RADIOLIB_SX126X_DIO3_OUTPUT_3_0 0x06 // 7 0 3.0 V +#define RADIOLIB_SX126X_DIO3_OUTPUT_3_3 0x07 // 7 0 3.3 V + +//RADIOLIB_SX126X_CMD_SET_PACKET_TYPE +#define RADIOLIB_SX126X_PACKET_TYPE_GFSK 0x00 // 7 0 packet type: GFSK +#define RADIOLIB_SX126X_PACKET_TYPE_LORA 0x01 // 7 0 LoRa +#define RADIOLIB_SX126X_PACKET_TYPE_LR_FHSS 0x03 // 7 0 LR-FHSS + +//RADIOLIB_SX126X_CMD_SET_TX_PARAMS +#define RADIOLIB_SX126X_PA_RAMP_10U 0x00 // 7 0 ramp time: 10 us +#define RADIOLIB_SX126X_PA_RAMP_20U 0x01 // 7 0 20 us +#define RADIOLIB_SX126X_PA_RAMP_40U 0x02 // 7 0 40 us +#define RADIOLIB_SX126X_PA_RAMP_80U 0x03 // 7 0 80 us +#define RADIOLIB_SX126X_PA_RAMP_200U 0x04 // 7 0 200 us +#define RADIOLIB_SX126X_PA_RAMP_800U 0x05 // 7 0 800 us +#define RADIOLIB_SX126X_PA_RAMP_1700U 0x06 // 7 0 1700 us +#define RADIOLIB_SX126X_PA_RAMP_3400U 0x07 // 7 0 3400 us + +//RADIOLIB_SX126X_CMD_SET_MODULATION_PARAMS +#define RADIOLIB_SX126X_GFSK_FILTER_NONE 0x00 // 7 0 GFSK filter: none +#define RADIOLIB_SX126X_GFSK_FILTER_GAUSS_0_3 0x08 // 7 0 Gaussian, BT = 0.3 +#define RADIOLIB_SX126X_GFSK_FILTER_GAUSS_0_5 0x09 // 7 0 Gaussian, BT = 0.5 +#define RADIOLIB_SX126X_GFSK_FILTER_GAUSS_0_7 0x0A // 7 0 Gaussian, BT = 0.7 +#define RADIOLIB_SX126X_GFSK_FILTER_GAUSS_1 0x0B // 7 0 Gaussian, BT = 1 +#define RADIOLIB_SX126X_GFSK_RX_BW_4_8 0x1F // 7 0 GFSK Rx bandwidth: 4.8 kHz +#define RADIOLIB_SX126X_GFSK_RX_BW_5_8 0x17 // 7 0 5.8 kHz +#define RADIOLIB_SX126X_GFSK_RX_BW_7_3 0x0F // 7 0 7.3 kHz +#define RADIOLIB_SX126X_GFSK_RX_BW_9_7 0x1E // 7 0 9.7 kHz +#define RADIOLIB_SX126X_GFSK_RX_BW_11_7 0x16 // 7 0 11.7 kHz +#define RADIOLIB_SX126X_GFSK_RX_BW_14_6 0x0E // 7 0 14.6 kHz +#define RADIOLIB_SX126X_GFSK_RX_BW_19_5 0x1D // 7 0 19.5 kHz +#define RADIOLIB_SX126X_GFSK_RX_BW_23_4 0x15 // 7 0 23.4 kHz +#define RADIOLIB_SX126X_GFSK_RX_BW_29_3 0x0D // 7 0 29.3 kHz +#define RADIOLIB_SX126X_GFSK_RX_BW_39_0 0x1C // 7 0 39.0 kHz +#define RADIOLIB_SX126X_GFSK_RX_BW_46_9 0x14 // 7 0 46.9 kHz +#define RADIOLIB_SX126X_GFSK_RX_BW_58_6 0x0C // 7 0 58.6 kHz +#define RADIOLIB_SX126X_GFSK_RX_BW_78_2 0x1B // 7 0 78.2 kHz +#define RADIOLIB_SX126X_GFSK_RX_BW_93_8 0x13 // 7 0 93.8 kHz +#define RADIOLIB_SX126X_GFSK_RX_BW_117_3 0x0B // 7 0 117.3 kHz +#define RADIOLIB_SX126X_GFSK_RX_BW_156_2 0x1A // 7 0 156.2 kHz +#define RADIOLIB_SX126X_GFSK_RX_BW_187_2 0x12 // 7 0 187.2 kHz +#define RADIOLIB_SX126X_GFSK_RX_BW_234_3 0x0A // 7 0 234.3 kHz +#define RADIOLIB_SX126X_GFSK_RX_BW_312_0 0x19 // 7 0 312.0 kHz +#define RADIOLIB_SX126X_GFSK_RX_BW_373_6 0x11 // 7 0 373.6 kHz +#define RADIOLIB_SX126X_GFSK_RX_BW_467_0 0x09 // 7 0 467.0 kHz +#define RADIOLIB_SX126X_LORA_BW_7_8 0x00 // 7 0 LoRa bandwidth: 7.8 kHz +#define RADIOLIB_SX126X_LORA_BW_10_4 0x08 // 7 0 10.4 kHz +#define RADIOLIB_SX126X_LORA_BW_15_6 0x01 // 7 0 15.6 kHz +#define RADIOLIB_SX126X_LORA_BW_20_8 0x09 // 7 0 20.8 kHz +#define RADIOLIB_SX126X_LORA_BW_31_25 0x02 // 7 0 31.25 kHz +#define RADIOLIB_SX126X_LORA_BW_41_7 0x0A // 7 0 41.7 kHz +#define RADIOLIB_SX126X_LORA_BW_62_5 0x03 // 7 0 62.5 kHz +#define RADIOLIB_SX126X_LORA_BW_125_0 0x04 // 7 0 125.0 kHz +#define RADIOLIB_SX126X_LORA_BW_250_0 0x05 // 7 0 250.0 kHz +#define RADIOLIB_SX126X_LORA_BW_500_0 0x06 // 7 0 500.0 kHz +#define RADIOLIB_SX126X_LORA_CR_4_5 0x01 // 7 0 LoRa coding rate: 4/5 +#define RADIOLIB_SX126X_LORA_CR_4_6 0x02 // 7 0 4/6 +#define RADIOLIB_SX126X_LORA_CR_4_7 0x03 // 7 0 4/7 +#define RADIOLIB_SX126X_LORA_CR_4_8 0x04 // 7 0 4/8 +#define RADIOLIB_SX126X_LORA_LOW_DATA_RATE_OPTIMIZE_OFF 0x00 // 7 0 LoRa low data rate optimization: disabled +#define RADIOLIB_SX126X_LORA_LOW_DATA_RATE_OPTIMIZE_ON 0x01 // 7 0 enabled + +//RADIOLIB_SX126X_CMD_SET_PACKET_PARAMS +#define RADIOLIB_SX126X_GFSK_PREAMBLE_DETECT_OFF 0x00 // 7 0 GFSK minimum preamble length before reception starts: detector disabled +#define RADIOLIB_SX126X_GFSK_PREAMBLE_DETECT_8 0x04 // 7 0 8 bits +#define RADIOLIB_SX126X_GFSK_PREAMBLE_DETECT_16 0x05 // 7 0 16 bits +#define RADIOLIB_SX126X_GFSK_PREAMBLE_DETECT_24 0x06 // 7 0 24 bits +#define RADIOLIB_SX126X_GFSK_PREAMBLE_DETECT_32 0x07 // 7 0 32 bits +#define RADIOLIB_SX126X_GFSK_ADDRESS_FILT_OFF 0x00 // 7 0 GFSK address filtering: disabled +#define RADIOLIB_SX126X_GFSK_ADDRESS_FILT_NODE 0x01 // 7 0 node only +#define RADIOLIB_SX126X_GFSK_ADDRESS_FILT_NODE_BROADCAST 0x02 // 7 0 node and broadcast +#define RADIOLIB_SX126X_GFSK_PACKET_FIXED 0x00 // 7 0 GFSK packet type: fixed (payload length known in advance to both sides) +#define RADIOLIB_SX126X_GFSK_PACKET_VARIABLE 0x01 // 7 0 variable (payload length added to packet) +#define RADIOLIB_SX126X_GFSK_CRC_OFF 0x01 // 7 0 GFSK packet CRC: disabled +#define RADIOLIB_SX126X_GFSK_CRC_1_BYTE 0x00 // 7 0 1 byte +#define RADIOLIB_SX126X_GFSK_CRC_2_BYTE 0x02 // 7 0 2 byte +#define RADIOLIB_SX126X_GFSK_CRC_1_BYTE_INV 0x04 // 7 0 1 byte, inverted +#define RADIOLIB_SX126X_GFSK_CRC_2_BYTE_INV 0x06 // 7 0 2 byte, inverted +#define RADIOLIB_SX126X_GFSK_WHITENING_OFF 0x00 // 7 0 GFSK data whitening: disabled +#define RADIOLIB_SX126X_GFSK_WHITENING_ON 0x01 // 7 0 enabled +#define RADIOLIB_SX126X_LORA_HEADER_EXPLICIT 0x00 // 7 0 LoRa header mode: explicit +#define RADIOLIB_SX126X_LORA_HEADER_IMPLICIT 0x01 // 7 0 implicit +#define RADIOLIB_SX126X_LORA_CRC_OFF 0x00 // 7 0 LoRa CRC mode: disabled +#define RADIOLIB_SX126X_LORA_CRC_ON 0x01 // 7 0 enabled +#define RADIOLIB_SX126X_LORA_IQ_STANDARD 0x00 // 7 0 LoRa IQ setup: standard +#define RADIOLIB_SX126X_LORA_IQ_INVERTED 0x01 // 7 0 inverted + +//RADIOLIB_SX126X_CMD_SET_CAD_PARAMS +#define RADIOLIB_SX126X_CAD_ON_1_SYMB 0x00 // 7 0 number of symbols used for CAD: 1 +#define RADIOLIB_SX126X_CAD_ON_2_SYMB 0x01 // 7 0 2 +#define RADIOLIB_SX126X_CAD_ON_4_SYMB 0x02 // 7 0 4 +#define RADIOLIB_SX126X_CAD_ON_8_SYMB 0x03 // 7 0 8 +#define RADIOLIB_SX126X_CAD_ON_16_SYMB 0x04 // 7 0 16 +#define RADIOLIB_SX126X_CAD_GOTO_STDBY 0x00 // 7 0 after CAD is done, always go to STDBY_RC mode +#define RADIOLIB_SX126X_CAD_GOTO_RX 0x01 // 7 0 after CAD is done, go to Rx mode if activity is detected +#define RADIOLIB_SX126X_CAD_PARAM_DEFAULT 0xFF // 7 0 used by the CAD methods to specify default parameter value +#define RADIOLIB_SX126X_CAD_PARAM_DET_MIN 10 // 7 0 default detMin CAD parameter + +//RADIOLIB_SX126X_CMD_GET_STATUS +#define RADIOLIB_SX126X_STATUS_MODE_STDBY_RC 0b00100000 // 6 4 current chip mode: STDBY_RC +#define RADIOLIB_SX126X_STATUS_MODE_STDBY_XOSC 0b00110000 // 6 4 STDBY_XOSC +#define RADIOLIB_SX126X_STATUS_MODE_FS 0b01000000 // 6 4 FS +#define RADIOLIB_SX126X_STATUS_MODE_RX 0b01010000 // 6 4 RX +#define RADIOLIB_SX126X_STATUS_MODE_TX 0b01100000 // 6 4 TX +#define RADIOLIB_SX126X_STATUS_DATA_AVAILABLE 0b00000100 // 3 1 command status: packet received and data can be retrieved +#define RADIOLIB_SX126X_STATUS_CMD_TIMEOUT 0b00000110 // 3 1 SPI command timed out +#define RADIOLIB_SX126X_STATUS_CMD_INVALID 0b00001000 // 3 1 invalid SPI command +#define RADIOLIB_SX126X_STATUS_CMD_FAILED 0b00001010 // 3 1 SPI command failed to execute +#define RADIOLIB_SX126X_STATUS_TX_DONE 0b00001100 // 3 1 packet transmission done +#define RADIOLIB_SX126X_STATUS_SPI_FAILED 0b11111111 // 7 0 SPI transaction failed + +//RADIOLIB_SX126X_CMD_GET_PACKET_STATUS +#define RADIOLIB_SX126X_GFSK_RX_STATUS_PREAMBLE_ERR 0b10000000 // 7 7 GFSK Rx status: preamble error +#define RADIOLIB_SX126X_GFSK_RX_STATUS_SYNC_ERR 0b01000000 // 6 6 sync word error +#define RADIOLIB_SX126X_GFSK_RX_STATUS_ADRS_ERR 0b00100000 // 5 5 address error +#define RADIOLIB_SX126X_GFSK_RX_STATUS_CRC_ERR 0b00010000 // 4 4 CRC error +#define RADIOLIB_SX126X_GFSK_RX_STATUS_LENGTH_ERR 0b00001000 // 3 3 length error +#define RADIOLIB_SX126X_GFSK_RX_STATUS_ABORT_ERR 0b00000100 // 2 2 abort error +#define RADIOLIB_SX126X_GFSK_RX_STATUS_PACKET_RECEIVED 0b00000010 // 2 2 packet received +#define RADIOLIB_SX126X_GFSK_RX_STATUS_PACKET_SENT 0b00000001 // 2 2 packet sent + +//RADIOLIB_SX126X_CMD_GET_DEVICE_ERRORS +#define RADIOLIB_SX126X_PA_RAMP_ERR 0b100000000 // 8 8 device errors: PA ramping failed +#define RADIOLIB_SX126X_PLL_LOCK_ERR 0b001000000 // 6 6 PLL failed to lock +#define RADIOLIB_SX126X_XOSC_START_ERR 0b000100000 // 5 5 crystal oscillator failed to start +#define RADIOLIB_SX126X_IMG_CALIB_ERR 0b000010000 // 4 4 image calibration failed +#define RADIOLIB_SX126X_ADC_CALIB_ERR 0b000001000 // 3 3 ADC calibration failed +#define RADIOLIB_SX126X_PLL_CALIB_ERR 0b000000100 // 2 2 PLL calibration failed +#define RADIOLIB_SX126X_RC13M_CALIB_ERR 0b000000010 // 1 1 RC13M calibration failed +#define RADIOLIB_SX126X_RC64K_CALIB_ERR 0b000000001 // 0 0 RC64K calibration failed + +//RADIOLIB_SX126X_CMD_SET_LBT_SCAN_PARAMS + RADIOLIB_SX126X_CMD_SET_SPECTR_SCAN_PARAMS +#define RADIOLIB_SX126X_SCAN_INTERVAL_7_68_US 10 // 7 0 RSSI reading interval: 7.68 us +#define RADIOLIB_SX126X_SCAN_INTERVAL_8_20_US 11 // 7 0 8.20 us +#define RADIOLIB_SX126X_SCAN_INTERVAL_8_68_US 12 // 7 0 8.68 us + +// SX126X SPI register variables +//RADIOLIB_SX126X_REG_HOPPING_ENABLE +#define RADIOLIB_SX126X_HOPPING_ENABLED 0b00000001 // 0 0 intra-packet hopping for LR-FHSS: enabled +#define RADIOLIB_SX126X_HOPPING_DISABLED 0b00000000 // 0 0 (disabled) + +//RADIOLIB_SX126X_REG_LORA_SYNC_WORD_MSB + LSB +#define RADIOLIB_SX126X_SYNC_WORD_PUBLIC 0x34 // actually 0x3444 NOTE: The low nibbles in each byte (0x_4_4) are masked out since apparently, they're reserved. +#define RADIOLIB_SX126X_SYNC_WORD_PRIVATE 0x12 // actually 0x1424 You couldn't make this up if you tried. + +// RADIOLIB_SX126X_REG_TX_BITBANG_ENABLE_1 +#define RADIOLIB_SX126X_TX_BITBANG_1_DISABLED 0b00000000 // 6 4 Tx bitbang: disabled (default) +#define RADIOLIB_SX126X_TX_BITBANG_1_ENABLED 0b00010000 // 6 4 enabled + +// RADIOLIB_SX126X_REG_TX_BITBANG_ENABLE_0 +#define RADIOLIB_SX126X_TX_BITBANG_0_DISABLED 0b00000000 // 3 0 Tx bitbang: disabled (default) +#define RADIOLIB_SX126X_TX_BITBANG_0_ENABLED 0b00001100 // 3 0 enabled + +// RADIOLIB_SX126X_REG_DIOX_OUT_ENABLE +#define RADIOLIB_SX126X_DIO1_OUT_DISABLED 0b00000010 // 1 1 DIO1 output: disabled +#define RADIOLIB_SX126X_DIO1_OUT_ENABLED 0b00000000 // 1 1 enabled +#define RADIOLIB_SX126X_DIO2_OUT_DISABLED 0b00000100 // 2 2 DIO2 output: disabled +#define RADIOLIB_SX126X_DIO2_OUT_ENABLED 0b00000000 // 2 2 enabled +#define RADIOLIB_SX126X_DIO3_OUT_DISABLED 0b00001000 // 3 3 DIO3 output: disabled +#define RADIOLIB_SX126X_DIO3_OUT_ENABLED 0b00000000 // 3 3 enabled + +// RADIOLIB_SX126X_REG_DIOX_IN_ENABLE +#define RADIOLIB_SX126X_DIO1_IN_DISABLED 0b00000000 // 1 1 DIO1 input: disabled +#define RADIOLIB_SX126X_DIO1_IN_ENABLED 0b00000010 // 1 1 enabled +#define RADIOLIB_SX126X_DIO2_IN_DISABLED 0b00000000 // 2 2 DIO2 input: disabled +#define RADIOLIB_SX126X_DIO2_IN_ENABLED 0b00000100 // 2 2 enabled +#define RADIOLIB_SX126X_DIO3_IN_DISABLED 0b00000000 // 3 3 DIO3 input: disabled +#define RADIOLIB_SX126X_DIO3_IN_ENABLED 0b00001000 // 3 3 enabled + +// RADIOLIB_SX126X_REG_RX_GAIN +#define RADIOLIB_SX126X_RX_GAIN_BOOSTED 0x96 // 7 0 Rx gain: boosted +#define RADIOLIB_SX126X_RX_GAIN_POWER_SAVING 0x94 // 7 0 power saving +#define RADIOLIB_SX126X_RX_GAIN_SPECTRAL_SCAN 0xCB // 7 0 spectral scan + +// RADIOLIB_SX126X_REG_PATCH_UPDATE_ENABLE +#define RADIOLIB_SX126X_PATCH_UPDATE_DISABLED 0b00000000 // 4 4 patch update: disabled +#define RADIOLIB_SX126X_PATCH_UPDATE_ENABLED 0b00010000 // 4 4 enabled + +// RADIOLIB_SX126X_REG_SPECTRAL_SCAN_STATUS +#define RADIOLIB_SX126X_SPECTRAL_SCAN_NONE 0x00 // 7 0 spectral scan status: none +#define RADIOLIB_SX126X_SPECTRAL_SCAN_ONGOING 0x0F // 7 0 ongoing +#define RADIOLIB_SX126X_SPECTRAL_SCAN_ABORTED 0xF0 // 7 0 aborted +#define RADIOLIB_SX126X_SPECTRAL_SCAN_COMPLETED 0xFF // 7 0 completed + +// RADIOLIB_SX126X_REG_RSSI_AVG_WINDOW +#define RADIOLIB_SX126X_SPECTRAL_SCAN_WINDOW_DEFAULT (0x05 << 2) // 7 0 default RSSI average window + +// RADIOLIB_SX126X_REG_ANA_LNA +#define RADIOLIB_SX126X_LNA_RNG_DISABLED 0b00000001 // 0 0 random number: disabled +#define RADIOLIB_SX126X_LNA_RNG_ENABLED 0b00000000 // 0 0 enabled + +// RADIOLIB_SX126X_REG_ANA_MIXER +#define RADIOLIB_SX126X_MIXER_RNG_DISABLED 0b00000001 // 7 7 random number: disabled +#define RADIOLIB_SX126X_MIXER_RNG_ENABLED 0b00000000 // 7 7 enabled + +// 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. + This class should not be instantiated directly from Arduino sketch, only from its derived classes. +*/ +class SX126x: public PhysicalLayer { + public: + // introduce PhysicalLayer overloads + using PhysicalLayer::transmit; + using PhysicalLayer::receive; + using PhysicalLayer::startTransmit; + using PhysicalLayer::readData; + + /*! + \brief Default constructor. + \param mod Instance of Module that will be used to communicate with the radio. + */ + explicit SX126x(Module* mod); + + /*! + \brief Whether the module has an XTAL (true) or TCXO (false). Defaults to false. + */ + bool XTAL; + + /*! + \brief Whether to use XOSC (true) or RC (false) oscillator in standby mode. Defaults to false. + */ + bool standbyXOSC; + + // basic methods + + /*! + \brief Initialization method for LoRa modem. + \param cr LoRa coding rate denominator. Allowed values range from 5 to 8. + \param syncWord 1-byte LoRa sync word. + \param preambleLength LoRa preamble length in symbols. Allowed values range from 1 to 65535. + \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 begin(uint8_t cr, uint8_t syncWord, uint16_t preambleLength, float tcxoVoltage, bool useRegulatorLDO = false); + + /*! + \brief Initialization method for FSK modem. + \param br FSK bit rate in kbps. Allowed values range from 0.6 to 300.0 kbps. + \param freqDev Frequency deviation from carrier frequency in kHz. Allowed values range from 0.0 to 200.0 kHz. + \param rxBw Receiver bandwidth in kHz. Allowed values are 4.8, 5.8, 7.3, 9.7, 11.7, 14.6, 19.5, 23.4, 29.3, 39.0, + 46.9, 58.6, 78.2, 93.8, 117.3, 156.2, 187.2, 234.3, 312.0, 373.6 and 467.0 kHz. + \param preambleLength FSK preamble length in bits. Allowed values range from 0 to 65535. + \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 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 + by repeatedly issuing setStandby command. Enabled by default. + \returns \ref status_codes + */ + int16_t reset(bool verify = true); + + /*! + \brief Blocking binary transmit method. + Overloads for string-based transmissions are implemented in PhysicalLayer. + \param data Binary data to be sent. + \param len Number of bytes to send. + \param addr Address to send the data to. Will only be added if address filtering was enabled. + \returns \ref status_codes + */ + int16_t transmit(const uint8_t* data, size_t len, uint8_t addr = 0) override; + + /*! + \brief Blocking binary receive method. + Overloads for string-based transmissions are implemented in PhysicalLayer. + \param data Binary data to be sent. + \param len Number of bytes to send. + \returns \ref status_codes + */ + int16_t receive(uint8_t* data, size_t len) override; + + /*! + \brief Starts direct mode transmission. + \param frf Raw RF frequency value. Defaults to 0, required for quick frequency shifts in RTTY. + \returns \ref status_codes + */ + int16_t transmitDirect(uint32_t frf = 0) override; + + /*! + \brief Starts direct mode reception. Only implemented for PhysicalLayer compatibility, as %SX126x series does not support direct mode reception. + Will always return RADIOLIB_ERR_UNKNOWN. + \returns \ref status_codes + */ + int16_t receiveDirect() override; + + /*! + \brief Performs scan for LoRa transmission in the current channel. Detects both preamble and payload. + Configuration defaults to the values recommended by AN1200.48. + \returns \ref status_codes + */ + int16_t scanChannel() override; + + /*! + \brief Performs scan for LoRa transmission in the current channel. Detects both preamble and payload. + \param config CAD configuration structure. + \returns \ref status_codes + */ + int16_t scanChannel(const ChannelScanConfig_t &config) override; + + /*! + \brief Sets the module to sleep mode. To wake the device up, call standby(). + Overload with warm start enabled for PhysicalLayer compatibility. + \returns \ref status_codes + */ + int16_t sleep() override; + + /*! + \brief Sets the module to sleep mode. To wake the device up, call standby(). + \param retainConfig Set to true to retain configuration of the currently active modem ("warm start") + or to false to discard current configuration ("cold start"). Defaults to true. + \returns \ref status_codes + */ + int16_t sleep(bool retainConfig); + + /*! + \brief Sets the module to standby mode (overload for PhysicalLayer compatibility, uses 13 MHz RC oscillator). + \returns \ref status_codes + */ + int16_t standby() override; + + /*! + \brief Sets the module to standby mode. + \param mode Oscillator to be used in standby mode. Can be set to RADIOLIB_SX126X_STANDBY_RC (13 MHz RC oscillator) + or RADIOLIB_SX126X_STANDBY_XOSC (32 MHz external crystal oscillator). + \param wakeup Whether to force the module to wake up. Setting to true will immediately attempt to wake up the module. + \returns \ref status_codes + */ + int16_t standby(uint8_t mode, bool wakeup = true); + + // interrupt methods + + /*! + \brief Sets interrupt service routine to call when DIO1 activates. + \param func ISR to call. + */ + void setDio1Action(void (*func)(void)); + + /*! + \brief Clears interrupt service routine to call when DIO1 activates. + */ + void clearDio1Action(); + + /*! + \brief Sets interrupt service routine to call when a packet is received. + \param func ISR to call. + */ + void setPacketReceivedAction(void (*func)(void)) override; + + /*! + \brief Clears interrupt service routine to call when a packet is received. + */ + void clearPacketReceivedAction() override; + + /*! + \brief Sets interrupt service routine to call when a packet is sent. + \param func ISR to call. + */ + void setPacketSentAction(void (*func)(void)) override; + + /*! + \brief Clears interrupt service routine to call when a packet is sent. + */ + void clearPacketSentAction() override; + + /*! + \brief Sets interrupt service routine to call when a channel scan is finished. + \param func ISR to call. + */ + void setChannelScanAction(void (*func)(void)) override; + + /*! + \brief Clears interrupt service routine to call when a channel scan is finished. + */ + void clearChannelScanAction() override; + + /*! + \brief Interrupt-driven binary transmit method. + Overloads for string-based transmissions are implemented in PhysicalLayer. + \param data Binary data to be sent. + \param len Number of bytes to send. + \param addr Address to send the data to. Will only be added if address filtering was enabled. + \returns \ref status_codes + */ + int16_t startTransmit(const uint8_t* data, size_t len, uint8_t addr = 0) override; + + /*! + \brief Clean up after transmission is done. + \returns \ref status_codes + */ + int16_t finishTransmit() override; + + /*! + \brief Interrupt-driven receive method with default parameters. + Implemented for compatibility with PhysicalLayer. + + \returns \ref status_codes + */ + int16_t startReceive() override; + + /*! + \brief Interrupt-driven receive method. DIO1 will be activated when full packet is received. + \param timeout Receive mode type and/or raw timeout value, expressed as multiples of 15.625 us. + When set to RADIOLIB_SX126X_RX_TIMEOUT_INF, the timeout will be infinite and the device will remain + in Rx mode until explicitly commanded to stop (Rx continuous mode). + When set to RADIOLIB_SX126X_RX_TIMEOUT_NONE, there will be no timeout and the device will return + to standby when a packet is received (Rx single mode). + For any other value, timeout will be applied and signal will be generated on DIO1 for conditions + defined by irqFlags and irqMask. + + \param irqFlags Sets the IRQ flags, defaults to RX done, RX timeout, CRC error and header error. + \param irqMask Sets the mask of IRQ flags that will trigger DIO1, defaults to RX done. + \param len Only for PhysicalLayer compatibility, not used. + \returns \ref status_codes + */ + int16_t startReceive(uint32_t timeout, RadioLibIrqFlags_t irqFlags = RADIOLIB_IRQ_RX_DEFAULT_FLAGS, RadioLibIrqFlags_t irqMask = RADIOLIB_IRQ_RX_DEFAULT_MASK, size_t len = 0); + + /*! + \brief Interrupt-driven receive method where the device mostly sleeps and periodically wakes to listen. + Note that this function assumes the unit will take 500us + TCXO_delay to change state. + See datasheet section 13.1.7, version 1.2. + + \param rxPeriod The duration the receiver will be in Rx mode, in microseconds. + \param sleepPeriod The duration the receiver will not be in Rx mode, in microseconds. + + \param irqFlags Sets the IRQ flags, defaults to RX done, RX timeout, CRC error and header error. + \param irqMask Sets the mask of IRQ flags that will trigger DIO1, defaults to RX done. + \returns \ref status_codes + */ + int16_t startReceiveDutyCycle(uint32_t rxPeriod, uint32_t sleepPeriod, RadioLibIrqFlags_t irqFlags = RADIOLIB_IRQ_RX_DEFAULT_FLAGS, RadioLibIrqFlags_t irqMask = RADIOLIB_IRQ_RX_DEFAULT_MASK); + + /*! + \brief Calls \ref startReceiveDutyCycle with rxPeriod and sleepPeriod set so the unit shouldn't miss any messages. + \param senderPreambleLength Expected preamble length of the messages to receive. + If set to zero, the currently configured preamble length will be used. Defaults to zero. + + \param minSymbols Parameters will be chosen to ensure that the unit will catch at least this many symbols + of any preamble of the specified length. Defaults to 8. + According to Semtech, receiver requires 8 symbols to reliably latch a preamble. + This makes this method redundant when transmitter preamble length is less than 17 (2*minSymbols + 1). + + \param irqFlags Sets the IRQ flags, defaults to RX done, RX timeout, CRC error and header error. + \param irqMask Sets the mask of IRQ flags that will trigger DIO1, defaults to RX done. + \returns \ref status_codes + */ + int16_t startReceiveDutyCycleAuto(uint16_t senderPreambleLength = 0, uint16_t minSymbols = 8, RadioLibIrqFlags_t irqFlags = RADIOLIB_IRQ_RX_DEFAULT_FLAGS, RadioLibIrqFlags_t irqMask = RADIOLIB_IRQ_RX_DEFAULT_MASK); + + /*! + \brief Reads data received after calling startReceive method. When the packet length is not known in advance, + getPacketLength method must be called BEFORE calling readData! + \param data Pointer to array to save the received binary data. + \param len Number of bytes that will be read. When set to 0, the packet length will be retrieved automatically. + When more bytes than received are requested, only the number of bytes requested will be returned. + \returns \ref status_codes + */ + int16_t readData(uint8_t* data, size_t len) override; + + /*! + \brief Interrupt-driven channel activity detection method. DIO1 will be activated + when LoRa preamble is detected, or upon timeout. Defaults to CAD parameter values recommended by AN1200.48. + \returns \ref status_codes + */ + int16_t startChannelScan() override; + + /*! + \brief Interrupt-driven channel activity detection method. DIO1 will be activated + when LoRa preamble is detected, or upon timeout. + \param config CAD configuration structure. + \returns \ref status_codes + */ + int16_t startChannelScan(const ChannelScanConfig_t &config) override; + + /*! + \brief Read the channel scan result + \returns \ref status_codes + */ + int16_t getChannelScanResult() override; + + // configuration methods + + /*! + \brief Sets LoRa bandwidth. Allowed values are 7.8, 10.4, 15.6, 20.8, 31.25, 41.7, 62.5, 125.0, 250.0 and 500.0 kHz. + \param bw LoRa bandwidth to be set in kHz. + \returns \ref status_codes + */ + int16_t setBandwidth(float bw); + + /*! + \brief Sets LoRa spreading factor. Allowed values range from 5 to 12. + \param sf LoRa spreading factor to be set. + \returns \ref status_codes + */ + int16_t setSpreadingFactor(uint8_t sf); + + /*! + \brief Sets LoRa coding rate denominator. Allowed values range from 5 to 8. + \param cr LoRa coding rate denominator to be set. + \returns \ref status_codes + */ + int16_t setCodingRate(uint8_t cr); + + /*! + \brief Sets LoRa sync word. + \param syncWord LoRa sync word to be set. + \param controlBits Undocumented control bits, required for compatibility purposes. + \returns \ref status_codes + */ + int16_t setSyncWord(uint8_t syncWord, uint8_t controlBits = 0x44); + + /*! + \brief Sets current protection limit. Can be set in 2.5 mA steps. + \param currentLimit current protection limit to be set in mA. Allowed values range from 0 to 140. + \returns \ref status_codes + */ + int16_t setCurrentLimit(float currentLimit); + + /*! + \brief Reads current protection limit. + \returns Currently configured overcurrent protection limit in mA. + */ + float getCurrentLimit(); + + /*! + \brief Sets preamble length for LoRa or FSK modem. Allowed values range from 1 to 65535. + \param preambleLength Preamble length to be set in symbols (LoRa) or bits (FSK). + \returns \ref status_codes + */ + int16_t setPreambleLength(size_t preambleLength) override; + + /*! + \brief Sets FSK frequency deviation. Allowed values range from 0.0 to 200.0 kHz. + \param freqDev FSK frequency deviation to be set in kHz. + \returns \ref status_codes + */ + int16_t setFrequencyDeviation(float freqDev) override; + + /*! + \brief Sets FSK bit rate. Allowed values range from 0.6 to 300.0 kbps. + \param br FSK bit rate to be set in kbps. + \returns \ref status_codes + */ + int16_t setBitRate(float br) override; + + /*! + \brief Set data. + \param dr Data rate struct. Interpretation depends on currently active modem (FSK or LoRa). + \returns \ref status_codes + */ + int16_t setDataRate(DataRate_t dr) override; + + /*! + \brief Check the data rate can be configured by this module. + \param dr Data rate struct. Interpretation depends on currently active modem (FSK or LoRa). + \returns \ref status_codes + */ + int16_t checkDataRate(DataRate_t dr) override; + + /*! + \brief Sets FSK receiver bandwidth. Allowed values are 4.8, 5.8, 7.3, 9.7, 11.7, 14.6, 19.5, + 23.4, 29.3, 39.0, 46.9, 58.6, 78.2, 93.8, 117.3, 156.2, 187.2, 234.3, 312.0, 373.6 and 467.0 kHz. + \param rxBw FSK receiver bandwidth to be set in kHz. + \returns \ref status_codes + */ + int16_t setRxBandwidth(float rxBw); + + /*! + \brief Enables or disables Rx Boosted Gain mode as described in SX126x datasheet + section 9.6 (SX1261/2 v2.1, SX1268 v1.1) + \param rxbgm True for Rx Boosted Gain, false for Rx Power Saving Gain + \param persist True to persist Rx gain setting when waking up from warm-start mode + (e.g. when using Rx duty cycle mode). + \returns \ref status_codes + */ + int16_t setRxBoostedGainMode(bool rxbgm, bool persist = true); + + /*! + \brief Sets time-bandwidth product of Gaussian filter applied for shaping. + Allowed values are RADIOLIB_SHAPING_0_3, RADIOLIB_SHAPING_0_5, RADIOLIB_SHAPING_0_7 or RADIOLIB_SHAPING_1_0. + Set to RADIOLIB_SHAPING_NONE to disable data shaping. + \param sh Time-bandwidth product of Gaussian filter to be set. + \returns \ref status_codes + */ + int16_t setDataShaping(uint8_t sh) override; + + /*! + \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 + */ + int16_t setSyncWord(uint8_t* syncWord, size_t len) override; + + /*! + \brief Sets FSK sync word in the form of array of up to 8 bytes. + \param syncWord FSK sync word to be set. + \param bitsLen FSK sync word length in bits. If length is not divisible by 8, + least significant bits of syncWord will be ignored. + \returns \ref status_codes + */ + int16_t setSyncBits(uint8_t *syncWord, uint8_t bitsLen); + + /*! + \brief Sets node address. Calling this method will also enable address filtering for node address only. + \param addr Node address to be set. + \returns \ref status_codes + */ + int16_t setNodeAddress(uint8_t addr); + + /*! + \brief Sets broadcast address. Calling this method will also enable address + filtering for node and broadcast address. + \param broadAddr Node address to be set. + \returns \ref status_codes + */ + int16_t setBroadcastAddress(uint8_t broadAddr); + + /*! + \brief Disables address filtering. Calling this method will also erase previously set addresses. + \returns \ref status_codes + */ + int16_t disableAddressFiltering(); + + /*! + \brief Sets CRC configuration. + \param len CRC length in bytes, Allowed values are 1 or 2, set to 0 to disable CRC. + \param initial Initial CRC value. FSK only. Defaults to 0x1D0F (CCIT CRC). + \param polynomial Polynomial for CRC calculation. FSK only. Defaults to 0x1021 (CCIT CRC). + \param inverted Invert CRC bytes. FSK only. Defaults to true (CCIT CRC). + \returns \ref status_codes + */ + int16_t setCRC(uint8_t len, uint16_t initial = 0x1D0F, uint16_t polynomial = 0x1021, bool inverted = true); + + /*! + \brief Sets FSK whitening parameters. + \param enabled True = Whitening enabled + \param initial Initial value used for the whitening LFSR in FSK mode. + By default set to 0x01FF for compatibility with SX127x and LoRaWAN. + \returns \ref status_codes + */ + int16_t setWhitening(bool enabled, uint16_t initial = 0x01FF); + + /*! + \brief Sets TCXO (Temperature Compensated Crystal Oscillator) configuration. + \param voltage TCXO reference voltage in volts. Allowed values are 1.6, 1.7, 1.8, 2.2. 2.4, 2.7, 3.0 and 3.3 V. + Set to 0 to disable TCXO. + NOTE: After setting this parameter to 0, the module will be reset (since there's no other way to disable TCXO). + + \param delay TCXO timeout in us. Defaults to 5000 us. + \returns \ref status_codes + */ + int16_t setTCXO(float voltage, uint32_t delay = 5000); + + /*! + \brief Set DIO2 to function as RF switch (default in Semtech example designs). + \returns \ref status_codes + */ + int16_t setDio2AsRfSwitch(bool enable = true); + + /*! + \brief Gets effective data rate for the last transmitted packet. The value is calculated only for payload bytes. + \returns Effective data rate in bps. + */ + float getDataRate() const; + + /*! + \brief Gets recorded signal strength indicator. + Overload with packet mode enabled for PhysicalLayer compatibility. + \returns RSSI value in dBm. + */ + float getRSSI() override; + + /*! + \brief Gets RSSI (Recorded Signal Strength Indicator). + \param packet Whether to read last packet RSSI, or the current value. + \returns RSSI value in dBm. + */ + float getRSSI(bool packet); + + /*! + \brief Gets SNR (Signal to Noise Ratio) of the last received packet. Only available for LoRa modem. + \returns SNR of the last received packet in dB. + */ + float getSNR() override; + + /*! + \brief Gets frequency error of the latest received packet. + WARNING: This functionality is based on SX128x implementation and not documented on SX126x. + While it seems to be working, it should be used with caution! + + \returns Frequency error in Hz. + */ + float getFrequencyError(); + + /*! + \brief Query modem for the packet length of received payload. + \param update Update received packet length. Will return cached value when set to false. + \returns Length of last received packet in bytes. + */ + size_t getPacketLength(bool update = true) override; + + /*! + \brief Query modem for the packet length of received payload and Rx buffer offset. + \param update Update received packet length. Will return cached value when set to false. + \param offset Pointer to variable to store the Rx buffer offset. + \returns Length of last received packet in bytes. + */ + size_t getPacketLength(bool update, uint8_t* offset); + + /*! + \brief Set modem in fixed packet length mode. Available in FSK mode only. + \param len Packet length. + \returns \ref status_codes + */ + int16_t fixedPacketLengthMode(uint8_t len = RADIOLIB_SX126X_MAX_PACKET_LENGTH); + + /*! + \brief Set modem in variable packet length mode. Available in FSK mode only. + \param maxLen Maximum packet length. + \returns \ref status_codes + */ + int16_t variablePacketLengthMode(uint8_t maxLen = RADIOLIB_SX126X_MAX_PACKET_LENGTH); + + /*! + \brief Get expected time-on-air for a given size of payload + \param len Payload length in bytes. + \returns Expected time-on-air in microseconds. + */ + RadioLibTime_t getTimeOnAir(size_t len) override; + + /*! + \brief Calculate the timeout value for this specific module / series (in number of symbols or units of time) + \param timeoutUs Timeout in microseconds to listen for + \returns Timeout value in a unit that is specific for the used module + */ + RadioLibTime_t calculateRxTimeout(RadioLibTime_t timeoutUs) override; + + /*! + \brief Read currently active IRQ flags. + \returns IRQ flags. + */ + uint32_t getIrqFlags() override; + + /*! + \brief Set interrupt on DIO1 to be sent on a specific IRQ bit (e.g. RxTimeout, CadDone). + \param irq Module-specific IRQ flags. + \returns \ref status_codes + */ + int16_t setIrqFlags(uint32_t irq) override; + + /*! + \brief Clear interrupt on a specific IRQ bit (e.g. RxTimeout, CadDone). + \param irq Module-specific IRQ flags. + \returns \ref status_codes + */ + int16_t clearIrqFlags(uint32_t irq) override; + + /*! + \brief Set implicit header mode for future reception/transmission. + \param len Payload length in bytes. + \returns \ref status_codes + */ + int16_t implicitHeader(size_t len); + + /*! + \brief Set explicit header mode for future reception/transmission. + \returns \ref status_codes + */ + int16_t explicitHeader(); + + /*! + \brief Set regulator mode to LDO. + \returns \ref status_codes + */ + int16_t setRegulatorLDO(); + + /*! + \brief Set regulator mode to DC-DC. + \returns \ref status_codes + */ + int16_t setRegulatorDCDC(); + + /*! + \brief Sets transmission encoding. Available in FSK mode only. Serves only as alias for PhysicalLayer compatibility. + \param encoding Encoding to be used. Set to 0 for NRZ, and 2 for whitening. + \returns \ref status_codes + */ + int16_t setEncoding(uint8_t encoding) override; + + /*! \copydoc Module::setRfSwitchPins */ + void setRfSwitchPins(uint32_t rxEn, uint32_t txEn); + + /*! \copydoc Module::setRfSwitchTable */ + void setRfSwitchTable(const uint32_t (&pins)[Module::RFSWITCH_MAX_PINS], const Module::RfSwitchMode_t table[]); + + /*! + \brief Forces LoRa low data rate optimization. Only available in LoRa mode. After calling this method, + LDRO will always be set to the provided value, regardless of symbol length. + To re-enable automatic LDRO configuration, call SX126x::autoLDRO() + + \param enable Force LDRO to be always enabled (true) or disabled (false). + \returns \ref status_codes + */ + int16_t forceLDRO(bool enable); + + /*! + \brief Re-enables automatic LDRO configuration. Only available in LoRa mode. + After calling this method, LDRO will be enabled automatically when symbol length exceeds 16 ms. + + \returns \ref status_codes + */ + int16_t autoLDRO(); + + /*! + \brief Get one truly random byte from RSSI noise. + \returns TRNG byte. + */ + uint8_t randomByte() override; + + /*! + \brief Enable/disable inversion of the I and Q signals + \param enable QI inversion enabled (true) or disabled (false); + \returns \ref status_codes + */ + int16_t invertIQ(bool enable) override; + + /*! + \brief Get modem currently in use by the radio. + \param modem Pointer to a variable to save the retrieved configuration into. + \returns \ref status_codes + */ + int16_t getModem(ModemType_t* modem) override; + + #if !RADIOLIB_EXCLUDE_DIRECT_RECEIVE + /*! + \brief Set interrupt service routine function to call when data bit is received in direct mode. + \param func Pointer to interrupt service routine. + */ + void setDirectAction(void (*func)(void)) override; + + /*! + \brief Function to read and process data bit in direct reception mode. + \param pin Pin on which to read. + */ + void readBit(uint32_t pin) override; + #endif + + /*! + \brief Upload binary patch into the SX126x device RAM. + Patch is needed to e.g., enable spectral scan and must be uploaded again on every power cycle. + \param patch Binary patch to upload. + \param len Length of the binary patch in 4-byte words. + \param nonvolatile Set to true when the patch is saved in non-volatile memory of the host processor, + or to false when the patch is in its RAM. + \returns \ref status_codes + */ + int16_t uploadPatch(const uint32_t* patch, size_t len, bool nonvolatile = true); + + /*! + \brief Start spectral scan. Requires binary path to be uploaded. + \param numSamples Number of samples for each scan. Fewer samples = better temporal resolution. + \param window RSSI averaging window size. + \param interval Scan interval length, one of RADIOLIB_SX126X_SCAN_INTERVAL_* macros. + \returns \ref status_codes + */ + int16_t spectralScanStart(uint16_t numSamples, uint8_t window = RADIOLIB_SX126X_SPECTRAL_SCAN_WINDOW_DEFAULT, uint8_t interval = RADIOLIB_SX126X_SCAN_INTERVAL_8_20_US); + + /*! + \brief Abort an ongoing spectral scan. + */ + void spectralScanAbort(); + + /*! + \brief Read the status of spectral scan. + \returns \ref status_codes + */ + int16_t spectralScanGetStatus(); + + /*! + \brief Read the result of spectral scan. + \param results Array to which the results will be saved, must be RADIOLIB_SX126X_SPECTRAL_SCAN_RES_SIZE long. + \returns \ref status_codes + */ + int16_t spectralScanGetResult(uint16_t* results); + + /*! + \brief Set the PA configuration. Allows user to optimize PA for a specific output power + and matching network. Any calls to this method must be done after calling begin/beginFSK and/or setOutputPower. + WARNING: Use at your own risk! Setting invalid values can and will lead to permanent damage! + \param paDutyCycle PA duty cycle raw value. + \param deviceSel Device select, usually RADIOLIB_SX126X_PA_CONFIG_SX1261, + RADIOLIB_SX126X_PA_CONFIG_SX1262 or RADIOLIB_SX126X_PA_CONFIG_SX1268. + \param hpMax hpMax raw value. + \param paLut paLut PA lookup table raw value. + \returns \ref status_codes + */ + int16_t setPaConfig(uint8_t paDutyCycle, uint8_t deviceSel, uint8_t hpMax = RADIOLIB_SX126X_PA_CONFIG_HP_MAX, uint8_t paLut = RADIOLIB_SX126X_PA_CONFIG_PA_LUT); + + /*! + \brief Perform image rejection calibration for the specified frequency. + Will try to use Semtech-defined presets first, and if none of them matches, + custom iamge calibration will be attempted using calibrateImageRejection. + \param freq Frequency to perform the calibration for. + \returns \ref status_codes + */ + int16_t calibrateImage(float freq); + + /*! + \brief Perform image rejection calibration for the specified frequency band. + WARNING: Use at your own risk! Setting incorrect values may lead to decreased performance + \param freqMin Frequency band lower bound. + \param freqMax Frequency band upper bound. + \returns \ref status_codes + */ + int16_t calibrateImageRejection(float freqMin, float freqMax); + + /*! + \brief Set PA ramp-up time. Set to 200us by default. + \returns \ref status_codes + */ + int16_t setPaRampTime(uint8_t rampTime); + +#if !RADIOLIB_GODMODE && !RADIOLIB_LOW_LEVEL + protected: +#endif + Module* getMod() override; + + // SX126x SPI command implementations + int16_t setFs(); + int16_t setTx(uint32_t timeout = 0); + int16_t setRx(uint32_t timeout); + int16_t setCad(uint8_t symbolNum, uint8_t detPeak, uint8_t detMin, uint8_t exitMode, RadioLibTime_t timeout); + int16_t writeRegister(uint16_t addr, uint8_t* data, uint8_t numBytes); + int16_t readRegister(uint16_t addr, uint8_t* data, uint8_t numBytes); + int16_t writeBuffer(uint8_t* data, uint8_t numBytes, uint8_t offset = 0x00); + int16_t readBuffer(uint8_t* data, uint8_t numBytes, uint8_t offset = 0x00); + int16_t setDioIrqParams(uint16_t irqMask, uint16_t dio1Mask, uint16_t dio2Mask = RADIOLIB_SX126X_IRQ_NONE, uint16_t dio3Mask = RADIOLIB_SX126X_IRQ_NONE); + virtual int16_t clearIrqStatus(uint16_t clearIrqParams = RADIOLIB_SX126X_IRQ_ALL); + int16_t setRfFrequency(uint32_t frf); + int16_t calibrateImage(uint8_t* data); + uint8_t getPacketType(); + int16_t setTxParams(uint8_t power, uint8_t rampTime); + int16_t setModulationParams(uint8_t sf, uint8_t bw, uint8_t cr, uint8_t ldro); + int16_t setModulationParamsFSK(uint32_t br, uint8_t sh, uint8_t rxBw, uint32_t freqDev); + int16_t setPacketParams(uint16_t preambleLen, uint8_t crcType, uint8_t payloadLen, uint8_t hdrType, uint8_t invertIQ); + int16_t setPacketParamsFSK(uint16_t preambleLen, uint8_t preambleDetectorLen, uint8_t crcType, uint8_t syncWordLen, uint8_t addrCmp, uint8_t whiten, uint8_t packType = RADIOLIB_SX126X_GFSK_PACKET_VARIABLE, uint8_t payloadLen = 0xFF); + int16_t setBufferBaseAddress(uint8_t txBaseAddress = 0x00, uint8_t rxBaseAddress = 0x00); + int16_t setRegulatorMode(uint8_t mode); + uint8_t getStatus(); + uint32_t getPacketStatus(); + uint16_t getDeviceErrors(); + int16_t clearDeviceErrors(); + +#if !RADIOLIB_GODMODE + protected: +#endif + const char* chipType = NULL; + uint8_t bandwidth = 0; + float freqMHz = 0; + + // Allow subclasses to define different TX modes + uint8_t txMode = Module::MODE_TX; + + int16_t setFrequencyRaw(float freq); + int16_t fixPaClamping(bool enable = true); + + // common low-level SPI interface + static int16_t SPIparseStatus(uint8_t in); + +#if !RADIOLIB_GODMODE + private: +#endif + Module* mod; + + uint8_t spreadingFactor = 0, codingRate = 0, ldrOptimize = 0, crcTypeLoRa = 0, headerType = 0; + uint16_t preambleLengthLoRa = 0; + float bandwidthKhz = 0; + bool ldroAuto = true; + + uint32_t bitRate = 0, frequencyDev = 0; + uint8_t preambleDetLength = 0, rxBandwidth = 0, pulseShape = 0, crcTypeFSK = 0, syncWordLength = 0, addrComp = 0, whitening = 0, packetType = 0; + uint16_t preambleLengthFSK = 0; + float rxBandwidthKhz = 0; + uint8_t nodeAddr = 0; + + float dataRateMeasured = 0; + + uint32_t tcxoDelay = 0; + uint8_t pwr = 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); + int16_t startReceiveCommon(uint32_t timeout = RADIOLIB_SX126X_RX_TIMEOUT_INF, RadioLibIrqFlags_t irqFlags = RADIOLIB_IRQ_RX_DEFAULT_FLAGS, RadioLibIrqFlags_t irqMask = RADIOLIB_IRQ_RX_DEFAULT_MASK); + int16_t setPacketMode(uint8_t mode, uint8_t len); + int16_t setHeaderType(uint8_t hdrType, size_t len = 0xFF); + int16_t directMode(); + int16_t packetMode(); + + // fixes to errata + int16_t fixSensitivity(); + 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); + void effectEvalPost(uint8_t* buff, uint32_t start); + void effectEval(); +}; + +#endif + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/SX126x/SX126x_LR_FHSS.cpp b/software/firmware/source/libraries/RadioLib/src/modules/SX126x/SX126x_LR_FHSS.cpp new file mode 100644 index 000000000..0f491f915 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/SX126x/SX126x_LR_FHSS.cpp @@ -0,0 +1,393 @@ +#include "SX126x.h" +#include +#include +#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((size_t)(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 frf = (this->freqMHz * (uint32_t(1) << RADIOLIB_SX126X_DIV_EXPONENT)) / RADIOLIB_SX126X_CRYSTAL_FREQ; + uint32_t freq_raw = 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 diff --git a/software/firmware/source/libraries/RadioLib/src/modules/SX126x/patches/SX126x_patch_scan.h b/software/firmware/source/libraries/RadioLib/src/modules/SX126x/patches/SX126x_patch_scan.h new file mode 100644 index 000000000..bc918749a --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/SX126x/patches/SX126x_patch_scan.h @@ -0,0 +1,110 @@ +/* +Binary in this file originates from https://github.com/Lora-net/sx1302_hal/tree/master +As such, license of the above repository is reproduced here. + +Copyright (c) 2019, SEMTECH S.A. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted 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. + +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 S.A. 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. +*/ + +#if !defined(_RADIOLIB_SX126X_PATCH_SCAN_H) +#define _RADIOLIB_SX126X_PATCH_SCAN_H + +#include "../../../TypeDef.h" + +#if !RADIOLIB_EXCLUDE_SX126X + +// the following is a binary patch to the SX1262 +// this patch is needed to enable spectral scan functionality +const uint32_t sx126x_patch_scan[] RADIOLIB_NONVOLATILE = { + 0x337fe1, 0x337fdb, 0x337fd5, 0x337fcf, 0x3a7fc8, 0x3f3fff, + 0x0378ff, 0x0379ff, 0x3a7fb7, 0x16a901, 0x16a801, 0x23ffff, + 0x0378ff, 0x0379ff, 0x3a7faf, 0x16a901, 0x16a801, 0x23ffff, + 0x0378ff, 0x0379ff, 0x3a7f34, 0x16a901, 0x16a801, 0x23ffff, + 0x0378ff, 0x0379ff, 0x3a7e80, 0x16a901, 0x16a801, 0x23ffff, + 0x0378ff, 0x0379ff, 0x3a7fc3, 0x16a901, 0x16a801, 0x337fc9, + 0x0378ff, 0x0379ff, 0x3a7fc0, 0x16a901, 0x16a801, 0x337fc9, + 0x0378ff, 0x0379ff, 0x3a7fbd, 0x16a901, 0x16a801, 0x337fc9, + 0x0378ff, 0x0379ff, 0x3a7fba, 0x16a901, 0x16a801, 0x337fc9, + 0x23ffff, 0x0ea1fc, 0x0ea0df, 0x0eafc9, 0x02cf0e, 0x23ffff, + 0x0eacff, 0x0eabff, 0x23ffff, 0x0eacff, 0x0eabff, 0x23ffff, + 0x0eacff, 0x0eabff, 0x23ffff, 0x0eacff, 0x0eabff, 0x23ffff, + 0x0378ff, 0x0379ff, 0x3a7fc8, 0x0eacfd, 0x0eabff, 0x16a901, + 0x16a801, 0x23ffff, 0x0374ff, 0x0375ff, 0x0378ff, 0x0379ff, + 0x16affe, 0x0ea5ff, 0x0ea465, 0x1dbb04, 0x0e1bfd, 0x307fa3, + 0x1caf00, 0x327fa0, 0x0eacf7, 0x0eabff, 0x337f3a, 0x1cad04, + 0x0eacfe, 0x0eabff, 0x0dbfdd, 0x307f97, 0x0dafcc, 0x0defbb, + 0x0dbfdd, 0x347f9b, 0x0eecef, 0x0caffc, 0x04ade8, 0x0cbdcd, + 0x01bde8, 0x04abe8, 0x0ebbbf, 0x01bbe8, 0x04abe8, 0x0ebbdf, + 0x01bbe8, 0x1ca800, 0x0ea9ff, 0x04abe3, 0x0e2b01, 0x01bbe3, + 0x04abee, 0x0e2b01, 0x01bbee, 0x1ca202, 0x1ca301, 0x02f300, + 0x02f201, 0x0ea064, 0x0ea1f7, 0x18ab00, 0x367f58, 0x0ea041, + 0x0ea1f7, 0x18ab00, 0x1c1b03, 0x317f3f, 0x1ea201, 0x1ea300, + 0x0cb23f, 0x327f4a, 0x1cab04, 0x0eaefe, 0x0dbfbb, 0x307f6c, + 0x0dafee, 0x0dbfbb, 0x347f6f, 0x04abee, 0x0cbbeb, 0x01bbee, + 0x0eacff, 0x0eabff, 0x0c1b9f, 0x327f64, 0x0c1c8f, 0x317f5c, + 0x3fffff, 0x0d1fcc, 0x0d5fbb, 0x0c1b9f, 0x327f5d, 0x0c1c8f, + 0x357f63, 0x0ea064, 0x0ea1f7, 0x18ab00, 0x327f7c, 0x1cad04, + 0x0eacfe, 0x0dbfdd, 0x307f51, 0x0dafcc, 0x0dbfdd, 0x347f54, + 0x0d8fcb, 0x04acee, 0x0c2bcb, 0x01bbee, 0x0eacfd, 0x0eabff, + 0x337f3a, 0x1cad04, 0x0eacfe, 0x0dbfdd, 0x307f43, 0x0dafcc, + 0x0dbfdd, 0x347f46, 0x0d8fcb, 0x04acee, 0x0c2bcb, 0x337f6a, + 0x0cb23f, 0x367f75, 0x0dbf22, 0x0dff33, 0x337f75, 0x16af02, + 0x16a901, 0x16a801, 0x16a501, 0x16a401, 0x23ffff, 0x0374ff, + 0x0375ff, 0x0378ff, 0x0379ff, 0x16afe0, 0x0ea5ff, 0x0ea465, + 0x1ca802, 0x0ea9ff, 0x0eafff, 0x02ff00, 0x0eaff7, 0x02ff01, + 0x0eafef, 0x02ff02, 0x0eafe7, 0x02ff03, 0x0eafdf, 0x02ff04, + 0x0eafd7, 0x02ff05, 0x0eafcf, 0x02ff06, 0x0eafc7, 0x02ff07, + 0x0eafbf, 0x02ff08, 0x0eafb7, 0x02ff09, 0x0eafaf, 0x02ff0a, + 0x0eafa7, 0x02ff0b, 0x0eaf9f, 0x02ff0c, 0x0eaf97, 0x02ff0d, + 0x0eaf8f, 0x02ff0e, 0x0eaf87, 0x02ff0f, 0x0eaf7f, 0x02ff10, + 0x0eaf77, 0x02ff11, 0x0eaf6f, 0x02ff12, 0x0eaf67, 0x02ff13, + 0x0eaf5f, 0x02ff14, 0x0eaf57, 0x02ff15, 0x0eaf4f, 0x02ff16, + 0x0eaf47, 0x02ff17, 0x0eaf3f, 0x02ff18, 0x0eaf37, 0x02ff19, + 0x0eaf2f, 0x02ff1a, 0x0eaf27, 0x02ff1b, 0x0eaf1f, 0x02ff1c, + 0x0eaf17, 0x02ff1d, 0x0eaf0f, 0x02ff1e, 0x0eaf07, 0x02ff1f, + 0x04abee, 0x0e2b01, 0x01bbee, 0x0eacff, 0x0eabff, 0x0cafc0, + 0x0ec0ff, 0x0cafb1, 0x0ed1fb, 0x0eafff, 0x02cf00, 0x0d1fcc, + 0x0d5fbb, 0x0e1bff, 0x327edb, 0x0e1c00, 0x347ee6, 0x0ea2ff, + 0x0ea3ff, 0x0ea032, 0x0ea1f8, 0x0eaff0, 0x02cf00, 0x0ea064, + 0x0ea1f7, 0x18ad00, 0x1c1300, 0x327ece, 0x1c1201, 0x317e9b, + 0x0e1dff, 0x367e9b, 0x0ea041, 0x0ea1f7, 0x18ab00, 0x0eebdf, + 0x0cafbc, 0x0eabff, 0x0ccccc, 0x0cdbbb, 0x0cafc0, 0x0ec0ff, + 0x0cafb1, 0x0ed1fb, 0x18ab01, 0x0eacff, 0x18ae02, 0x0eadff, + 0x0ccecc, 0x0cddbb, 0x0d1fcc, 0x0d5fbb, 0x0cafbe, 0x0eadff, + 0x02ce01, 0x02cc02, 0x0d1f22, 0x0d5f33, 0x0ea064, 0x0ea1f7, + 0x18ad00, 0x0eacff, 0x0eabff, 0x0c1b9f, 0x327ea9, 0x0c1c8f, + 0x317ea1, 0x3fffff, 0x0d1fcc, 0x0d5fbb, 0x0c1b9f, 0x327ea2, + 0x0c1c8f, 0x357ea8, 0x1c1300, 0x327e9e, 0x1c1201, 0x317e9b, + 0x0e1dff, 0x327ecb, 0x0e1dff, 0x367e90, 0x0ea032, 0x0ea1f8, + 0x0eaf00, 0x02cf00, 0x0ea1fb, 0x0ea0ff, 0x0eaf00, 0x02cf00, + 0x337e88, 0x0ea032, 0x0ea1f8, 0x0eaf0f, 0x02cf00, 0x0ea1fb, + 0x0ea0ff, 0x0eaf0f, 0x02cf00, 0x0eacfd, 0x0eabff, 0x16af20, + 0x16a901, 0x16a801, 0x16a501, 0x16a401, 0x23ffff, 0x0eacf7, + 0x0eabff, 0x23ffff +}; + +#endif + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1272.cpp b/software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1272.cpp new file mode 100644 index 000000000..17a04dcf0 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1272.cpp @@ -0,0 +1,601 @@ +#include "SX1272.h" +#include +#if !RADIOLIB_EXCLUDE_SX127X + +SX1272::SX1272(Module* mod) : SX127x(mod) { + +} + +int16_t SX1272::begin(float freq, float bw, uint8_t sf, uint8_t cr, uint8_t syncWord, int8_t power, uint16_t preambleLength, uint8_t gain) { + // execute common part + uint8_t version = RADIOLIB_SX1272_CHIP_VERSION; + int16_t state = SX127x::begin(&version, 1, syncWord, preambleLength); + RADIOLIB_ASSERT(state); + + // configure publicly accessible settings + state = setBandwidth(bw); + RADIOLIB_ASSERT(state); + + state = setFrequency(freq); + RADIOLIB_ASSERT(state); + + state = setSpreadingFactor(sf); + RADIOLIB_ASSERT(state); + + state = setCodingRate(cr); + RADIOLIB_ASSERT(state); + + state = setOutputPower(power); + RADIOLIB_ASSERT(state); + + state = setGain(gain); + RADIOLIB_ASSERT(state); + + // set publicly accessible settings that are not a part of begin method + state = setCRC(true); + RADIOLIB_ASSERT(state); + + return(state); +} + +int16_t SX1272::beginFSK(float freq, float br, float freqDev, float rxBw, int8_t power, uint16_t preambleLength, bool enableOOK) { + // execute common part + uint8_t version = RADIOLIB_SX1272_CHIP_VERSION; + int16_t state = SX127x::beginFSK(&version, 1, freqDev, rxBw, preambleLength, enableOOK); + RADIOLIB_ASSERT(state); + + // configure settings not accessible by API + state = configFSK(); + RADIOLIB_ASSERT(state); + + // configure publicly accessible settings + state = setFrequency(freq); + RADIOLIB_ASSERT(state); + + state = setBitRate(br); + RADIOLIB_ASSERT(state); + + state = setOutputPower(power); + RADIOLIB_ASSERT(state); + + if(enableOOK) { + state = setDataShapingOOK(RADIOLIB_SHAPING_NONE); + RADIOLIB_ASSERT(state); + } else { + state = setDataShaping(RADIOLIB_SHAPING_NONE); + RADIOLIB_ASSERT(state); + } + + // set publicly accessible settings that are not a part of begin method + state = setCRC(true); + RADIOLIB_ASSERT(state); + + return(state); +} + +void SX1272::reset() { + Module* mod = this->getMod(); + mod->hal->pinMode(mod->getRst(), mod->hal->GpioModeOutput); + mod->hal->digitalWrite(mod->getRst(), mod->hal->GpioLevelHigh); + mod->hal->delay(1); + mod->hal->digitalWrite(mod->getRst(), mod->hal->GpioLevelLow); + mod->hal->delay(5); +} + +int16_t SX1272::setFrequency(float freq) { + RADIOLIB_CHECK_RANGE(freq, 860.0, 1020.0, RADIOLIB_ERR_INVALID_FREQUENCY); + + // set frequency and if successful, save the new setting + int16_t state = SX127x::setFrequencyRaw(freq); + if(state == RADIOLIB_ERR_NONE) { + SX127x::frequency = freq; + } + return(state); +} + +int16_t SX1272::setBandwidth(float bw) { + // check active modem + if(getActiveModem() != RADIOLIB_SX127X_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + uint8_t newBandwidth; + + // check allowed bandwidth values + if(fabsf(bw - 125.0) <= 0.001) { + newBandwidth = RADIOLIB_SX1272_BW_125_00_KHZ; + } else if(fabsf(bw - 250.0) <= 0.001) { + newBandwidth = RADIOLIB_SX1272_BW_250_00_KHZ; + } else if(fabsf(bw - 500.0) <= 0.001) { + newBandwidth = RADIOLIB_SX1272_BW_500_00_KHZ; + } else { + return(RADIOLIB_ERR_INVALID_BANDWIDTH); + } + + // set bandwidth and if successful, save the new setting + int16_t state = SX1272::setBandwidthRaw(newBandwidth); + if(state == RADIOLIB_ERR_NONE) { + SX127x::bandwidth = bw; + + // calculate symbol length and set low data rate optimization, if auto-configuration is enabled + if(this->ldroAuto) { + float symbolLength = (float)(uint32_t(1) << SX127x::spreadingFactor) / (float)SX127x::bandwidth; + Module* mod = this->getMod(); + if(symbolLength >= 16.0) { + state = mod->SPIsetRegValue(RADIOLIB_SX127X_REG_MODEM_CONFIG_1, RADIOLIB_SX1272_LOW_DATA_RATE_OPT_ON, 0, 0); + } else { + state = mod->SPIsetRegValue(RADIOLIB_SX127X_REG_MODEM_CONFIG_1, RADIOLIB_SX1272_LOW_DATA_RATE_OPT_OFF, 0, 0); + } + } + } + return(state); +} + +int16_t SX1272::setSpreadingFactor(uint8_t sf) { + // check active modem + if(getActiveModem() != RADIOLIB_SX127X_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + uint8_t newSpreadingFactor; + + // check allowed spreading factor values + switch(sf) { + case 6: + newSpreadingFactor = RADIOLIB_SX127X_SF_6; + break; + case 7: + newSpreadingFactor = RADIOLIB_SX127X_SF_7; + break; + case 8: + newSpreadingFactor = RADIOLIB_SX127X_SF_8; + break; + case 9: + newSpreadingFactor = RADIOLIB_SX127X_SF_9; + break; + case 10: + newSpreadingFactor = RADIOLIB_SX127X_SF_10; + break; + case 11: + newSpreadingFactor = RADIOLIB_SX127X_SF_11; + break; + case 12: + newSpreadingFactor = RADIOLIB_SX127X_SF_12; + break; + default: + return(RADIOLIB_ERR_INVALID_SPREADING_FACTOR); + } + + // set spreading factor and if successful, save the new setting + int16_t state = SX1272::setSpreadingFactorRaw(newSpreadingFactor); + if(state == RADIOLIB_ERR_NONE) { + SX127x::spreadingFactor = sf; + + // calculate symbol length and set low data rate optimization, if auto-configuration is enabled + if(this->ldroAuto) { + float symbolLength = (float)(uint32_t(1) << SX127x::spreadingFactor) / (float)SX127x::bandwidth; + Module* mod = this->getMod(); + if(symbolLength >= 16.0) { + state = mod->SPIsetRegValue(RADIOLIB_SX127X_REG_MODEM_CONFIG_1, RADIOLIB_SX1272_LOW_DATA_RATE_OPT_ON, 0, 0); + } else { + state = mod->SPIsetRegValue(RADIOLIB_SX127X_REG_MODEM_CONFIG_1, RADIOLIB_SX1272_LOW_DATA_RATE_OPT_OFF, 0, 0); + } + } + } + return(state); +} + +int16_t SX1272::setCodingRate(uint8_t cr) { + // check active modem + if(getActiveModem() != RADIOLIB_SX127X_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + uint8_t newCodingRate; + + // check allowed coding rate values + switch(cr) { + case 5: + newCodingRate = RADIOLIB_SX1272_CR_4_5; + break; + case 6: + newCodingRate = RADIOLIB_SX1272_CR_4_6; + break; + case 7: + newCodingRate = RADIOLIB_SX1272_CR_4_7; + break; + case 8: + newCodingRate = RADIOLIB_SX1272_CR_4_8; + break; + default: + return(RADIOLIB_ERR_INVALID_CODING_RATE); + } + + // set coding rate and if successful, save the new setting + int16_t state = SX1272::setCodingRateRaw(newCodingRate); + if(state == RADIOLIB_ERR_NONE) { + SX127x::codingRate = cr; + } + return(state); +} + +int16_t SX1272::setBitRate(float br) { + return(SX127x::setBitRateCommon(br, RADIOLIB_SX1272_REG_BIT_RATE_FRAC)); +} + +int16_t SX1272::setDataRate(DataRate_t dr) { + int16_t state = RADIOLIB_ERR_UNKNOWN; + + // select interpretation based on active modem + uint8_t modem = this->getActiveModem(); + if(modem == RADIOLIB_SX127X_FSK_OOK) { + // set the bit rate + state = this->setBitRate(dr.fsk.bitRate); + RADIOLIB_ASSERT(state); + + // set the frequency deviation + state = this->setFrequencyDeviation(dr.fsk.freqDev); + + } else if(modem == RADIOLIB_SX127X_LORA) { + // set the spreading factor + state = this->setSpreadingFactor(dr.lora.spreadingFactor); + RADIOLIB_ASSERT(state); + + // set the bandwidth + state = this->setBandwidth(dr.lora.bandwidth); + RADIOLIB_ASSERT(state); + + // set the coding rate + state = this->setCodingRate(dr.lora.codingRate); + } + + return(state); +} + +int16_t SX1272::checkDataRate(DataRate_t dr) { + int16_t state = RADIOLIB_ERR_UNKNOWN; + + // select interpretation based on active modem + int16_t modem = getActiveModem(); + if(modem == RADIOLIB_SX127X_FSK_OOK) { + RADIOLIB_CHECK_RANGE(dr.fsk.bitRate, 0.5, 300.0, RADIOLIB_ERR_INVALID_BIT_RATE); + if(!((dr.fsk.freqDev + dr.fsk.bitRate/2.0 <= 250.0) && (dr.fsk.freqDev <= 200.0))) { + return(RADIOLIB_ERR_INVALID_FREQUENCY_DEVIATION); + } + return(RADIOLIB_ERR_NONE); + + } else if(modem == RADIOLIB_SX127X_LORA) { + RADIOLIB_CHECK_RANGE(dr.lora.spreadingFactor, 6, 12, RADIOLIB_ERR_INVALID_SPREADING_FACTOR); + RADIOLIB_CHECK_RANGE(dr.lora.bandwidth, 100.0, 510.0, RADIOLIB_ERR_INVALID_BANDWIDTH); + RADIOLIB_CHECK_RANGE(dr.lora.codingRate, 5, 8, RADIOLIB_ERR_INVALID_CODING_RATE); + return(RADIOLIB_ERR_NONE); + + } + + return(state); +} + +int16_t SX1272::setOutputPower(int8_t power) { + return(this->setOutputPower(power, false)); +} + +int16_t SX1272::setOutputPower(int8_t power, bool useRfo) { + // check if power value is configurable + int16_t state = checkOutputPower(power, NULL, useRfo); + RADIOLIB_ASSERT(state); + + // set mode to standby + state = SX127x::standby(); + Module* mod = this->getMod(); + + if(useRfo) { + // RFO output + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PA_CONFIG, RADIOLIB_SX127X_PA_SELECT_RFO, 7, 7); + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PA_CONFIG, (power + 1), 3, 0); + state |= mod->SPIsetRegValue(RADIOLIB_SX1272_REG_PA_DAC, RADIOLIB_SX127X_PA_BOOST_OFF, 2, 0); + + } else { + if(power <= 17) { + // power is 2 - 17 dBm, enable PA1 + PA2 on PA_BOOST + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PA_CONFIG, RADIOLIB_SX127X_PA_SELECT_BOOST, 7, 7); + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PA_CONFIG, (power - 2), 3, 0); + state |= mod->SPIsetRegValue(RADIOLIB_SX1272_REG_PA_DAC, RADIOLIB_SX127X_PA_BOOST_OFF, 2, 0); + + } else { + // power is 18 - 20 dBm, enable PA1 + PA2 on PA_BOOST and enable high power control + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PA_CONFIG, RADIOLIB_SX127X_PA_SELECT_BOOST, 7, 7); + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PA_CONFIG, (power - 5), 3, 0); + state |= mod->SPIsetRegValue(RADIOLIB_SX1272_REG_PA_DAC, RADIOLIB_SX127X_PA_BOOST_ON, 2, 0); + + } + + } + + return(state); +} + +int16_t SX1272::checkOutputPower(int8_t power, int8_t* clipped) { + return(checkOutputPower(power, clipped, false)); +} + +int16_t SX1272::checkOutputPower(int8_t power, int8_t* clipped, bool useRfo) { + // check allowed power range + if(useRfo) { + if(clipped) { + *clipped = RADIOLIB_MAX(-1, RADIOLIB_MIN(14, power)); + } + RADIOLIB_CHECK_RANGE(power, -1, 14, RADIOLIB_ERR_INVALID_OUTPUT_POWER); + } else { + if(clipped) { + *clipped = RADIOLIB_MAX(2, RADIOLIB_MIN(20, power)); + } + RADIOLIB_CHECK_RANGE(power, 2, 20, RADIOLIB_ERR_INVALID_OUTPUT_POWER); + } + return(RADIOLIB_ERR_NONE); +} + +int16_t SX1272::setGain(uint8_t gain) { + // check allowed range + if(gain > 6) { + return(RADIOLIB_ERR_INVALID_GAIN); + } + + // set mode to standby + int16_t state = SX127x::standby(); + Module* mod = this->getMod(); + + // get modem + int16_t modem = getActiveModem(); + if(modem == RADIOLIB_SX127X_LORA) { + // set gain + if(gain == 0) { + // gain set to 0, enable AGC loop + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_MODEM_CONFIG_2, RADIOLIB_SX1272_AGC_AUTO_ON, 2, 2); + } else { + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_MODEM_CONFIG_2, RADIOLIB_SX1272_AGC_AUTO_OFF, 2, 2); + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_LNA, (gain << 5) | RADIOLIB_SX127X_LNA_BOOST_ON); + } + + } else if(modem == RADIOLIB_SX127X_FSK_OOK) { + // set gain + if(gain == 0) { + // gain set to 0, enable AGC loop + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_RX_CONFIG, RADIOLIB_SX127X_AGC_AUTO_ON, 3, 3); + } else { + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_RX_CONFIG, RADIOLIB_SX127X_AGC_AUTO_ON, 3, 3); + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_LNA, (gain << 5) | RADIOLIB_SX127X_LNA_BOOST_ON); + } + + } + + return(state); +} + +int16_t SX1272::setDataShaping(uint8_t sh) { + // check active modem + if(getActiveModem() != RADIOLIB_SX127X_FSK_OOK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // check modulation + if(SX127x::ookEnabled) { + return(RADIOLIB_ERR_INVALID_MODULATION); + } + + // set mode to standby + int16_t state = SX127x::standby(); + RADIOLIB_ASSERT(state); + + // set data shaping + Module* mod = this->getMod(); + switch(sh) { + case RADIOLIB_SHAPING_NONE: + return(mod->SPIsetRegValue(RADIOLIB_SX127X_REG_OP_MODE, RADIOLIB_SX1272_NO_SHAPING, 4, 3)); + case RADIOLIB_SHAPING_0_3: + return(mod->SPIsetRegValue(RADIOLIB_SX127X_REG_OP_MODE, RADIOLIB_SX1272_FSK_GAUSSIAN_0_3, 4, 3)); + case RADIOLIB_SHAPING_0_5: + return(mod->SPIsetRegValue(RADIOLIB_SX127X_REG_OP_MODE, RADIOLIB_SX1272_FSK_GAUSSIAN_0_5, 4, 3)); + case RADIOLIB_SHAPING_1_0: + return(mod->SPIsetRegValue(RADIOLIB_SX127X_REG_OP_MODE, RADIOLIB_SX1272_FSK_GAUSSIAN_1_0, 4, 3)); + default: + return(RADIOLIB_ERR_INVALID_DATA_SHAPING); + } +} + +int16_t SX1272::setDataShapingOOK(uint8_t sh) { + // check active modem + if(getActiveModem() != RADIOLIB_SX127X_FSK_OOK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // check modulation + if(!SX127x::ookEnabled) { + return(RADIOLIB_ERR_INVALID_MODULATION); + } + + // set mode to standby + int16_t state = SX127x::standby(); + + // set data shaping + Module* mod = this->getMod(); + switch(sh) { + case 0: + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_OP_MODE, RADIOLIB_SX1272_NO_SHAPING, 4, 3); + break; + case 1: + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_OP_MODE, RADIOLIB_SX1272_OOK_FILTER_BR, 4, 3); + break; + case 2: + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_OP_MODE, RADIOLIB_SX1272_OOK_FILTER_2BR, 4, 3); + break; + default: + state = RADIOLIB_ERR_INVALID_DATA_SHAPING; + break; + } + + return(state); +} + +float SX1272::getRSSI() { + return(SX1272::getRSSI(true, false)); +} + +float SX1272::getRSSI(bool packet, bool skipReceive) { + return(SX127x::getRSSI(packet, skipReceive, -139)); +} + +int16_t SX1272::setCRC(bool enable, bool mode) { + Module* mod = this->getMod(); + if(getActiveModem() == RADIOLIB_SX127X_LORA) { + // set LoRa CRC + SX127x::crcEnabled = enable; + if(enable) { + return(mod->SPIsetRegValue(RADIOLIB_SX127X_REG_MODEM_CONFIG_1, RADIOLIB_SX1272_RX_CRC_MODE_ON, 1, 1)); + } else { + return(mod->SPIsetRegValue(RADIOLIB_SX127X_REG_MODEM_CONFIG_1, RADIOLIB_SX1272_RX_CRC_MODE_OFF, 1, 1)); + } + } else { + // set FSK CRC + int16_t state = RADIOLIB_ERR_NONE; + if(enable) { + state = mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PACKET_CONFIG_1, RADIOLIB_SX127X_CRC_ON, 4, 4); + } else { + state = mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PACKET_CONFIG_1, RADIOLIB_SX127X_CRC_OFF, 4, 4); + } + RADIOLIB_ASSERT(state); + + // set FSK CRC mode + if(mode) { + return(mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PACKET_CONFIG_1, RADIOLIB_SX127X_CRC_WHITENING_TYPE_IBM, 0, 0)); + } else { + return(mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PACKET_CONFIG_1, RADIOLIB_SX127X_CRC_WHITENING_TYPE_CCITT, 0, 0)); + } + } +} + +int16_t SX1272::forceLDRO(bool enable) { + if(getActiveModem() != RADIOLIB_SX127X_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + this->ldroAuto = false; + Module* mod = this->getMod(); + if(enable) { + return(mod->SPIsetRegValue(RADIOLIB_SX127X_REG_MODEM_CONFIG_1, RADIOLIB_SX1272_LOW_DATA_RATE_OPT_ON, 0, 0)); + } else { + return(mod->SPIsetRegValue(RADIOLIB_SX127X_REG_MODEM_CONFIG_1, RADIOLIB_SX1272_LOW_DATA_RATE_OPT_OFF, 0, 0)); + } +} + +int16_t SX1272::autoLDRO() { + if(getActiveModem() != RADIOLIB_SX127X_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + this->ldroAuto = true; + return(RADIOLIB_ERR_NONE); +} + +int16_t SX1272::implicitHeader(size_t len) { + return(setHeaderType(RADIOLIB_SX1272_HEADER_IMPL_MODE, len)); +} + +int16_t SX1272::explicitHeader() { + return(setHeaderType(RADIOLIB_SX1272_HEADER_EXPL_MODE)); +} + +int16_t SX1272::setBandwidthRaw(uint8_t newBandwidth) { + // set mode to standby + int16_t state = SX127x::standby(); + + // write register + Module* mod = this->getMod(); + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_MODEM_CONFIG_1, newBandwidth, 7, 6); + return(state); +} + +int16_t SX1272::setSpreadingFactorRaw(uint8_t newSpreadingFactor) { + // set mode to standby + int16_t state = SX127x::standby(); + + // write registers + Module* mod = this->getMod(); + if(newSpreadingFactor == RADIOLIB_SX127X_SF_6) { + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_MODEM_CONFIG_1, RADIOLIB_SX1272_HEADER_IMPL_MODE | (SX127x::crcEnabled ? RADIOLIB_SX1272_RX_CRC_MODE_ON : RADIOLIB_SX1272_RX_CRC_MODE_OFF), 2, 1); + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_MODEM_CONFIG_2, RADIOLIB_SX127X_SF_6 | RADIOLIB_SX127X_TX_MODE_SINGLE, 7, 3); + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_DETECT_OPTIMIZE, RADIOLIB_SX127X_DETECT_OPTIMIZE_SF_6, 2, 0); + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_DETECTION_THRESHOLD, RADIOLIB_SX127X_DETECTION_THRESHOLD_SF_6); + } else { + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_MODEM_CONFIG_1, RADIOLIB_SX1272_HEADER_EXPL_MODE | (SX127x::crcEnabled ? RADIOLIB_SX1272_RX_CRC_MODE_ON : RADIOLIB_SX1272_RX_CRC_MODE_OFF), 2, 1); + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_MODEM_CONFIG_2, newSpreadingFactor | RADIOLIB_SX127X_TX_MODE_SINGLE, 7, 3); + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_DETECT_OPTIMIZE, RADIOLIB_SX127X_DETECT_OPTIMIZE_SF_7_12, 2, 0); + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_DETECTION_THRESHOLD, RADIOLIB_SX127X_DETECTION_THRESHOLD_SF_7_12); + } + return(state); +} + +int16_t SX1272::setCodingRateRaw(uint8_t newCodingRate) { + // set mode to standby + int16_t state = SX127x::standby(); + + // write register + Module* mod = this->getMod(); + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_MODEM_CONFIG_1, newCodingRate, 5, 3); + return(state); +} + +int16_t SX1272::setHeaderType(uint8_t headerType, size_t len) { + // check active modem + if(getActiveModem() != RADIOLIB_SX127X_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // set requested packet mode + Module* mod = this->getMod(); + int16_t state = mod->SPIsetRegValue(RADIOLIB_SX127X_REG_MODEM_CONFIG_1, headerType, 2, 2); + RADIOLIB_ASSERT(state); + + // set length to register + state = mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PAYLOAD_LENGTH, len); + RADIOLIB_ASSERT(state); + + // update cached value + SX127x::packetLength = len; + + return(state); +} + +int16_t SX1272::configFSK() { + // configure common registers + int16_t state = SX127x::configFSK(); + RADIOLIB_ASSERT(state); + + // set fast PLL hop + Module* mod = this->getMod(); + state = mod->SPIsetRegValue(RADIOLIB_SX1272_REG_PLL_HOP, RADIOLIB_SX127X_FAST_HOP_ON, 7, 7); + return(state); +} + +void SX1272::errataFix(bool rx) { + (void)rx; + + // mitigation of receiver spurious response + // see SX1272/73 Errata, section 2.2 for details + Module* mod = this->getMod(); + mod->SPIsetRegValue(0x31, 0b10000000, 7, 7); +} + +int16_t SX1272::setModem(ModemType_t modem) { + switch(modem) { + case(ModemType_t::RADIOLIB_MODEM_LORA): { + return(this->begin()); + } break; + case(ModemType_t::RADIOLIB_MODEM_FSK): { + return(this->beginFSK()); + } break; + default: + return(RADIOLIB_ERR_WRONG_MODEM); + } +} + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1272.h b/software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1272.h new file mode 100644 index 000000000..268996925 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1272.h @@ -0,0 +1,334 @@ +#if !defined(_RADIOLIB_SX1272_H) +#define _RADIOLIB_SX1272_H + +#include "../../TypeDef.h" + +#if !RADIOLIB_EXCLUDE_SX127X + +#include "../../Module.h" +#include "SX127x.h" + +// SX1272 specific register map +#define RADIOLIB_SX1272_REG_AGC_REF 0x43 +#define RADIOLIB_SX1272_REG_AGC_THRESH_1 0x44 +#define RADIOLIB_SX1272_REG_AGC_THRESH_2 0x45 +#define RADIOLIB_SX1272_REG_AGC_THRESH_3 0x46 +#define RADIOLIB_SX1272_REG_PLL_HOP 0x4B +#define RADIOLIB_SX1272_REG_TCXO 0x58 +#define RADIOLIB_SX1272_REG_PA_DAC 0x5A +#define RADIOLIB_SX1272_REG_PLL 0x5C +#define RADIOLIB_SX1272_REG_PLL_LOW_PN 0x5E +#define RADIOLIB_SX1272_REG_FORMER_TEMP 0x6C +#define RADIOLIB_SX1272_REG_BIT_RATE_FRAC 0x70 + +// SX1272 LoRa modem settings +// RADIOLIB_SX1272_REG_FRF_MSB + REG_FRF_MID + REG_FRF_LSB +#define RADIOLIB_SX1272_FRF_MSB 0xE4 // 7 0 carrier frequency setting: f_RF = (F(XOSC) * FRF)/2^19 +#define RADIOLIB_SX1272_FRF_MID 0xC0 // 7 0 where F(XOSC) = 32 MHz +#define RADIOLIB_SX1272_FRF_LSB 0x00 // 7 0 FRF = 3 byte value of FRF registers + +// RADIOLIB_SX127X_REG_MODEM_CONFIG_1 +#define RADIOLIB_SX1272_BW_125_00_KHZ 0b00000000 // 7 6 bandwidth: 125 kHz +#define RADIOLIB_SX1272_BW_250_00_KHZ 0b01000000 // 7 6 250 kHz +#define RADIOLIB_SX1272_BW_500_00_KHZ 0b10000000 // 7 6 500 kHz +#define RADIOLIB_SX1272_CR_4_5 0b00001000 // 5 3 error coding rate: 4/5 +#define RADIOLIB_SX1272_CR_4_6 0b00010000 // 5 3 4/6 +#define RADIOLIB_SX1272_CR_4_7 0b00011000 // 5 3 4/7 +#define RADIOLIB_SX1272_CR_4_8 0b00100000 // 5 3 4/8 +#define RADIOLIB_SX1272_HEADER_EXPL_MODE 0b00000000 // 2 2 explicit header mode +#define RADIOLIB_SX1272_HEADER_IMPL_MODE 0b00000100 // 2 2 implicit header mode +#define RADIOLIB_SX1272_RX_CRC_MODE_OFF 0b00000000 // 1 1 CRC disabled +#define RADIOLIB_SX1272_RX_CRC_MODE_ON 0b00000010 // 1 1 CRC enabled +#define RADIOLIB_SX1272_LOW_DATA_RATE_OPT_OFF 0b00000000 // 0 0 low data rate optimization disabled +#define RADIOLIB_SX1272_LOW_DATA_RATE_OPT_ON 0b00000001 // 0 0 low data rate optimization enabled, mandatory for SF 11 and 12 with BW 125 kHz + +// RADIOLIB_SX127X_REG_MODEM_CONFIG_2 +#define RADIOLIB_SX1272_AGC_AUTO_OFF 0b00000000 // 2 2 LNA gain set by REG_LNA +#define RADIOLIB_SX1272_AGC_AUTO_ON 0b00000100 // 2 2 LNA gain set by internal AGC loop + +// RADIOLIB_SX127X_REG_VERSION +#define RADIOLIB_SX1272_CHIP_VERSION 0x22 + +// SX1272 FSK modem settings +// RADIOLIB_SX127X_REG_OP_MODE +#define RADIOLIB_SX1272_NO_SHAPING 0b00000000 // 4 3 data shaping: no shaping (default) +#define RADIOLIB_SX1272_FSK_GAUSSIAN_1_0 0b00001000 // 4 3 FSK modulation Gaussian filter, BT = 1.0 +#define RADIOLIB_SX1272_FSK_GAUSSIAN_0_5 0b00010000 // 4 3 FSK modulation Gaussian filter, BT = 0.5 +#define RADIOLIB_SX1272_FSK_GAUSSIAN_0_3 0b00011000 // 4 3 FSK modulation Gaussian filter, BT = 0.3 +#define RADIOLIB_SX1272_OOK_FILTER_BR 0b00001000 // 4 3 OOK modulation filter, f_cutoff = BR +#define RADIOLIB_SX1272_OOK_FILTER_2BR 0b00010000 // 4 3 OOK modulation filter, f_cutoff = 2*BR + +// RADIOLIB_SX127X_REG_PA_RAMP +#define RADIOLIB_SX1272_LOW_PN_TX_PLL_OFF 0b00010000 // 4 4 use standard PLL in transmit mode (default) +#define RADIOLIB_SX1272_LOW_PN_TX_PLL_ON 0b00000000 // 4 4 use lower phase noise PLL in transmit mode + +// RADIOLIB_SX127X_REG_SYNC_CONFIG +#define RADIOLIB_SX1272_FIFO_FILL_CONDITION_SYNC_ADDRESS 0b00000000 // 3 3 FIFO will be filled when sync address interrupt occurs (default) +#define RADIOLIB_SX1272_FIFO_FILL_CONDITION_ALWAYS 0b00001000 // 3 3 FIFO will be filled as long as this bit is set + +// RADIOLIB_SX1272_REG_AGC_REF +#define RADIOLIB_SX1272_AGC_REFERENCE_LEVEL 0x13 // 5 0 floor reference for AGC thresholds: AgcRef = -174 + 10*log(2*RxBw) + 8 + AGC_REFERENCE_LEVEL [dBm] + +// RADIOLIB_SX1272_REG_AGC_THRESH_1 +#define RADIOLIB_SX1272_AGC_STEP_1 0x0E // 4 0 1st AGC threshold + +// RADIOLIB_SX1272_REG_AGC_THRESH_2 +#define RADIOLIB_SX1272_AGC_STEP_2 0x50 // 7 4 2nd AGC threshold +#define RADIOLIB_SX1272_AGC_STEP_3 0x0B // 4 0 3rd AGC threshold + +// RADIOLIB_SX1272_REG_AGC_THRESH_3 +#define RADIOLIB_SX1272_AGC_STEP_4 0xD0 // 7 4 4th AGC threshold +#define RADIOLIB_SX1272_AGC_STEP_5 0x0B // 4 0 5th AGC threshold + +// RADIOLIB_SX1272_REG_PLL_LOW_PN +#define RADIOLIB_SX1272_PLL_LOW_PN_BANDWIDTH_75_KHZ 0b00000000 // 7 6 low phase noise PLL bandwidth: 75 kHz +#define RADIOLIB_SX1272_PLL_LOW_PN_BANDWIDTH_150_KHZ 0b01000000 // 7 6 150 kHz +#define RADIOLIB_SX1272_PLL_LOW_PN_BANDWIDTH_225_KHZ 0b10000000 // 7 6 225 kHz +#define RADIOLIB_SX1272_PLL_LOW_PN_BANDWIDTH_300_KHZ 0b11000000 // 7 6 300 kHz (default) + +/*! + \class SX1272 + \brief Derived class for %SX1272 modules. Also used as base class for SX1273. + Both modules use the same basic hardware and only differ in parameter ranges. +*/ +class SX1272: public SX127x { + public: + + // constructor + + /*! + \brief Default constructor. Called from Arduino sketch when creating new LoRa instance. + \param mod Instance of Module that will be used to communicate with the %LoRa chip. + */ + SX1272(Module* mod); // cppcheck-suppress noExplicitConstructor + + // basic methods + + /*! + \brief %LoRa modem initialization method. Must be called at least once from Arduino sketch to initialize the module. + \param freq Carrier frequency in MHz. Allowed values range from 860.0 MHz to 1020.0 MHz. + \param bw %LoRa link bandwidth in kHz. Allowed values are 125, 250 and 500 kHz. + \param sf %LoRa link spreading factor. Allowed values range from 6 to 12. + \param cr %LoRa link coding rate denominator. Allowed values range from 5 to 8. + \param syncWord %LoRa sync word. Can be used to distinguish different networks. Note that value 0x34 is reserved for LoRaWAN networks. + \param power Transmission output power in dBm. Allowed values range from 2 to 17 dBm. + \param preambleLength Length of %LoRa transmission preamble in symbols. The actual preamble length is 4.25 symbols longer than the set number. + Allowed values range from 6 to 65535. + \param gain Gain of receiver LNA (low-noise amplifier). Can be set to any integer in range 1 to 6 where 1 is the highest gain. + Set to 0 to enable automatic gain control (recommended). + \returns \ref status_codes + */ + int16_t begin(float freq = 915.0, float bw = 125.0, uint8_t sf = 9, uint8_t cr = 7, uint8_t syncWord = RADIOLIB_SX127X_SYNC_WORD, int8_t power = 10, uint16_t preambleLength = 8, uint8_t gain = 0); + + /*! + \brief FSK modem initialization method. Must be called at least once from Arduino sketch to initialize the module. + \param freq Carrier frequency in MHz. Allowed values range from 860.0 MHz to 1020.0 MHz. + \param br Bit rate of the FSK transmission in kbps (kilobits per second). Allowed values range from 1.2 to 300.0 kbps. + \param freqDev Frequency deviation of the FSK transmission in kHz. Allowed values range from 0.6 to 200.0 kHz. + Note that the allowed range changes based on bit rate setting, so that the condition FreqDev + BitRate/2 <= 250 kHz is always met. + \param rxBw Receiver bandwidth in kHz. Allowed values are 2.6, 3.1, 3.9, 5.2, 6.3, 7.8, 10.4, 12.5, 15.6, 20.8, 25, 31.3, 41.7, 50, 62.5, 83.3, 100, 125, 166.7, 200 and 250 kHz. + \param power Transmission output power in dBm. Allowed values range from 2 to 17 dBm. + \param preambleLength Length of FSK preamble in bits. + \param enableOOK Use OOK modulation instead of FSK. + \returns \ref status_codes + */ + int16_t beginFSK(float freq = 915.0, float br = 4.8, float freqDev = 5.0, float rxBw = 125.0, int8_t power = 10, uint16_t preambleLength = 16, bool enableOOK = false); + + /*! + \brief Reset method. Will reset the chip to the default state using RST pin. + */ + void reset() override; + + // configuration methods + + /*! + \brief Sets carrier frequency. Allowed values range from 860.0 MHz to 1020.0 MHz. + \param freq Carrier frequency to be set in MHz. + \returns \ref status_codes + */ + int16_t setFrequency(float freq) override; + + /*! + \brief Sets %LoRa link bandwidth. Allowed values are 125, 250 and 500 kHz. Only available in %LoRa mode. + \param bw %LoRa link bandwidth to be set in kHz. + \returns \ref status_codes + */ + int16_t setBandwidth(float bw); + + /*! + \brief Sets %LoRa link spreading factor. Allowed values range from 6 to 12. Only available in %LoRa mode. + \param sf %LoRa link spreading factor to be set. + \returns \ref status_codes + */ + int16_t setSpreadingFactor(uint8_t sf); + + /*! + \brief Sets %LoRa link coding rate denominator. Allowed values range from 5 to 8. Only available in %LoRa mode. + \param cr %LoRa link coding rate denominator to be set. + \returns \ref status_codes + */ + int16_t setCodingRate(uint8_t cr); + + /*! + \brief Sets FSK bit rate. Allowed values range from 0.5 to 300 kbps. Only available in FSK mode. + \param br Bit rate to be set (in kbps). + \returns \ref status_codes + */ + int16_t setBitRate(float br) override; + + /*! + \brief Set data. + \param dr Data rate struct. Interpretation depends on currently active modem (FSK or LoRa). + \returns \ref status_codes + */ + int16_t setDataRate(DataRate_t dr) override; + + /*! + \brief Check the data rate can be configured by this module. + \param dr Data rate struct. Interpretation depends on currently active modem (FSK or LoRa). + \returns \ref status_codes + */ + int16_t checkDataRate(DataRate_t dr) override; + + /*! + \brief Sets transmission output power. Allowed values range from -1 to 14 dBm (RFO pin) or +2 to +20 dBm (PA_BOOST pin). + High power +20 dBm operation is also supported, on the PA_BOOST pin. Defaults to PA_BOOST. + \param power Transmission output power in dBm. + \returns \ref status_codes + */ + int16_t setOutputPower(int8_t power) override; + + /*! + \brief Sets transmission output power. Allowed values range from -1 to 14 dBm (RFO pin) or +2 to +20 dBm (PA_BOOST pin). + \param power Transmission output power in dBm. + \param useRfo Whether to use the RFO (true) or the PA_BOOST (false) pin for the RF output. + \returns \ref status_codes + */ + int16_t setOutputPower(int8_t power, bool useRfo); + + /*! + \brief Check if output power is configurable. + This method is needed for compatibility with PhysicalLayer::checkOutputPower. + \param power Output power in dBm, assumes PA_BOOST pin. + \param clipped Clipped output power value to what is possible within the module's range. + \returns \ref status_codes + */ + int16_t checkOutputPower(int8_t power, int8_t* clipped) override; + + /*! + \brief Check if output power is configurable. + \param power Output power in dBm. + \param clipped Clipped output power value to what is possible within the module's range. + \param useRfo Whether to use the RFO (true) or the PA_BOOST (false) pin for the RF output. + \returns \ref status_codes + */ + int16_t checkOutputPower(int8_t power, int8_t* clipped, bool useRfo); + + /*! + \brief Sets gain of receiver LNA (low-noise amplifier). Can be set to any integer in range 1 to 6 where 1 is the highest gain. + Set to 0 to enable automatic gain control (recommended). + \param gain Gain of receiver LNA (low-noise amplifier) to be set. + \returns \ref status_codes + */ + int16_t setGain(uint8_t gain); + + /*! + \brief Sets Gaussian filter bandwidth-time product that will be used for data shaping. Only available in FSK mode with FSK modulation. + Allowed values are RADIOLIB_SHAPING_0_3, RADIOLIB_SHAPING_0_5 or RADIOLIB_SHAPING_1_0. Set to RADIOLIB_SHAPING_NONE to disable data shaping. + \param sh Gaussian shaping bandwidth-time product that will be used for data shaping + \returns \ref status_codes + */ + int16_t setDataShaping(uint8_t sh) override; + + /*! + \brief Sets filter cutoff frequency that will be used for data shaping. + Allowed values are 1 for frequency equal to bit rate and 2 for frequency equal to 2x bit rate. Set to 0 to disable data shaping. + Only available in FSK mode with OOK modulation. + \param sh Cutoff frequency that will be used for data shaping + \returns \ref status_codes + */ + int16_t setDataShapingOOK(uint8_t sh); + + /*! + \brief Gets recorded signal strength indicator. + Overload with packet mode enabled for PhysicalLayer compatibility. + \returns RSSI value in dBm. + */ + float getRSSI() override; + + /*! + \brief Gets recorded signal strength indicator. + \param packet Whether to read last packet RSSI, or the current value. LoRa mode only, ignored for FSK. + \param skipReceive Set to true to skip putting radio in receive mode for the RSSI measurement in FSK/OOK mode. + \returns RSSI value in dBm. + */ + float getRSSI(bool packet, bool skipReceive = false); + + /*! + \brief Enables/disables CRC check of received packets. + \param enable Enable (true) or disable (false) CRC. + \param mode Set CRC mode to RADIOLIB_SX127X_CRC_WHITENING_TYPE_CCITT for CCITT, + polynomial X16 + X12 + X5 + 1 (false) or RADIOLIB_SX127X_CRC_WHITENING_TYPE_IBM for IBM, + polynomial X16 + X15 + X2 + 1 (true). Only valid in FSK mode. + \returns \ref status_codes + */ + int16_t setCRC(bool enable, bool mode = false); + + /*! + \brief Forces LoRa low data rate optimization. Only available in LoRa mode. After calling this method, LDRO will always be set to + the provided value, regardless of symbol length. To re-enable automatic LDRO configuration, call SX1278::autoLDRO() + \param enable Force LDRO to be always enabled (true) or disabled (false). + \returns \ref status_codes + */ + int16_t forceLDRO(bool enable); + + /*! + \brief Re-enables automatic LDRO configuration. Only available in LoRa mode. After calling this method, LDRO will be enabled automatically + when symbol length exceeds 16 ms. + \returns \ref status_codes + */ + int16_t autoLDRO(); + + /*! + \brief Set implicit header mode for future reception/transmission. Required for spreading factor 6. + \param len Payload length in bytes. + \returns \ref status_codes + */ + int16_t implicitHeader(size_t len); + + /*! + \brief Set explicit header mode for future reception/transmission. + \returns \ref status_codes + */ + int16_t explicitHeader(); + + /*! + \brief Set modem for the radio to use. Will perform full reset and reconfigure the radio + using its default parameters. + \param modem Modem type to set - FSK or LoRa. + \returns \ref status_codes + */ + int16_t setModem(ModemType_t modem) override; + +#if !RADIOLIB_GODMODE + protected: +#endif + int16_t setBandwidthRaw(uint8_t newBandwidth); + int16_t setSpreadingFactorRaw(uint8_t newSpreadingFactor); + int16_t setCodingRateRaw(uint8_t newCodingRate); + int16_t setHeaderType(uint8_t headerType, size_t len = 0xFF); + + int16_t configFSK(); + void errataFix(bool rx) override; + +#if !RADIOLIB_GODMODE + private: +#endif + bool ldroAuto = true; + bool ldroEnabled = false; + +}; + +#endif + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1273.cpp b/software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1273.cpp new file mode 100644 index 000000000..6a3af96d5 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1273.cpp @@ -0,0 +1,131 @@ +#include "SX1273.h" +#if !RADIOLIB_EXCLUDE_SX127X + +SX1273::SX1273(Module* mod) : SX1272(mod) { + +} + +int16_t SX1273::begin(float freq, float bw, uint8_t sf, uint8_t cr, uint8_t syncWord, int8_t power, uint16_t preambleLength, uint8_t gain) { + // execute common part + uint8_t version = RADIOLIB_SX1272_CHIP_VERSION; + int16_t state = SX127x::begin(&version, 1, syncWord, preambleLength); + RADIOLIB_ASSERT(state); + + // configure publicly accessible settings + state = setBandwidth(bw); + RADIOLIB_ASSERT(state); + + state = setFrequency(freq); + RADIOLIB_ASSERT(state); + + state = setSpreadingFactor(sf); + RADIOLIB_ASSERT(state); + + state = setCodingRate(cr); + RADIOLIB_ASSERT(state); + + state = setOutputPower(power); + RADIOLIB_ASSERT(state); + + state = setGain(gain); + RADIOLIB_ASSERT(state); + + // set publicly accessible settings that are not a part of begin method + state = setCRC(true); + RADIOLIB_ASSERT(state); + + return(state); +} + +int16_t SX1273::setSpreadingFactor(uint8_t sf) { + uint8_t newSpreadingFactor; + + // check allowed spreading factor values + switch(sf) { + case 6: + newSpreadingFactor = RADIOLIB_SX127X_SF_6; + break; + case 7: + newSpreadingFactor = RADIOLIB_SX127X_SF_7; + break; + case 8: + newSpreadingFactor = RADIOLIB_SX127X_SF_8; + break; + case 9: + newSpreadingFactor = RADIOLIB_SX127X_SF_9; + break; + default: + return(RADIOLIB_ERR_INVALID_SPREADING_FACTOR); + } + + // set spreading factor and if successful, save the new setting + int16_t state = setSpreadingFactorRaw(newSpreadingFactor); + if(state == RADIOLIB_ERR_NONE) { + SX127x::spreadingFactor = sf; + } + + return(state); +} + +int16_t SX1273::setDataRate(DataRate_t dr) { + int16_t state = RADIOLIB_ERR_UNKNOWN; + + // select interpretation based on active modem + uint8_t modem = this->getActiveModem(); + if(modem == RADIOLIB_SX127X_FSK_OOK) { + // set the bit rate + state = this->setBitRate(dr.fsk.bitRate); + RADIOLIB_ASSERT(state); + + // set the frequency deviation + state = this->setFrequencyDeviation(dr.fsk.freqDev); + + } else if(modem == RADIOLIB_SX127X_LORA) { + // set the spreading factor + state = this->setSpreadingFactor(dr.lora.spreadingFactor); + RADIOLIB_ASSERT(state); + + // set the bandwidth + state = this->setBandwidth(dr.lora.bandwidth); + } + + return(state); +} + +int16_t SX1273::checkDataRate(DataRate_t dr) { + int16_t state = RADIOLIB_ERR_UNKNOWN; + + // select interpretation based on active modem + int16_t modem = getActiveModem(); + if(modem == RADIOLIB_SX127X_FSK_OOK) { + RADIOLIB_CHECK_RANGE(dr.fsk.bitRate, 0.5, 300.0, RADIOLIB_ERR_INVALID_BIT_RATE); + if(!((dr.fsk.freqDev + dr.fsk.bitRate/2.0 <= 250.0) && (dr.fsk.freqDev <= 200.0))) { + return(RADIOLIB_ERR_INVALID_FREQUENCY_DEVIATION); + } + return(RADIOLIB_ERR_NONE); + + } else if(modem == RADIOLIB_SX127X_LORA) { + RADIOLIB_CHECK_RANGE(dr.lora.spreadingFactor, 6, 9, RADIOLIB_ERR_INVALID_SPREADING_FACTOR); + RADIOLIB_CHECK_RANGE(dr.lora.bandwidth, 100.0, 510.0, RADIOLIB_ERR_INVALID_BANDWIDTH); + RADIOLIB_CHECK_RANGE(dr.lora.codingRate, 5, 8, RADIOLIB_ERR_INVALID_CODING_RATE); + return(RADIOLIB_ERR_NONE); + + } + + return(state); +} + +int16_t SX1273::setModem(ModemType_t modem) { + switch(modem) { + case(ModemType_t::RADIOLIB_MODEM_LORA): { + return(this->begin()); + } break; + case(ModemType_t::RADIOLIB_MODEM_FSK): { + return(this->beginFSK()); + } break; + default: + return(RADIOLIB_ERR_WRONG_MODEM); + } +} + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1273.h b/software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1273.h new file mode 100644 index 000000000..4e9dd1ed7 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1273.h @@ -0,0 +1,82 @@ +#if !defined(_RADIOLIB_SX1273_H) +#define _RADIOLIB_SX1273_H + +#include "../../TypeDef.h" + +#if !RADIOLIB_EXCLUDE_SX127X + +#include "SX1272.h" + +/*! + \class SX1273 + \brief Derived class for %SX1273 modules. Overrides some methods from SX1272 due to different parameter ranges. +*/ +class SX1273: public SX1272 { + public: + + // constructor + + /*! + \brief Default constructor. Called from Arduino sketch when creating new LoRa instance. + \param mod Instance of Module that will be used to communicate with the %LoRa chip. + */ + SX1273(Module* mod); // cppcheck-suppress noExplicitConstructor + + // basic methods + + /*! + \brief %LoRa modem initialization method. Must be called at least once from Arduino sketch to initialize the module. + \param freq Carrier frequency in MHz. Allowed values range from 860.0 MHz to 1020.0 MHz. + \param bw %LoRa link bandwidth in kHz. Allowed values are 125, 250 and 500 kHz. + \param sf %LoRa link spreading factor. Allowed values range from 6 to 9. + \param cr %LoRa link coding rate denominator. Allowed values range from 5 to 8. + \param syncWord %LoRa sync word. Can be used to distinguish different networks. Note that value 0x34 is reserved for LoRaWAN networks. + \param power Transmission output power in dBm. Allowed values range from 2 to 17 dBm. + \param preambleLength Length of %LoRa transmission preamble in symbols. The actual preamble length is 4.25 symbols longer than the set number. + Allowed values range from 6 to 65535. + \param gain Gain of receiver LNA (low-noise amplifier). Can be set to any integer in range 1 to 6 where 1 is the highest gain. + Set to 0 to enable automatic gain control (recommended). + \returns \ref status_codes + */ + int16_t begin(float freq = 915.0, float bw = 125.0, uint8_t sf = 9, uint8_t cr = 7, uint8_t syncWord = RADIOLIB_SX127X_SYNC_WORD, int8_t power = 10, uint16_t preambleLength = 8, uint8_t gain = 0); + + // configuration methods + + /*! + \brief Sets %LoRa link spreading factor. Allowed values range from 6 to 9. Only available in %LoRa mode. + \param sf %LoRa link spreading factor to be set. + \returns \ref status_codes + */ + int16_t setSpreadingFactor(uint8_t sf); + + /*! + \brief Set data. + \param dr Data rate struct. Interpretation depends on currently active modem (FSK or LoRa). + \returns \ref status_codes + */ + int16_t setDataRate(DataRate_t dr) override; + + /*! + \brief Check the data rate can be configured by this module. + \param dr Data rate struct. Interpretation depends on currently active modem (FSK or LoRa). + \returns \ref status_codes + */ + int16_t checkDataRate(DataRate_t dr) override; + + /*! + \brief Set modem for the radio to use. Will perform full reset and reconfigure the radio + using its default parameters. + \param modem Modem type to set - FSK or LoRa. + \returns \ref status_codes + */ + int16_t setModem(ModemType_t modem) override; + +#if !RADIOLIB_GODMODE + private: +#endif + +}; + +#endif + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1276.cpp b/software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1276.cpp new file mode 100644 index 000000000..062d11b05 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1276.cpp @@ -0,0 +1,95 @@ +#include "SX1276.h" +#if !RADIOLIB_EXCLUDE_SX127X + +SX1276::SX1276(Module* mod) : SX1278(mod) { + +} + +int16_t SX1276::begin(float freq, float bw, uint8_t sf, uint8_t cr, uint8_t syncWord, int8_t power, uint16_t preambleLength, uint8_t gain) { + // execute common part + uint8_t versions[] = { RADIOLIB_SX1278_CHIP_VERSION, RADIOLIB_SX1278_CHIP_VERSION_ALT, RADIOLIB_SX1278_CHIP_VERSION_RFM9X }; + int16_t state = SX127x::begin(versions, 3, syncWord, preambleLength); + RADIOLIB_ASSERT(state); + + // configure publicly accessible settings + state = setBandwidth(bw); + RADIOLIB_ASSERT(state); + + state = setFrequency(freq); + RADIOLIB_ASSERT(state); + + state = setSpreadingFactor(sf); + RADIOLIB_ASSERT(state); + + state = setCodingRate(cr); + RADIOLIB_ASSERT(state); + + state = setOutputPower(power); + RADIOLIB_ASSERT(state); + + state = setGain(gain); + RADIOLIB_ASSERT(state); + + // set publicly accessible settings that are not a part of begin method + state = setCRC(true); + RADIOLIB_ASSERT(state); + + return(state); +} + +int16_t SX1276::beginFSK(float freq, float br, float freqDev, float rxBw, int8_t power, uint16_t preambleLength, bool enableOOK) { + // execute common part + uint8_t versions[] = { RADIOLIB_SX1278_CHIP_VERSION, RADIOLIB_SX1278_CHIP_VERSION_ALT, RADIOLIB_SX1278_CHIP_VERSION_RFM9X }; + int16_t state = SX127x::beginFSK(versions, 3, freqDev, rxBw, preambleLength, enableOOK); + RADIOLIB_ASSERT(state); + + // configure settings not accessible by API + state = configFSK(); + RADIOLIB_ASSERT(state); + + // configure publicly accessible settings + state = setFrequency(freq); + RADIOLIB_ASSERT(state); + + state = setBitRate(br); + RADIOLIB_ASSERT(state); + + state = setOutputPower(power); + RADIOLIB_ASSERT(state); + + if(enableOOK) { + state = setDataShapingOOK(RADIOLIB_SHAPING_NONE); + RADIOLIB_ASSERT(state); + } else { + state = setDataShaping(RADIOLIB_SHAPING_NONE); + RADIOLIB_ASSERT(state); + } + + return(state); +} + +int16_t SX1276::setFrequency(float freq) { + RADIOLIB_CHECK_RANGE(freq, 137.0, 1020.0, RADIOLIB_ERR_INVALID_FREQUENCY); + + // set frequency and if successful, save the new setting + int16_t state = SX127x::setFrequencyRaw(freq); + if(state == RADIOLIB_ERR_NONE) { + SX127x::frequency = freq; + } + return(state); +} + +int16_t SX1276::setModem(ModemType_t modem) { + switch(modem) { + case(ModemType_t::RADIOLIB_MODEM_LORA): { + return(this->begin()); + } break; + case(ModemType_t::RADIOLIB_MODEM_FSK): { + return(this->beginFSK()); + } break; + default: + return(RADIOLIB_ERR_WRONG_MODEM); + } +} + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1276.h b/software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1276.h new file mode 100644 index 000000000..64dbaa3e3 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1276.h @@ -0,0 +1,94 @@ +#if !defined(_RADIOLIB_SX1276_H) +#define _RADIOLIB_SX1276_H + +#include "../../TypeDef.h" + +#if !RADIOLIB_EXCLUDE_SX127X + +#include "SX1278.h" + +/*! + \class SX1276 + \brief Derived class for %SX1276 modules. Overrides some methods from SX1278 due to different parameter ranges. +*/ +class SX1276: public SX1278 { + public: + + // constructor + + /*! + \brief Default constructor. Called from Arduino sketch when creating new LoRa instance. + \param mod Instance of Module that will be used to communicate with the %LoRa chip. + */ + SX1276(Module* mod); // cppcheck-suppress noExplicitConstructor + + // basic methods + + /*! + \brief %LoRa modem initialization method. Must be called at least once from Arduino sketch to initialize the module. + \param freq Carrier frequency in MHz. Allowed values range from 137.0 MHz to 1020.0 MHz. + \param bw %LoRa link bandwidth in kHz. Allowed values are 10.4, 15.6, 20.8, 31.25, 41.7, 62.5, 125, 250 and 500 kHz. + \param sf %LoRa link spreading factor. Allowed values range from 6 to 12. + \param cr %LoRa link coding rate denominator. Allowed values range from 5 to 8. + \param syncWord %LoRa sync word. Can be used to distinguish different networks. Note that value 0x34 is reserved for LoRaWAN networks. + \param power Transmission output power in dBm. Allowed values range from 2 to 17 dBm. + \param preambleLength Length of %LoRa transmission preamble in symbols. The actual preamble length is 4.25 symbols longer than the set number. + Allowed values range from 6 to 65535. + \param gain Gain of receiver LNA (low-noise amplifier). Can be set to any integer in range 1 to 6 where 1 is the highest gain. + Set to 0 to enable automatic gain control (recommended). + \returns \ref status_codes + */ + int16_t begin(float freq = 434.0, float bw = 125.0, uint8_t sf = 9, uint8_t cr = 7, uint8_t syncWord = RADIOLIB_SX127X_SYNC_WORD, int8_t power = 10, uint16_t preambleLength = 8, uint8_t gain = 0); + + /*! + \brief FSK modem initialization method. Must be called at least once from Arduino sketch to initialize the module. + \param freq Carrier frequency in MHz. Allowed values range from 137.0 MHz to 1020.0 MHz. + \param br Bit rate of the FSK transmission in kbps (kilobits per second). Allowed values range from 1.2 to 300.0 kbps. + \param freqDev Frequency deviation of the FSK transmission in kHz. Allowed values range from 0.6 to 200.0 kHz. + Note that the allowed range changes based on bit rate setting, so that the condition FreqDev + BitRate/2 <= 250 kHz is always met. + \param rxBw Receiver bandwidth in kHz. Allowed values are 2.6, 3.1, 3.9, 5.2, 6.3, 7.8, 10.4, 12.5, 15.6, 20.8, 25, 31.3, 41.7, 50, 62.5, 83.3, 100, 125, 166.7, 200 and 250 kHz. + \param power Transmission output power in dBm. Allowed values range from 2 to 17 dBm. + \param preambleLength Length of FSK preamble in bits. + \param enableOOK Use OOK modulation instead of FSK. + \returns \ref status_codes + */ + int16_t beginFSK(float freq = 434.0, float br = 4.8, float freqDev = 5.0, float rxBw = 125.0, int8_t power = 10, uint16_t preambleLength = 16, bool enableOOK = false); + + // configuration methods + + /*! + \brief Sets carrier frequency. Allowed values range from 137.0 MHz to 1020.0 MHz. + \param freq Carrier frequency to be set in MHz. + \returns \ref status_codes + */ + int16_t setFrequency(float freq) override; + + /*! + \brief Set modem for the radio to use. Will perform full reset and reconfigure the radio + using its default parameters. + \param modem Modem type to set - FSK or LoRa. + \returns \ref status_codes + */ + int16_t setModem(ModemType_t modem) override; + +#if !RADIOLIB_GODMODE + private: +#endif + +}; + +/*! + \class RFM95 + \brief Only exists as alias for SX1276, since there seems to be no difference between %RFM95 and %SX1276 modules. +*/ +RADIOLIB_TYPE_ALIAS(SX1276, RFM95) + +/*! + \class RFM96 + \brief Only exists as alias for SX1276, since there seems to be no difference between %RFM96 and %SX1276 modules. +*/ +RADIOLIB_TYPE_ALIAS(SX1276, RFM96) + +#endif + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1277.cpp b/software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1277.cpp new file mode 100644 index 000000000..59729e898 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1277.cpp @@ -0,0 +1,173 @@ +#include "SX1277.h" +#if !RADIOLIB_EXCLUDE_SX127X + +SX1277::SX1277(Module* mod) : SX1278(mod) { + +} + +int16_t SX1277::begin(float freq, float bw, uint8_t sf, uint8_t cr, uint8_t syncWord, int8_t power, uint16_t preambleLength, uint8_t gain) { + // execute common part + uint8_t versions[] = { RADIOLIB_SX1278_CHIP_VERSION, RADIOLIB_SX1278_CHIP_VERSION_ALT, RADIOLIB_SX1278_CHIP_VERSION_RFM9X }; + int16_t state = SX127x::begin(versions, 3, syncWord, preambleLength); + RADIOLIB_ASSERT(state); + + // configure publicly accessible settings + state = setBandwidth(bw); + RADIOLIB_ASSERT(state); + + state = setFrequency(freq); + RADIOLIB_ASSERT(state); + + state = setSpreadingFactor(sf); + RADIOLIB_ASSERT(state); + + state = setCodingRate(cr); + RADIOLIB_ASSERT(state); + + state = setOutputPower(power); + RADIOLIB_ASSERT(state); + + state = setGain(gain); + RADIOLIB_ASSERT(state); + + // set publicly accessible settings that are not a part of begin method + state = setCRC(true); + RADIOLIB_ASSERT(state); + + return(state); +} + +int16_t SX1277::beginFSK(float freq, float br, float freqDev, float rxBw, int8_t power, uint16_t preambleLength, bool enableOOK) { + // execute common part + uint8_t versions[] = { RADIOLIB_SX1278_CHIP_VERSION, RADIOLIB_SX1278_CHIP_VERSION_ALT, RADIOLIB_SX1278_CHIP_VERSION_RFM9X }; + int16_t state = SX127x::beginFSK(versions, 3, freqDev, rxBw, preambleLength, enableOOK); + RADIOLIB_ASSERT(state); + + // configure settings not accessible by API + state = configFSK(); + RADIOLIB_ASSERT(state); + + // configure publicly accessible settings + state = setFrequency(freq); + RADIOLIB_ASSERT(state); + + state = setBitRate(br); + RADIOLIB_ASSERT(state); + + state = setOutputPower(power); + RADIOLIB_ASSERT(state); + + if(enableOOK) { + state = setDataShapingOOK(RADIOLIB_SHAPING_NONE); + RADIOLIB_ASSERT(state); + } else { + state = setDataShaping(RADIOLIB_SHAPING_NONE); + RADIOLIB_ASSERT(state); + } + + return(state); +} + +int16_t SX1277::setFrequency(float freq) { + RADIOLIB_CHECK_RANGE(freq, 137.0, 1020.0, RADIOLIB_ERR_INVALID_FREQUENCY); + + // set frequency and if successful, save the new setting + int16_t state = SX127x::setFrequencyRaw(freq); + if(state == RADIOLIB_ERR_NONE) { + SX127x::frequency = freq; + } + return(state); +} + +int16_t SX1277::setSpreadingFactor(uint8_t sf) { + uint8_t newSpreadingFactor; + + // check allowed spreading factor values + switch(sf) { + case 6: + newSpreadingFactor = RADIOLIB_SX127X_SF_6; + break; + case 7: + newSpreadingFactor = RADIOLIB_SX127X_SF_7; + break; + case 8: + newSpreadingFactor = RADIOLIB_SX127X_SF_8; + break; + case 9: + newSpreadingFactor = RADIOLIB_SX127X_SF_9; + break; + default: + return(RADIOLIB_ERR_INVALID_SPREADING_FACTOR); + } + + // set spreading factor and if successful, save the new setting + int16_t state = SX1278::setSpreadingFactorRaw(newSpreadingFactor); + if(state == RADIOLIB_ERR_NONE) { + SX127x::spreadingFactor = sf; + } + + return(state); +} + +int16_t SX1277::setDataRate(DataRate_t dr) { + int16_t state = RADIOLIB_ERR_UNKNOWN; + + // select interpretation based on active modem + uint8_t modem = this->getActiveModem(); + if(modem == RADIOLIB_SX127X_FSK_OOK) { + // set the bit rate + state = this->setBitRate(dr.fsk.bitRate); + RADIOLIB_ASSERT(state); + + // set the frequency deviation + state = this->setFrequencyDeviation(dr.fsk.freqDev); + + } else if(modem == RADIOLIB_SX127X_LORA) { + // set the spreading factor + state = this->setSpreadingFactor(dr.lora.spreadingFactor); + RADIOLIB_ASSERT(state); + + // set the bandwidth + state = this->setBandwidth(dr.lora.bandwidth); + } + + return(state); +} + +int16_t SX1277::checkDataRate(DataRate_t dr) { + int16_t state = RADIOLIB_ERR_UNKNOWN; + + // select interpretation based on active modem + int16_t modem = getActiveModem(); + if(modem == RADIOLIB_SX127X_FSK_OOK) { + RADIOLIB_CHECK_RANGE(dr.fsk.bitRate, 0.5, 300.0, RADIOLIB_ERR_INVALID_BIT_RATE); + if(!((dr.fsk.freqDev + dr.fsk.bitRate/2.0 <= 250.0) && (dr.fsk.freqDev <= 200.0))) { + return(RADIOLIB_ERR_INVALID_FREQUENCY_DEVIATION); + } + return(RADIOLIB_ERR_NONE); + + } else if(modem == RADIOLIB_SX127X_LORA) { + RADIOLIB_CHECK_RANGE(dr.lora.spreadingFactor, 6, 9, RADIOLIB_ERR_INVALID_SPREADING_FACTOR); + RADIOLIB_CHECK_RANGE(dr.lora.bandwidth, 0.0, 510.0, RADIOLIB_ERR_INVALID_BANDWIDTH); + RADIOLIB_CHECK_RANGE(dr.lora.codingRate, 5, 8, RADIOLIB_ERR_INVALID_CODING_RATE); + return(RADIOLIB_ERR_NONE); + + } + + return(state); +} + +int16_t SX1277::setModem(ModemType_t modem) { + switch(modem) { + case(ModemType_t::RADIOLIB_MODEM_LORA): { + return(this->begin()); + } break; + case(ModemType_t::RADIOLIB_MODEM_FSK): { + return(this->beginFSK()); + } break; + default: + return(RADIOLIB_ERR_WRONG_MODEM); + } +} + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1277.h b/software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1277.h new file mode 100644 index 000000000..fe068c0d7 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1277.h @@ -0,0 +1,109 @@ +#if !defined(_RADIOLIB_SX1277_H) +#define _RADIOLIB_SX1277_H + +#include "../../TypeDef.h" + +#if !RADIOLIB_EXCLUDE_SX127X + +#include "SX1278.h" + +/*! + \class SX1277 + \brief Derived class for %SX1277 modules. Overrides some methods from SX1278 due to different parameter ranges. +*/ +class SX1277: public SX1278 { + public: + + // constructor + + /*! + \brief Default constructor. Called from Arduino sketch when creating new LoRa instance. + \param mod Instance of Module that will be used to communicate with the %LoRa chip. + */ + SX1277(Module* mod); // cppcheck-suppress noExplicitConstructor + + // basic methods + + /*! + \brief %LoRa modem initialization method. Must be called at least once from Arduino sketch to initialize the module. + \param freq Carrier frequency in MHz. Allowed values range from 137.0 MHz to 1020.0 MHz. + \param bw %LoRa link bandwidth in kHz. Allowed values are 10.4, 15.6, 20.8, 31.25, 41.7, 62.5, 125, 250 and 500 kHz. + \param sf %LoRa link spreading factor. Allowed values range from 6 to 9. + \param cr %LoRa link coding rate denominator. Allowed values range from 5 to 8. + \param syncWord %LoRa sync word. Can be used to distinguish different networks. Note that value 0x34 is reserved for LoRaWAN networks. + \param power Transmission output power in dBm. Allowed values range from 2 to 17 dBm. + \param preambleLength Length of %LoRa transmission preamble in symbols. The actual preamble length is 4.25 symbols longer than the set number. + Allowed values range from 6 to 65535. + \param gain Gain of receiver LNA (low-noise amplifier). Can be set to any integer in range 1 to 6 where 1 is the highest gain. + Set to 0 to enable automatic gain control (recommended). + \returns \ref status_codes + */ + int16_t begin(float freq = 434.0, float bw = 125.0, uint8_t sf = 9, uint8_t cr = 7, uint8_t syncWord = RADIOLIB_SX127X_SYNC_WORD, int8_t power = 10, uint16_t preambleLength = 8, uint8_t gain = 0); + + /*! + \brief FSK modem initialization method. Must be called at least once from Arduino sketch to initialize the module. + \param freq Carrier frequency in MHz. Allowed values range from 137.0 MHz to 525.0 MHz. + \param br Bit rate of the FSK transmission in kbps (kilobits per second). Allowed values range from 1.2 to 300.0 kbps. + \param freqDev Frequency deviation of the FSK transmission in kHz. Allowed values range from 0.6 to 200.0 kHz. + Note that the allowed range changes based on bit rate setting, so that the condition FreqDev + BitRate/2 <= 250 kHz is always met. + \param rxBw Receiver bandwidth in kHz. Allowed values are 2.6, 3.1, 3.9, 5.2, 6.3, 7.8, 10.4, 12.5, 15.6, 20.8, 25, 31.3, 41.7, 50, 62.5, 83.3, 100, 125, 166.7, 200 and 250 kHz. + \param power Transmission output power in dBm. Allowed values range from 2 to 17 dBm. + \param preambleLength Length of FSK preamble in bits. + \param enableOOK Use OOK modulation instead of FSK. + \returns \ref status_codes + */ + int16_t beginFSK(float freq = 434.0, float br = 4.8, float freqDev = 5.0, float rxBw = 125.0, int8_t power = 10, uint16_t preambleLength = 16, bool enableOOK = false); + + // configuration methods + + /*! + \brief Sets carrier frequency. Allowed values range from 137.0 MHz to 1020.0 MHz. + \param freq Carrier frequency to be set in MHz. + \returns \ref status_codes + */ + int16_t setFrequency(float freq) override; + + /*! + \brief Sets %LoRa link spreading factor. Allowed values range from 6 to 9. Only available in %LoRa mode. + \param sf %LoRa link spreading factor to be set. + \returns \ref status_codes + */ + int16_t setSpreadingFactor(uint8_t sf); + + /*! + \brief Set data. + \param dr Data rate struct. Interpretation depends on currently active modem (FSK or LoRa). + \returns \ref status_codes + */ + int16_t setDataRate(DataRate_t dr) override; + + /*! + \brief Check the data rate can be configured by this module. + \param dr Data rate struct. Interpretation depends on currently active modem (FSK or LoRa). + \returns \ref status_codes + */ + int16_t checkDataRate(DataRate_t dr) override; + + /*! + \brief Set modem for the radio to use. Will perform full reset and reconfigure the radio + using its default parameters. + \param modem Modem type to set - FSK or LoRa. + \returns \ref status_codes + */ + int16_t setModem(ModemType_t modem) override; + +#if !RADIOLIB_GODMODE + private: +#endif + +}; + +/*! + \class RFM97 + \brief Only exists as alias for SX1277, since there seems to be no difference between %RFM97 and %SX1277 modules. +*/ +RADIOLIB_TYPE_ALIAS(SX1277, RFM97) + +#endif + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1278.cpp b/software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1278.cpp new file mode 100644 index 000000000..c76859059 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1278.cpp @@ -0,0 +1,721 @@ +#include "SX1278.h" +#include +#if !RADIOLIB_EXCLUDE_SX127X + +SX1278::SX1278(Module* mod) : SX127x(mod) { + +} + +int16_t SX1278::begin(float freq, float bw, uint8_t sf, uint8_t cr, uint8_t syncWord, int8_t power, uint16_t preambleLength, uint8_t gain) { + // execute common part + uint8_t versions[] = { RADIOLIB_SX1278_CHIP_VERSION, RADIOLIB_SX1278_CHIP_VERSION_ALT, RADIOLIB_SX1278_CHIP_VERSION_RFM9X }; + int16_t state = SX127x::begin(versions, 3, syncWord, preambleLength); + RADIOLIB_ASSERT(state); + + // configure publicly accessible settings + state = setBandwidth(bw); + RADIOLIB_ASSERT(state); + + state = setFrequency(freq); + RADIOLIB_ASSERT(state); + + state = setSpreadingFactor(sf); + RADIOLIB_ASSERT(state); + + state = setCodingRate(cr); + RADIOLIB_ASSERT(state); + + state = setOutputPower(power); + RADIOLIB_ASSERT(state); + + state = setGain(gain); + RADIOLIB_ASSERT(state); + + // set publicly accessible settings that are not a part of begin method + state = setCRC(true); + RADIOLIB_ASSERT(state); + + return(state); +} + +int16_t SX1278::beginFSK(float freq, float br, float freqDev, float rxBw, int8_t power, uint16_t preambleLength, bool enableOOK) { + // execute common part + uint8_t versions[] = { RADIOLIB_SX1278_CHIP_VERSION, RADIOLIB_SX1278_CHIP_VERSION_ALT, RADIOLIB_SX1278_CHIP_VERSION_RFM9X }; + int16_t state = SX127x::beginFSK(versions, 3, freqDev, rxBw, preambleLength, enableOOK); + RADIOLIB_ASSERT(state); + + // configure settings not accessible by API + state = configFSK(); + RADIOLIB_ASSERT(state); + + // configure publicly accessible settings + state = setFrequency(freq); + RADIOLIB_ASSERT(state); + + state = setBitRate(br); + RADIOLIB_ASSERT(state); + + state = setOutputPower(power); + RADIOLIB_ASSERT(state); + + if(enableOOK) { + state = setDataShapingOOK(RADIOLIB_SHAPING_NONE); + RADIOLIB_ASSERT(state); + } else { + state = setDataShaping(RADIOLIB_SHAPING_NONE); + RADIOLIB_ASSERT(state); + } + + // set publicly accessible settings that are not a part of begin method + state = setCRC(true); + RADIOLIB_ASSERT(state); + + return(state); +} + +void SX1278::reset() { + Module* mod = this->getMod(); + mod->hal->pinMode(mod->getRst(), mod->hal->GpioModeOutput); + mod->hal->digitalWrite(mod->getRst(), mod->hal->GpioLevelLow); + mod->hal->delay(1); + mod->hal->digitalWrite(mod->getRst(), mod->hal->GpioLevelHigh); + mod->hal->delay(5); +} + +int16_t SX1278::setFrequency(float freq) { + RADIOLIB_CHECK_RANGE(freq, 137.0, 525.0, RADIOLIB_ERR_INVALID_FREQUENCY); + + // set frequency and if successful, save the new setting + int16_t state = SX127x::setFrequencyRaw(freq); + if(state == RADIOLIB_ERR_NONE) { + SX127x::frequency = freq; + } + return(state); +} + +int16_t SX1278::setBandwidth(float bw) { + // check active modem + if(getActiveModem() != RADIOLIB_SX127X_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + uint8_t newBandwidth; + + // check allowed bandwidth values + if(fabsf(bw - 7.8) <= 0.001) { + newBandwidth = RADIOLIB_SX1278_BW_7_80_KHZ; + } else if(fabsf(bw - 10.4) <= 0.001) { + newBandwidth = RADIOLIB_SX1278_BW_10_40_KHZ; + } else if(fabsf(bw - 15.6) <= 0.001) { + newBandwidth = RADIOLIB_SX1278_BW_15_60_KHZ; + } else if(fabsf(bw - 20.8) <= 0.001) { + newBandwidth = RADIOLIB_SX1278_BW_20_80_KHZ; + } else if(fabsf(bw - 31.25) <= 0.001) { + newBandwidth = RADIOLIB_SX1278_BW_31_25_KHZ; + } else if(fabsf(bw - 41.7) <= 0.001) { + newBandwidth = RADIOLIB_SX1278_BW_41_70_KHZ; + } else if(fabsf(bw - 62.5) <= 0.001) { + newBandwidth = RADIOLIB_SX1278_BW_62_50_KHZ; + } else if(fabsf(bw - 125.0) <= 0.001) { + newBandwidth = RADIOLIB_SX1278_BW_125_00_KHZ; + } else if(fabsf(bw - 250.0) <= 0.001) { + newBandwidth = RADIOLIB_SX1278_BW_250_00_KHZ; + } else if(fabsf(bw - 500.0) <= 0.001) { + newBandwidth = RADIOLIB_SX1278_BW_500_00_KHZ; + } else { + return(RADIOLIB_ERR_INVALID_BANDWIDTH); + } + + // set bandwidth and if successful, save the new setting + int16_t state = SX1278::setBandwidthRaw(newBandwidth); + if(state == RADIOLIB_ERR_NONE) { + SX127x::bandwidth = bw; + + // calculate symbol length and set low data rate optimization, if auto-configuration is enabled + if(this->ldroAuto) { + float symbolLength = (float)(uint32_t(1) << SX127x::spreadingFactor) / (float)SX127x::bandwidth; + Module* mod = this->getMod(); + if(symbolLength >= 16.0) { + state = mod->SPIsetRegValue(RADIOLIB_SX1278_REG_MODEM_CONFIG_3, RADIOLIB_SX1278_LOW_DATA_RATE_OPT_ON, 3, 3); + } else { + state = mod->SPIsetRegValue(RADIOLIB_SX1278_REG_MODEM_CONFIG_3, RADIOLIB_SX1278_LOW_DATA_RATE_OPT_OFF, 3, 3); + } + } + } + return(state); +} + +int16_t SX1278::setSpreadingFactor(uint8_t sf) { + // check active modem + if(getActiveModem() != RADIOLIB_SX127X_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + uint8_t newSpreadingFactor; + + // check allowed spreading factor values + switch(sf) { + case 6: + newSpreadingFactor = RADIOLIB_SX127X_SF_6; + break; + case 7: + newSpreadingFactor = RADIOLIB_SX127X_SF_7; + break; + case 8: + newSpreadingFactor = RADIOLIB_SX127X_SF_8; + break; + case 9: + newSpreadingFactor = RADIOLIB_SX127X_SF_9; + break; + case 10: + newSpreadingFactor = RADIOLIB_SX127X_SF_10; + break; + case 11: + newSpreadingFactor = RADIOLIB_SX127X_SF_11; + break; + case 12: + newSpreadingFactor = RADIOLIB_SX127X_SF_12; + break; + default: + return(RADIOLIB_ERR_INVALID_SPREADING_FACTOR); + } + + // set spreading factor and if successful, save the new setting + int16_t state = SX1278::setSpreadingFactorRaw(newSpreadingFactor); + if(state == RADIOLIB_ERR_NONE) { + SX127x::spreadingFactor = sf; + + // calculate symbol length and set low data rate optimization, if auto-configuration is enabled + if(this->ldroAuto) { + float symbolLength = (float)(uint32_t(1) << SX127x::spreadingFactor) / (float)SX127x::bandwidth; + Module* mod = this->getMod(); + if(symbolLength >= 16.0) { + state = mod->SPIsetRegValue(RADIOLIB_SX1278_REG_MODEM_CONFIG_3, RADIOLIB_SX1278_LOW_DATA_RATE_OPT_ON, 3, 3); + } else { + state = mod->SPIsetRegValue(RADIOLIB_SX1278_REG_MODEM_CONFIG_3, RADIOLIB_SX1278_LOW_DATA_RATE_OPT_OFF, 3, 3); + } + } + } + return(state); +} + +int16_t SX1278::setCodingRate(uint8_t cr) { + // check active modem + if(getActiveModem() != RADIOLIB_SX127X_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + uint8_t newCodingRate; + + // check allowed coding rate values + switch(cr) { + case 5: + newCodingRate = RADIOLIB_SX1278_CR_4_5; + break; + case 6: + newCodingRate = RADIOLIB_SX1278_CR_4_6; + break; + case 7: + newCodingRate = RADIOLIB_SX1278_CR_4_7; + break; + case 8: + newCodingRate = RADIOLIB_SX1278_CR_4_8; + break; + default: + return(RADIOLIB_ERR_INVALID_CODING_RATE); + } + + // set coding rate and if successful, save the new setting + int16_t state = SX1278::setCodingRateRaw(newCodingRate); + if(state == RADIOLIB_ERR_NONE) { + SX127x::codingRate = cr; + } + return(state); +} + +int16_t SX1278::setBitRate(float br) { + return(SX127x::setBitRateCommon(br, RADIOLIB_SX1278_REG_BIT_RATE_FRAC)); +} + +int16_t SX1278::setDataRate(DataRate_t dr) { + int16_t state = RADIOLIB_ERR_UNKNOWN; + + // select interpretation based on active modem + uint8_t modem = this->getActiveModem(); + if(modem == RADIOLIB_SX127X_FSK_OOK) { + // set the bit rate + state = this->setBitRate(dr.fsk.bitRate); + RADIOLIB_ASSERT(state); + + // set the frequency deviation + state = this->setFrequencyDeviation(dr.fsk.freqDev); + + } else if(modem == RADIOLIB_SX127X_LORA) { + // set the spreading factor + state = this->setSpreadingFactor(dr.lora.spreadingFactor); + RADIOLIB_ASSERT(state); + + // set the bandwidth + state = this->setBandwidth(dr.lora.bandwidth); + RADIOLIB_ASSERT(state); + + // set the coding rate + state = this->setCodingRate(dr.lora.codingRate); + } + + return(state); +} + +int16_t SX1278::checkDataRate(DataRate_t dr) { + int16_t state = RADIOLIB_ERR_UNKNOWN; + + // select interpretation based on active modem + int16_t modem = getActiveModem(); + if(modem == RADIOLIB_SX127X_FSK_OOK) { + RADIOLIB_CHECK_RANGE(dr.fsk.bitRate, 0.5, 300.0, RADIOLIB_ERR_INVALID_BIT_RATE); + if(!((dr.fsk.freqDev + dr.fsk.bitRate/2.0 <= 250.0) && (dr.fsk.freqDev <= 200.0))) { + return(RADIOLIB_ERR_INVALID_FREQUENCY_DEVIATION); + } + return(RADIOLIB_ERR_NONE); + + } else if(modem == RADIOLIB_SX127X_LORA) { + RADIOLIB_CHECK_RANGE(dr.lora.spreadingFactor, 6, 12, RADIOLIB_ERR_INVALID_SPREADING_FACTOR); + RADIOLIB_CHECK_RANGE(dr.lora.bandwidth, 0.0, 510.0, RADIOLIB_ERR_INVALID_BANDWIDTH); + RADIOLIB_CHECK_RANGE(dr.lora.codingRate, 5, 8, RADIOLIB_ERR_INVALID_CODING_RATE); + return(RADIOLIB_ERR_NONE); + + } + + return(state); +} + +int16_t SX1278::setOutputPower(int8_t power) { + return(this->setOutputPower(power, false)); +} + +int16_t SX1278::setOutputPower(int8_t power, bool useRfo) { + // check if power value is configurable + int16_t state = checkOutputPower(power, NULL, useRfo); + RADIOLIB_ASSERT(state); + + // set mode to standby + state = SX127x::standby(); + Module* mod = this->getMod(); + + if(useRfo) { + uint8_t paCfg = 0; + if(power < 0) { + // low power mode RFO output + paCfg = RADIOLIB_SX1278_LOW_POWER | (power + 3); + } else { + // high power mode RFO output + paCfg = RADIOLIB_SX1278_MAX_POWER | power; + } + + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PA_CONFIG, RADIOLIB_SX127X_PA_SELECT_RFO, 7, 7); + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PA_CONFIG, paCfg, 6, 0); + state |= mod->SPIsetRegValue(RADIOLIB_SX1278_REG_PA_DAC, RADIOLIB_SX127X_PA_BOOST_OFF, 2, 0); + + } else { + if(power != 20) { + // power is 2 - 17 dBm, enable PA1 + PA2 on PA_BOOST + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PA_CONFIG, RADIOLIB_SX127X_PA_SELECT_BOOST, 7, 7); + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PA_CONFIG, RADIOLIB_SX1278_MAX_POWER | (power - 2), 6, 0); + state |= mod->SPIsetRegValue(RADIOLIB_SX1278_REG_PA_DAC, RADIOLIB_SX127X_PA_BOOST_OFF, 2, 0); + + } else { + // power is 20 dBm, enable PA1 + PA2 on PA_BOOST and enable high power control + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PA_CONFIG, RADIOLIB_SX127X_PA_SELECT_BOOST, 7, 7); + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PA_CONFIG, RADIOLIB_SX1278_MAX_POWER | 0x0F, 6, 0); + state |= mod->SPIsetRegValue(RADIOLIB_SX1278_REG_PA_DAC, RADIOLIB_SX127X_PA_BOOST_ON, 2, 0); + + } + } + + return(state); +} + +int16_t SX1278::checkOutputPower(int8_t power, int8_t* clipped) { + return(checkOutputPower(power, clipped, false)); +} + +int16_t SX1278::checkOutputPower(int8_t power, int8_t* clipped, bool useRfo) { + // check allowed power range + if(useRfo) { + // RFO output + if(clipped) { + *clipped = RADIOLIB_MAX(-3, RADIOLIB_MIN(15, power)); + } + RADIOLIB_CHECK_RANGE(power, -3, 15, RADIOLIB_ERR_INVALID_OUTPUT_POWER); + } else { + // PA_BOOST output, check high-power operation + if(clipped) { + if(power != 20) { + *clipped = RADIOLIB_MAX(2, RADIOLIB_MIN(17, power)); + } else { + *clipped = 20; + } + } + if(power != 20) { + RADIOLIB_CHECK_RANGE(power, 2, 17, RADIOLIB_ERR_INVALID_OUTPUT_POWER); + } + } + return(RADIOLIB_ERR_NONE); +} + +int16_t SX1278::setGain(uint8_t gain) { + // check allowed range + if(gain > 6) { + return(RADIOLIB_ERR_INVALID_GAIN); + } + + // set mode to standby + int16_t state = SX127x::standby(); + Module* mod = this->getMod(); + + // get modem + int16_t modem = getActiveModem(); + if(modem == RADIOLIB_SX127X_LORA){ + // set gain + if(gain == 0) { + // gain set to 0, enable AGC loop + state |= mod->SPIsetRegValue(RADIOLIB_SX1278_REG_MODEM_CONFIG_3, RADIOLIB_SX1278_AGC_AUTO_ON, 2, 2); + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_LNA, RADIOLIB_SX127X_LNA_BOOST_ON, 1, 0); + } else { + state |= mod->SPIsetRegValue(RADIOLIB_SX1278_REG_MODEM_CONFIG_3, RADIOLIB_SX1278_AGC_AUTO_OFF, 2, 2); + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_LNA, (gain << 5) | RADIOLIB_SX127X_LNA_BOOST_ON); + } + + } else if(modem == RADIOLIB_SX127X_FSK_OOK) { + // set gain + if(gain == 0) { + // gain set to 0, enable AGC loop + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_RX_CONFIG, RADIOLIB_SX127X_AGC_AUTO_ON, 3, 3); + } else { + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_RX_CONFIG, RADIOLIB_SX1278_AGC_AUTO_OFF, 3, 3); + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_LNA, (gain << 5) | RADIOLIB_SX127X_LNA_BOOST_ON); + } + + } + + return(state); +} + +int16_t SX1278::setDataShaping(uint8_t sh) { + // check active modem + if(getActiveModem() != RADIOLIB_SX127X_FSK_OOK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // check modulation + if(SX127x::ookEnabled) { + // we're in OOK mode, the only thing we can do is disable + if(sh == RADIOLIB_SHAPING_NONE) { + return(setDataShapingOOK(0)); + } + + return(RADIOLIB_ERR_INVALID_MODULATION); + } + + // set mode to standby + int16_t state = SX127x::standby(); + RADIOLIB_ASSERT(state); + + // set data shaping + Module* mod = this->getMod(); + switch(sh) { + case RADIOLIB_SHAPING_NONE: + return(mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PA_RAMP, RADIOLIB_SX1278_NO_SHAPING, 6, 5)); + case RADIOLIB_SHAPING_0_3: + return(mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PA_RAMP, RADIOLIB_SX1278_FSK_GAUSSIAN_0_3, 6, 5)); + case RADIOLIB_SHAPING_0_5: + return(mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PA_RAMP, RADIOLIB_SX1278_FSK_GAUSSIAN_0_5, 6, 5)); + case RADIOLIB_SHAPING_1_0: + return(mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PA_RAMP, RADIOLIB_SX1278_FSK_GAUSSIAN_1_0, 6, 5)); + default: + return(RADIOLIB_ERR_INVALID_DATA_SHAPING); + } +} + +int16_t SX1278::setDataShapingOOK(uint8_t sh) { + // check active modem + if(getActiveModem() != RADIOLIB_SX127X_FSK_OOK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // check modulation + if(!SX127x::ookEnabled) { + return(RADIOLIB_ERR_INVALID_MODULATION); + } + + // set mode to standby + int16_t state = SX127x::standby(); + + // set data shaping + Module* mod = this->getMod(); + switch(sh) { + case 0: + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PA_RAMP, RADIOLIB_SX1278_NO_SHAPING, 6, 5); + break; + case 1: + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PA_RAMP, RADIOLIB_SX1278_OOK_FILTER_BR, 6, 5); + break; + case 2: + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PA_RAMP, RADIOLIB_SX1278_OOK_FILTER_2BR, 6, 5); + break; + default: + return(RADIOLIB_ERR_INVALID_DATA_SHAPING); + } + + return(state); +} + +float SX1278::getRSSI() { + return(SX1278::getRSSI(true, false)); +} + +float SX1278::getRSSI(bool packet, bool skipReceive) { + int16_t offset = -157; + if(frequency < 868.0) { + offset = -164; + } + return(SX127x::getRSSI(packet, skipReceive, offset)); +} + +int16_t SX1278::setCRC(bool enable, bool mode) { + Module* mod = this->getMod(); + if(getActiveModem() == RADIOLIB_SX127X_LORA) { + // set LoRa CRC + SX127x::crcEnabled = enable; + if(enable) { + return(mod->SPIsetRegValue(RADIOLIB_SX127X_REG_MODEM_CONFIG_2, RADIOLIB_SX1278_RX_CRC_MODE_ON, 2, 2)); + } else { + return(mod->SPIsetRegValue(RADIOLIB_SX127X_REG_MODEM_CONFIG_2, RADIOLIB_SX1278_RX_CRC_MODE_OFF, 2, 2)); + } + } else { + // set FSK CRC + int16_t state = RADIOLIB_ERR_NONE; + if(enable) { + state = mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PACKET_CONFIG_1, RADIOLIB_SX127X_CRC_ON, 4, 4); + } else { + state = mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PACKET_CONFIG_1, RADIOLIB_SX127X_CRC_OFF, 4, 4); + } + RADIOLIB_ASSERT(state); + + // set FSK CRC mode + if(mode) { + return(mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PACKET_CONFIG_1, RADIOLIB_SX127X_CRC_WHITENING_TYPE_IBM, 0, 0)); + } else { + return(mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PACKET_CONFIG_1, RADIOLIB_SX127X_CRC_WHITENING_TYPE_CCITT, 0, 0)); + } + } +} + +int16_t SX1278::forceLDRO(bool enable) { + if(getActiveModem() != RADIOLIB_SX127X_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + Module* mod = this->getMod(); + this->ldroAuto = false; + if(enable) { + return(mod->SPIsetRegValue(RADIOLIB_SX1278_REG_MODEM_CONFIG_3, RADIOLIB_SX1278_LOW_DATA_RATE_OPT_ON, 3, 3)); + } else { + return(mod->SPIsetRegValue(RADIOLIB_SX1278_REG_MODEM_CONFIG_3, RADIOLIB_SX1278_LOW_DATA_RATE_OPT_OFF, 3, 3)); + } +} + +int16_t SX1278::autoLDRO() { + if(getActiveModem() != RADIOLIB_SX127X_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + this->ldroAuto = true; + return(RADIOLIB_ERR_NONE); +} + +int16_t SX1278::implicitHeader(size_t len) { + return(setHeaderType(RADIOLIB_SX1278_HEADER_IMPL_MODE, len)); +} + +int16_t SX1278::explicitHeader() { + return(setHeaderType(RADIOLIB_SX1278_HEADER_EXPL_MODE)); +} + +int16_t SX1278::setBandwidthRaw(uint8_t newBandwidth) { + // set mode to standby + int16_t state = SX127x::standby(); + + // write register + Module* mod = this->getMod(); + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_MODEM_CONFIG_1, newBandwidth, 7, 4); + return(state); +} + +int16_t SX1278::setSpreadingFactorRaw(uint8_t newSpreadingFactor) { + // set mode to standby + int16_t state = SX127x::standby(); + + // write registers + Module* mod = this->getMod(); + if(newSpreadingFactor == RADIOLIB_SX127X_SF_6) { + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_MODEM_CONFIG_1, RADIOLIB_SX1278_HEADER_IMPL_MODE, 0, 0); + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_MODEM_CONFIG_2, RADIOLIB_SX127X_SF_6 | RADIOLIB_SX127X_TX_MODE_SINGLE, 7, 3); + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_DETECT_OPTIMIZE, RADIOLIB_SX127X_DETECT_OPTIMIZE_SF_6, 2, 0); + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_DETECTION_THRESHOLD, RADIOLIB_SX127X_DETECTION_THRESHOLD_SF_6); + } else { + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_MODEM_CONFIG_1, RADIOLIB_SX1278_HEADER_EXPL_MODE, 0, 0); + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_MODEM_CONFIG_2, newSpreadingFactor | RADIOLIB_SX127X_TX_MODE_SINGLE, 7, 3); + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_DETECT_OPTIMIZE, RADIOLIB_SX127X_DETECT_OPTIMIZE_SF_7_12, 2, 0); + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_DETECTION_THRESHOLD, RADIOLIB_SX127X_DETECTION_THRESHOLD_SF_7_12); + } + return(state); +} + +int16_t SX1278::setCodingRateRaw(uint8_t newCodingRate) { + // set mode to standby + int16_t state = SX127x::standby(); + + // write register + Module* mod = this->getMod(); + state |= mod->SPIsetRegValue(RADIOLIB_SX127X_REG_MODEM_CONFIG_1, newCodingRate, 3, 1); + return(state); +} + +int16_t SX1278::setHeaderType(uint8_t headerType, size_t len) { + // check active modem + if(getActiveModem() != RADIOLIB_SX127X_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // set requested packet mode + Module* mod = this->getMod(); + int16_t state = mod->SPIsetRegValue(RADIOLIB_SX127X_REG_MODEM_CONFIG_1, headerType, 0, 0); + RADIOLIB_ASSERT(state); + + // set length to register + state = mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PAYLOAD_LENGTH, len); + RADIOLIB_ASSERT(state); + + // update cached value + SX127x::packetLength = len; + + return(state); +} + +int16_t SX1278::configFSK() { + // configure common registers + int16_t state = SX127x::configFSK(); + RADIOLIB_ASSERT(state); + + // set fast PLL hop + Module* mod = this->getMod(); + state = mod->SPIsetRegValue(RADIOLIB_SX1278_REG_PLL_HOP, RADIOLIB_SX127X_FAST_HOP_ON, 7, 7); + return(state); +} + +void SX1278::errataFix(bool rx) { + // only apply in LoRa mode + if(getActiveModem() != RADIOLIB_SX127X_LORA) { + return; + } + + // sensitivity optimization for 500kHz bandwidth + // see SX1276/77/78 Errata, section 2.1 for details + Module* mod = this->getMod(); + if(fabsf(SX127x::bandwidth - 500.0) <= 0.001) { + if((frequency >= 862.0) && (frequency <= 1020.0)) { + mod->SPIwriteRegister(0x36, 0x02); + mod->SPIwriteRegister(0x3a, 0x64); + } else if((frequency >= 410.0) && (frequency <= 525.0)) { + mod->SPIwriteRegister(0x36, 0x02); + mod->SPIwriteRegister(0x3a, 0x7F); + } + } + + // mitigation of receiver spurious response + // see SX1276/77/78 Errata, section 2.3 for details + + // figure out what we need to set + uint8_t fixedRegs[3] = { 0x00, 0x00, 0x00 }; + float rxFreq = frequency; + if(fabsf(SX127x::bandwidth - 7.8) <= 0.001) { + fixedRegs[0] = 0b00000000; + fixedRegs[1] = 0x48; + fixedRegs[2] = 0x00; + rxFreq += 0.00781; + } else if(fabsf(SX127x::bandwidth - 10.4) <= 0.001) { + fixedRegs[0] = 0b00000000; + fixedRegs[1] = 0x44; + fixedRegs[2] = 0x00; + rxFreq += 0.01042; + } else if(fabsf(SX127x::bandwidth - 15.6) <= 0.001) { + fixedRegs[0] = 0b00000000; + fixedRegs[1] = 0x44; + fixedRegs[2] = 0x00; + rxFreq += 0.01562; + } else if(fabsf(SX127x::bandwidth - 20.8) <= 0.001) { + fixedRegs[0] = 0b00000000; + fixedRegs[1] = 0x44; + fixedRegs[2] = 0x00; + rxFreq += 0.02083; + } else if(fabsf(SX127x::bandwidth - 31.25) <= 0.001) { + fixedRegs[0] = 0b00000000; + fixedRegs[1] = 0x44; + fixedRegs[2] = 0x00; + rxFreq += 0.03125; + } else if(fabsf(SX127x::bandwidth - 41.7) <= 0.001) { + fixedRegs[0] = 0b00000000; + fixedRegs[1] = 0x44; + fixedRegs[2] = 0x00; + rxFreq += 0.04167; + } else if(fabsf(SX127x::bandwidth - 62.5) <= 0.001) { + fixedRegs[0] = 0b00000000; + fixedRegs[1] = 0x40; + fixedRegs[2] = 0x00; + } else if(fabsf(SX127x::bandwidth - 125.0) <= 0.001) { + fixedRegs[0] = 0b00000000; + fixedRegs[1] = 0x40; + fixedRegs[2] = 0x00; + } else if(fabsf(SX127x::bandwidth - 250.0) <= 0.001) { + fixedRegs[0] = 0b00000000; + fixedRegs[1] = 0x40; + fixedRegs[2] = 0x00; + } else if(fabsf(SX127x::bandwidth - 500.0) <= 0.001) { + fixedRegs[0] = 0b10000000; + fixedRegs[1] = mod->SPIreadRegister(0x2F); + fixedRegs[2] = mod->SPIreadRegister(0x30); + } else { + return; + } + + // first, go to standby + standby(); + + // shift the freqency up when receiving, or restore the original when transmitting + if(rx) { + SX127x::setFrequencyRaw(rxFreq); + } else { + SX127x::setFrequencyRaw(frequency); + } + + // finally, apply errata fixes + mod->SPIsetRegValue(0x31, fixedRegs[0], 7, 7); + mod->SPIsetRegValue(0x2F, fixedRegs[1]); + mod->SPIsetRegValue(0x30, fixedRegs[2]); +} + +int16_t SX1278::setModem(ModemType_t modem) { + switch(modem) { + case(ModemType_t::RADIOLIB_MODEM_LORA): { + return(this->begin()); + } break; + case(ModemType_t::RADIOLIB_MODEM_FSK): { + return(this->beginFSK()); + } break; + default: + return(RADIOLIB_ERR_WRONG_MODEM); + } +} + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1278.h b/software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1278.h new file mode 100644 index 000000000..feb1d09cb --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1278.h @@ -0,0 +1,352 @@ +#if !defined(_RADIOLIB_SX1278_H) +#define _RADIOLIB_SX1278_H + +#include "../../TypeDef.h" + +#if !RADIOLIB_EXCLUDE_SX127X + +#include "../../Module.h" +#include "SX127x.h" + +// SX1278 specific register map +#define RADIOLIB_SX1278_REG_MODEM_CONFIG_3 0x26 +#define RADIOLIB_SX1278_REG_PLL_HOP 0x44 +#define RADIOLIB_SX1278_REG_TCXO 0x4B +#define RADIOLIB_SX1278_REG_PA_DAC 0x4D +#define RADIOLIB_SX1278_REG_FORMER_TEMP 0x5B +#define RADIOLIB_SX1278_REG_BIT_RATE_FRAC 0x5D +#define RADIOLIB_SX1278_REG_AGC_REF 0x61 +#define RADIOLIB_SX1278_REG_AGC_THRESH_1 0x62 +#define RADIOLIB_SX1278_REG_AGC_THRESH_2 0x63 +#define RADIOLIB_SX1278_REG_AGC_THRESH_3 0x64 +#define RADIOLIB_SX1278_REG_PLL 0x70 + +// SX1278 LoRa modem settings +// RADIOLIB_SX1278_REG_OP_MODE MSB LSB DESCRIPTION +#define RADIOLIB_SX1278_HIGH_FREQ 0b00000000 // 3 3 access HF test registers +#define RADIOLIB_SX1278_LOW_FREQ 0b00001000 // 3 3 access LF test registers + +// RADIOLIB_SX1278_REG_FRF_MSB + REG_FRF_MID + REG_FRF_LSB +#define RADIOLIB_SX1278_FRF_MSB 0x6C // 7 0 carrier frequency setting: f_RF = (F(XOSC) * FRF)/2^19 +#define RADIOLIB_SX1278_FRF_MID 0x80 // 7 0 where F(XOSC) = 32 MHz +#define RADIOLIB_SX1278_FRF_LSB 0x00 // 7 0 FRF = 3 byte value of FRF registers + +// RADIOLIB_SX1278_REG_PA_CONFIG +#define RADIOLIB_SX1278_MAX_POWER 0b01110000 // 6 4 max power: P_max = 10.8 + 0.6*MAX_POWER [dBm]; P_max(MAX_POWER = 0b111) = 15 dBm +#define RADIOLIB_SX1278_LOW_POWER 0b00100000 // 6 4 + +// RADIOLIB_SX1278_REG_LNA +#define RADIOLIB_SX1278_LNA_BOOST_LF_OFF 0b00000000 // 4 3 default LNA current + +// SX127X_REG_MODEM_CONFIG_1 +#define RADIOLIB_SX1278_BW_7_80_KHZ 0b00000000 // 7 4 bandwidth: 7.80 kHz +#define RADIOLIB_SX1278_BW_10_40_KHZ 0b00010000 // 7 4 10.40 kHz +#define RADIOLIB_SX1278_BW_15_60_KHZ 0b00100000 // 7 4 15.60 kHz +#define RADIOLIB_SX1278_BW_20_80_KHZ 0b00110000 // 7 4 20.80 kHz +#define RADIOLIB_SX1278_BW_31_25_KHZ 0b01000000 // 7 4 31.25 kHz +#define RADIOLIB_SX1278_BW_41_70_KHZ 0b01010000 // 7 4 41.70 kHz +#define RADIOLIB_SX1278_BW_62_50_KHZ 0b01100000 // 7 4 62.50 kHz +#define RADIOLIB_SX1278_BW_125_00_KHZ 0b01110000 // 7 4 125.00 kHz +#define RADIOLIB_SX1278_BW_250_00_KHZ 0b10000000 // 7 4 250.00 kHz +#define RADIOLIB_SX1278_BW_500_00_KHZ 0b10010000 // 7 4 500.00 kHz +#define RADIOLIB_SX1278_CR_4_5 0b00000010 // 3 1 error coding rate: 4/5 +#define RADIOLIB_SX1278_CR_4_6 0b00000100 // 3 1 4/6 +#define RADIOLIB_SX1278_CR_4_7 0b00000110 // 3 1 4/7 +#define RADIOLIB_SX1278_CR_4_8 0b00001000 // 3 1 4/8 +#define RADIOLIB_SX1278_HEADER_EXPL_MODE 0b00000000 // 0 0 explicit header mode +#define RADIOLIB_SX1278_HEADER_IMPL_MODE 0b00000001 // 0 0 implicit header mode + +// SX127X_REG_MODEM_CONFIG_2 +#define RADIOLIB_SX1278_RX_CRC_MODE_OFF 0b00000000 // 2 2 CRC disabled +#define RADIOLIB_SX1278_RX_CRC_MODE_ON 0b00000100 // 2 2 CRC enabled + +// RADIOLIB_SX1278_REG_MODEM_CONFIG_3 +#define RADIOLIB_SX1278_LOW_DATA_RATE_OPT_OFF 0b00000000 // 3 3 low data rate optimization disabled +#define RADIOLIB_SX1278_LOW_DATA_RATE_OPT_ON 0b00001000 // 3 3 low data rate optimization enabled +#define RADIOLIB_SX1278_AGC_AUTO_OFF 0b00000000 // 2 2 LNA gain set by REG_LNA +#define RADIOLIB_SX1278_AGC_AUTO_ON 0b00000100 // 2 2 LNA gain set by internal AGC loop + +// SX127X_REG_VERSION +#define RADIOLIB_SX1278_CHIP_VERSION 0x12 // this is the "official" version listed in datasheet +#define RADIOLIB_SX1278_CHIP_VERSION_ALT 0x13 // appears sometimes +#define RADIOLIB_SX1278_CHIP_VERSION_RFM9X 0x11 // this value is used for the RFM9x + +// SX1278 FSK modem settings +// SX127X_REG_PA_RAMP +#define RADIOLIB_SX1278_NO_SHAPING 0b00000000 // 6 5 data shaping: no shaping (default) +#define RADIOLIB_SX1278_FSK_GAUSSIAN_1_0 0b00100000 // 6 5 FSK modulation Gaussian filter, BT = 1.0 +#define RADIOLIB_SX1278_FSK_GAUSSIAN_0_5 0b01000000 // 6 5 FSK modulation Gaussian filter, BT = 0.5 +#define RADIOLIB_SX1278_FSK_GAUSSIAN_0_3 0b01100000 // 6 5 FSK modulation Gaussian filter, BT = 0.3 +#define RADIOLIB_SX1278_OOK_FILTER_BR 0b00100000 // 6 5 OOK modulation filter, f_cutoff = BR +#define RADIOLIB_SX1278_OOK_FILTER_2BR 0b01000000 // 6 5 OOK modulation filter, f_cutoff = 2*BR + +// RADIOLIB_SX1278_REG_AGC_REF +#define RADIOLIB_SX1278_AGC_REFERENCE_LEVEL_LF 0x19 // 5 0 floor reference for AGC thresholds: AgcRef = -174 + 10*log(2*RxBw) + 8 + AGC_REFERENCE_LEVEL [dBm]: below 525 MHz +#define RADIOLIB_SX1278_AGC_REFERENCE_LEVEL_HF 0x1C // 5 0 above 779 MHz + +// RADIOLIB_SX1278_REG_AGC_THRESH_1 +#define RADIOLIB_SX1278_AGC_STEP_1_LF 0x0C // 4 0 1st AGC threshold: below 525 MHz +#define RADIOLIB_SX1278_AGC_STEP_1_HF 0x0E // 4 0 above 779 MHz + +// RADIOLIB_SX1278_REG_AGC_THRESH_2 +#define RADIOLIB_SX1278_AGC_STEP_2_LF 0x40 // 7 4 2nd AGC threshold: below 525 MHz +#define RADIOLIB_SX1278_AGC_STEP_2_HF 0x50 // 7 4 above 779 MHz +#define RADIOLIB_SX1278_AGC_STEP_3 0x0B // 3 0 3rd AGC threshold + +// RADIOLIB_SX1278_REG_AGC_THRESH_3 +#define RADIOLIB_SX1278_AGC_STEP_4 0xC0 // 7 4 4th AGC threshold +#define RADIOLIB_SX1278_AGC_STEP_5 0x0C // 4 0 5th AGC threshold + +/*! + \class SX1278 + \brief Derived class for %SX1278 modules. Also used as base class for SX1276, SX1277, SX1279, RFM95 and RFM96. + All of these modules use the same basic hardware and only differ in parameter ranges (and names). +*/ +class SX1278: public SX127x { + public: + + // constructor + + /*! + \brief Default constructor. Called from Arduino sketch when creating new LoRa instance. + \param mod Instance of Module that will be used to communicate with the %LoRa chip. + */ + SX1278(Module* mod); // cppcheck-suppress noExplicitConstructor + + // basic methods + + /*! + \brief %LoRa modem initialization method. Must be called at least once from Arduino sketch to initialize the module. + \param freq Carrier frequency in MHz. Allowed values range from 137.0 MHz to 525.0 MHz. + \param bw %LoRa link bandwidth in kHz. Allowed values are 10.4, 15.6, 20.8, 31.25, 41.7, 62.5, 125, 250 and 500 kHz. + \param sf %LoRa link spreading factor. Allowed values range from 6 to 12. + \param cr %LoRa link coding rate denominator. Allowed values range from 5 to 8. + \param syncWord %LoRa sync word. Can be used to distinguish different networks. Note that value 0x34 is reserved for LoRaWAN networks. + \param power Transmission output power in dBm. Allowed values range from 2 to 17 dBm. + \param preambleLength Length of %LoRa transmission preamble in symbols. The actual preamble length is 4.25 symbols longer than the set number. + Allowed values range from 6 to 65535. + \param gain Gain of receiver LNA (low-noise amplifier). Can be set to any integer in range 1 to 6 where 1 is the highest gain. + Set to 0 to enable automatic gain control (recommended). + \returns \ref status_codes + */ + int16_t begin(float freq = 434.0, float bw = 125.0, uint8_t sf = 9, uint8_t cr = 7, uint8_t syncWord = RADIOLIB_SX127X_SYNC_WORD, int8_t power = 10, uint16_t preambleLength = 8, uint8_t gain = 0); + + /*! + \brief FSK modem initialization method. Must be called at least once from Arduino sketch to initialize the module. + \param freq Carrier frequency in MHz. Allowed values range from 137.0 MHz to 525.0 MHz. + \param br Bit rate of the FSK transmission in kbps (kilobits per second). Allowed values range from 1.2 to 300.0 kbps. + \param freqDev Frequency deviation of the FSK transmission in kHz. Allowed values range from 0.6 to 200.0 kHz. + Note that the allowed range changes based on bit rate setting, so that the condition FreqDev + BitRate/2 <= 250 kHz is always met. + \param rxBw Receiver bandwidth in kHz. Allowed values are 2.6, 3.1, 3.9, 5.2, 6.3, 7.8, 10.4, 12.5, 15.6, 20.8, 25, 31.3, 41.7, 50, 62.5, 83.3, 100, 125, 166.7, 200 and 250 kHz. + \param power Transmission output power in dBm. Allowed values range from 2 to 17 dBm. + \param preambleLength Length of FSK preamble in bits. + \param enableOOK Use OOK modulation instead of FSK. + \returns \ref status_codes + */ + int16_t beginFSK(float freq = 434.0, float br = 4.8, float freqDev = 5.0, float rxBw = 125.0, int8_t power = 10, uint16_t preambleLength = 16, bool enableOOK = false); + + /*! + \brief Reset method. Will reset the chip to the default state using RST pin. + */ + void reset() override; + + // configuration methods + + /*! + \brief Sets carrier frequency. Allowed values range from 137.0 MHz to 525.0 MHz. + \param freq Carrier frequency to be set in MHz. + \returns \ref status_codes + */ + int16_t setFrequency(float freq) override; + + /*! + \brief Sets %LoRa link bandwidth. Allowed values are 10.4, 15.6, 20.8, 31.25, 41.7, 62.5, 125, 250 and 500 kHz. Only available in %LoRa mode. + \param bw %LoRa link bandwidth to be set in kHz. + \returns \ref status_codes + */ + int16_t setBandwidth(float bw); + + /*! + \brief Sets %LoRa link spreading factor. Allowed values range from 6 to 12. Only available in %LoRa mode. + \param sf %LoRa link spreading factor to be set. + \returns \ref status_codes + */ + int16_t setSpreadingFactor(uint8_t sf); + + /*! + \brief Sets %LoRa link coding rate denominator. Allowed values range from 5 to 8. Only available in %LoRa mode. + \param cr %LoRa link coding rate denominator to be set. + \returns \ref status_codes + */ + int16_t setCodingRate(uint8_t cr); + + /*! + \brief Sets FSK bit rate. Allowed values range from 0.5 to 300 kbps. Only available in FSK mode. + \param br Bit rate to be set (in kbps). + \returns \ref status_codes + */ + int16_t setBitRate(float br) override; + + /*! + \brief Set data. + \param dr Data rate struct. Interpretation depends on currently active modem (FSK or LoRa). + \returns \ref status_codes + */ + int16_t setDataRate(DataRate_t dr) override; + + /*! + \brief Check the data rate can be configured by this module. + \param dr Data rate struct. Interpretation depends on currently active modem (FSK or LoRa). + \returns \ref status_codes + */ + int16_t checkDataRate(DataRate_t dr) override; + + /*! + \brief Sets transmission output power. Allowed values range from -3 to 15 dBm (RFO pin) or +2 to +17 dBm (PA_BOOST pin). + High power +20 dBm operation is also supported, on the PA_BOOST pin. Defaults to PA_BOOST. + \param power Transmission output power in dBm. + \returns \ref status_codes + */ + int16_t setOutputPower(int8_t power) override; + + /*! + \brief Sets transmission output power. Allowed values range from -3 to 15 dBm (RFO pin) or +2 to +17 dBm (PA_BOOST pin). + High power +20 dBm operation is also supported, on the PA_BOOST pin. + \param power Transmission output power in dBm. + \param useRfo Whether to use the RFO (true) or the PA_BOOST (false) pin for the RF output. + \returns \ref status_codes + */ + int16_t setOutputPower(int8_t power, bool useRfo); + + /*! + \brief Check if output power is configurable. + This method is needed for compatibility with PhysicalLayer::checkOutputPower. + \param power Output power in dBm, assumes PA_BOOST pin. + \param clipped Clipped output power value to what is possible within the module's range. + \returns \ref status_codes + */ + int16_t checkOutputPower(int8_t power, int8_t* clipped) override; + + /*! + \brief Check if output power is configurable. + \param power Output power in dBm. + \param clipped Clipped output power value to what is possible within the module's range. + \param useRfo Whether to use the RFO (true) or the PA_BOOST (false) pin for the RF output. + \returns \ref status_codes + */ + int16_t checkOutputPower(int8_t power, int8_t* clipped, bool useRfo); + + /*! + \brief Sets gain of receiver LNA (low-noise amplifier). Can be set to any integer in range 1 to 6 where 1 is the highest gain. + Set to 0 to enable automatic gain control (recommended). + \param gain Gain of receiver LNA (low-noise amplifier) to be set. + \returns \ref status_codes + */ + int16_t setGain(uint8_t gain); + + /*! + \brief Sets Gaussian filter bandwidth-time product that will be used for data shaping. Only available in FSK mode with FSK modulation. + Allowed values are RADIOLIB_SHAPING_0_3, RADIOLIB_SHAPING_0_5 or RADIOLIB_SHAPING_1_0. Set to RADIOLIB_SHAPING_NONE to disable data shaping. + \param sh Gaussian shaping bandwidth-time product that will be used for data shaping + \returns \ref status_codes + */ + int16_t setDataShaping(uint8_t sh) override; + + /*! + \brief Sets filter cutoff frequency that will be used for data shaping. + Allowed values are 1 for frequency equal to bit rate and 2 for frequency equal to 2x bit rate. Set to 0 to disable data shaping. + Only available in FSK mode with OOK modulation. + \param sh Cutoff frequency that will be used for data shaping + \returns \ref status_codes + */ + int16_t setDataShapingOOK(uint8_t sh); + + /*! + \brief Gets recorded signal strength indicator. + Overload with packet mode enabled for PhysicalLayer compatibility. + \returns RSSI value in dBm. + */ + float getRSSI() override; + + /*! + \brief Gets recorded signal strength indicator. + \param packet Whether to read last packet RSSI, or the current value. LoRa mode only, ignored for FSK. + \param skipReceive Set to true to skip putting radio in receive mode for the RSSI measurement in FSK/OOK mode. + \returns RSSI value in dBm. + */ + float getRSSI(bool packet, bool skipReceive = false); + + /*! + \brief Enables/disables CRC check of received packets. + \param enable Enable (true) or disable (false) CRC. + \param mode Set CRC mode to SX127X_CRC_WHITENING_TYPE_CCITT for CCITT, polynomial X16 + X12 + X5 + 1 (false) + or SX127X_CRC_WHITENING_TYPE_IBM for IBM, polynomial X16 + X15 + X2 + 1 (true). Only valid in FSK mode. + \returns \ref status_codes + */ + int16_t setCRC(bool enable, bool mode = false); + + /*! + \brief Forces LoRa low data rate optimization. Only available in LoRa mode. After calling this method, + LDRO will always be set to the provided value, regardless of symbol length. + To re-enable automatic LDRO configuration, call SX1278::autoLDRO() + \param enable Force LDRO to be always enabled (true) or disabled (false). + \returns \ref status_codes + */ + int16_t forceLDRO(bool enable); + + /*! + \brief Re-enables automatic LDRO configuration. Only available in LoRa mode. After calling this method, + LDRO will be enabled automatically when symbol length exceeds 16 ms. + \returns \ref status_codes + */ + int16_t autoLDRO(); + + /*! + \brief Set implicit header mode for future reception/transmission. Required for spreading factor 6. + \param len Payload length in bytes. + \returns \ref status_codes + */ + int16_t implicitHeader(size_t len); + + /*! + \brief Set explicit header mode for future reception/transmission. + \returns \ref status_codes + */ + int16_t explicitHeader(); + + /*! + \brief Set modem for the radio to use. Will perform full reset and reconfigure the radio + using its default parameters. + \param modem Modem type to set - FSK or LoRa. + \returns \ref status_codes + */ + int16_t setModem(ModemType_t modem) override; + +#if !RADIOLIB_GODMODE + protected: +#endif + int16_t setBandwidthRaw(uint8_t newBandwidth); + int16_t setSpreadingFactorRaw(uint8_t newSpreadingFactor); + int16_t setCodingRateRaw(uint8_t newCodingRate); + int16_t setHeaderType(uint8_t headerType, size_t len = 0xFF); + + int16_t configFSK(); + void errataFix(bool rx) override; + +#if !RADIOLIB_GODMODE + private: +#endif + bool ldroAuto = true; + bool ldroEnabled = false; + +}; + +/*! + \class RFM98 + \brief Only exists as alias for SX1278, since there seems to be no difference between %RFM98 and %SX1278 modules. +*/ +RADIOLIB_TYPE_ALIAS(SX1278, RFM98) + +#endif + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1279.cpp b/software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1279.cpp new file mode 100644 index 000000000..a91273fd0 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1279.cpp @@ -0,0 +1,95 @@ +#include "SX1279.h" +#if !RADIOLIB_EXCLUDE_SX127X + +SX1279::SX1279(Module* mod) : SX1278(mod) { + +} + +int16_t SX1279::begin(float freq, float bw, uint8_t sf, uint8_t cr, uint8_t syncWord, int8_t power, uint16_t preambleLength, uint8_t gain) { + // execute common part + uint8_t versions[] = { RADIOLIB_SX1278_CHIP_VERSION, RADIOLIB_SX1278_CHIP_VERSION_ALT, RADIOLIB_SX1278_CHIP_VERSION_RFM9X }; + int16_t state = SX127x::begin(versions, 3, syncWord, preambleLength); + RADIOLIB_ASSERT(state); + + // configure publicly accessible settings + state = setBandwidth(bw); + RADIOLIB_ASSERT(state); + + state = setFrequency(freq); + RADIOLIB_ASSERT(state); + + state = setSpreadingFactor(sf); + RADIOLIB_ASSERT(state); + + state = setCodingRate(cr); + RADIOLIB_ASSERT(state); + + state = setOutputPower(power); + RADIOLIB_ASSERT(state); + + state = setGain(gain); + RADIOLIB_ASSERT(state); + + // set publicly accessible settings that are not a part of begin method + state = setCRC(true); + RADIOLIB_ASSERT(state); + + return(state); +} + +int16_t SX1279::beginFSK(float freq, float br, float freqDev, float rxBw, int8_t power, uint16_t preambleLength, bool enableOOK) { + // execute common part + uint8_t versions[] = { RADIOLIB_SX1278_CHIP_VERSION, RADIOLIB_SX1278_CHIP_VERSION_ALT, RADIOLIB_SX1278_CHIP_VERSION_RFM9X }; + int16_t state = SX127x::beginFSK(versions, 3, freqDev, rxBw, preambleLength, enableOOK); + RADIOLIB_ASSERT(state); + + // configure settings not accessible by API + state = configFSK(); + RADIOLIB_ASSERT(state); + + // configure publicly accessible settings + state = setFrequency(freq); + RADIOLIB_ASSERT(state); + + state = setBitRate(br); + RADIOLIB_ASSERT(state); + + state = setOutputPower(power); + RADIOLIB_ASSERT(state); + + if(enableOOK) { + state = setDataShapingOOK(RADIOLIB_SHAPING_NONE); + RADIOLIB_ASSERT(state); + } else { + state = setDataShaping(RADIOLIB_SHAPING_NONE); + RADIOLIB_ASSERT(state); + } + + return(state); +} + +int16_t SX1279::setFrequency(float freq) { + RADIOLIB_CHECK_RANGE(freq, 137.0, 960.0, RADIOLIB_ERR_INVALID_FREQUENCY); + + // set frequency and if successful, save the new setting + int16_t state = SX127x::setFrequencyRaw(freq); + if(state == RADIOLIB_ERR_NONE) { + SX127x::frequency = freq; + } + return(state); +} + +int16_t SX1279::setModem(ModemType_t modem) { + switch(modem) { + case(ModemType_t::RADIOLIB_MODEM_LORA): { + return(this->begin()); + } break; + case(ModemType_t::RADIOLIB_MODEM_FSK): { + return(this->beginFSK()); + } break; + default: + return(RADIOLIB_ERR_WRONG_MODEM); + } +} + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1279.h b/software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1279.h new file mode 100644 index 000000000..865b1b22c --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX1279.h @@ -0,0 +1,82 @@ +#if !defined(_RADIOLIB_SX1279_H) +#define _RADIOLIB_SX1279_H + +#include "../../TypeDef.h" + +#if !RADIOLIB_EXCLUDE_SX127X + +#include "SX1278.h" + +/*! + \class SX1279 + \brief Derived class for %SX1279 modules. Overrides some methods from SX1278 due to different parameter ranges. +*/ +class SX1279: public SX1278 { + public: + + // constructor + + /*! + \brief Default constructor. Called from Arduino sketch when creating new LoRa instance. + \param mod Instance of Module that will be used to communicate with the %LoRa chip. + */ + SX1279(Module* mod); // cppcheck-suppress noExplicitConstructor + + // basic methods + + /*! + \brief %LoRa modem initialization method. Must be called at least once from Arduino sketch to initialize the module. + \param freq Carrier frequency in MHz. Allowed values range from 137.0 MHz to 960.0 MHz. + \param bw %LoRa link bandwidth in kHz. Allowed values are 10.4, 15.6, 20.8, 31.25, 41.7, 62.5, 125, 250 and 500 kHz. + \param sf %LoRa link spreading factor. Allowed values range from 6 to 12. + \param cr %LoRa link coding rate denominator. Allowed values range from 5 to 8. + \param syncWord %LoRa sync word. Can be used to distinguish different networks. Note that value 0x34 is reserved for LoRaWAN networks. + \param power Transmission output power in dBm. Allowed values range from 2 to 17 dBm. + \param preambleLength Length of %LoRa transmission preamble in symbols. The actual preamble length is 4.25 symbols longer than the set number. + Allowed values range from 6 to 65535. + \param gain Gain of receiver LNA (low-noise amplifier). Can be set to any integer in range 1 to 6 where 1 is the highest gain. + Set to 0 to enable automatic gain control (recommended). + \returns \ref status_codes + */ + int16_t begin(float freq = 434.0, float bw = 125.0, uint8_t sf = 9, uint8_t cr = 7, uint8_t syncWord = RADIOLIB_SX127X_SYNC_WORD, int8_t power = 10, uint16_t preambleLength = 8, uint8_t gain = 0); + + /*! + \brief FSK modem initialization method. Must be called at least once from Arduino sketch to initialize the module. + \param freq Carrier frequency in MHz. Allowed values range from 137.0 MHz to 525.0 MHz. + \param br Bit rate of the FSK transmission in kbps (kilobits per second). Allowed values range from 1.2 to 300.0 kbps. + \param freqDev Frequency deviation of the FSK transmission in kHz. Allowed values range from 0.6 to 200.0 kHz. + Note that the allowed range changes based on bit rate setting, so that the condition FreqDev + BitRate/2 <= 250 kHz is always met. + \param rxBw Receiver bandwidth in kHz. Allowed values are 2.6, 3.1, 3.9, 5.2, 6.3, 7.8, 10.4, 12.5, 15.6, 20.8, 25, 31.3, 41.7, 50, 62.5, 83.3, 100, 125, 166.7, 200 and 250 kHz. + \param power Transmission output power in dBm. Allowed values range from 2 to 17 dBm. + \param preambleLength Length of FSK preamble in bits. + \param enableOOK Use OOK modulation instead of FSK. + \returns \ref status_codes + */ + int16_t beginFSK(float freq = 434.0, float br = 4.8, float freqDev = 5.0, float rxBw = 125.0, int8_t power = 10, uint16_t preambleLength = 16, bool enableOOK = false); + + // configuration methods + + /*! + \brief Sets carrier frequency. Allowed values range from 137.0 MHz to 960.0 MHz. + \param freq Carrier frequency to be set in MHz. + \returns \ref status_codes + */ + int16_t setFrequency(float freq) override; + + /*! + \brief Set modem for the radio to use. Will perform full reset and reconfigure the radio + using its default parameters. + \param modem Modem type to set - FSK or LoRa. + \returns \ref status_codes + */ + int16_t setModem(ModemType_t modem) override; + +#if !RADIOLIB_GODMODE + private: +#endif + +}; + +#endif + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX127x.cpp b/software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX127x.cpp new file mode 100644 index 000000000..e4ba4c75e --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX127x.cpp @@ -0,0 +1,1881 @@ +#include "SX127x.h" +#include +#if !RADIOLIB_EXCLUDE_SX127X + +SX127x::SX127x(Module* mod) : PhysicalLayer(RADIOLIB_SX127X_FREQUENCY_STEP_SIZE, RADIOLIB_SX127X_MAX_PACKET_LENGTH) { + this->mod = mod; +} + +int16_t SX127x::begin(uint8_t* chipVersions, uint8_t numVersions, uint8_t syncWord, uint16_t preambleLength) { + // set module properties + this->mod->init(); + this->mod->hal->pinMode(this->mod->getIrq(), this->mod->hal->GpioModeInput); + this->mod->hal->pinMode(this->mod->getGpio(), this->mod->hal->GpioModeInput); + + // set IRQ mapping - it is different for LoRa and FSK mode + this->irqMap[RADIOLIB_IRQ_TX_DONE] = RADIOLIB_SX127X_CLEAR_IRQ_FLAG_TX_DONE; + this->irqMap[RADIOLIB_IRQ_RX_DONE] = RADIOLIB_SX127X_CLEAR_IRQ_FLAG_RX_DONE; + this->irqMap[RADIOLIB_IRQ_PREAMBLE_DETECTED] = RADIOLIB_IRQ_NOT_SUPPORTED; + this->irqMap[RADIOLIB_IRQ_SYNC_WORD_VALID] = RADIOLIB_IRQ_NOT_SUPPORTED; + this->irqMap[RADIOLIB_IRQ_HEADER_VALID] = RADIOLIB_SX127X_CLEAR_IRQ_FLAG_VALID_HEADER; + this->irqMap[RADIOLIB_IRQ_HEADER_ERR] = RADIOLIB_IRQ_NOT_SUPPORTED; + this->irqMap[RADIOLIB_IRQ_CRC_ERR] = RADIOLIB_SX127X_CLEAR_IRQ_FLAG_PAYLOAD_CRC_ERROR; + this->irqMap[RADIOLIB_IRQ_CAD_DONE] = RADIOLIB_SX127X_CLEAR_IRQ_FLAG_CAD_DONE; + this->irqMap[RADIOLIB_IRQ_CAD_DETECTED] = RADIOLIB_SX127X_CLEAR_IRQ_FLAG_CAD_DETECTED; + this->irqMap[RADIOLIB_IRQ_TIMEOUT] = RADIOLIB_SX127X_CLEAR_IRQ_FLAG_RX_TIMEOUT; + + // try to find the SX127x chip + if(!SX127x::findChip(chipVersions, numVersions)) { + RADIOLIB_DEBUG_BASIC_PRINTLN("No SX127x found!"); + this->mod->term(); + return(RADIOLIB_ERR_CHIP_NOT_FOUND); + } + RADIOLIB_DEBUG_BASIC_PRINTLN("M\tSX127x"); + + // set mode to standby + int16_t state = standby(); + RADIOLIB_ASSERT(state); + + // configure settings not accessible by API + state = config(); + RADIOLIB_ASSERT(state); + + // check active modem + if(getActiveModem() != RADIOLIB_SX127X_LORA) { + // set LoRa mode + state = setActiveModem(RADIOLIB_SX127X_LORA); + RADIOLIB_ASSERT(state); + } + + // set LoRa sync word + state = SX127x::setSyncWord(syncWord); + RADIOLIB_ASSERT(state); + + // set over current protection + state = SX127x::setCurrentLimit(60); + RADIOLIB_ASSERT(state); + + // set preamble length + state = SX127x::setPreambleLength(preambleLength); + RADIOLIB_ASSERT(state); + + // disable IQ inversion + state = SX127x::invertIQ(false); + RADIOLIB_ASSERT(state); + + // initialize internal variables + this->dataRate = 0.0; + + return(state); +} + +int16_t SX127x::beginFSK(uint8_t* chipVersions, uint8_t numVersions, float freqDev, float rxBw, uint16_t preambleLength, bool enableOOK) { + // set module properties + this->mod->init(); + this->mod->hal->pinMode(this->mod->getIrq(), this->mod->hal->GpioModeInput); + this->mod->hal->pinMode(this->mod->getGpio(), this->mod->hal->GpioModeInput); + + // set IRQ mapping - it is different for LoRa and FSK mode + this->irqMap[RADIOLIB_IRQ_TX_DONE] = RADIOLIB_SX127X_FLAG_PACKET_SENT << 8; + this->irqMap[RADIOLIB_IRQ_RX_DONE] = RADIOLIB_SX127X_FLAG_PAYLOAD_READY << 8; + this->irqMap[RADIOLIB_IRQ_PREAMBLE_DETECTED] = RADIOLIB_SX127X_FLAG_PREAMBLE_DETECT << 0; + this->irqMap[RADIOLIB_IRQ_SYNC_WORD_VALID] = RADIOLIB_SX127X_FLAG_SYNC_ADDRESS_MATCH << 0; + this->irqMap[RADIOLIB_IRQ_HEADER_VALID] = RADIOLIB_IRQ_NOT_SUPPORTED; + this->irqMap[RADIOLIB_IRQ_HEADER_ERR] = RADIOLIB_IRQ_NOT_SUPPORTED; + this->irqMap[RADIOLIB_IRQ_CRC_ERR] = RADIOLIB_IRQ_NOT_SUPPORTED; + this->irqMap[RADIOLIB_IRQ_CAD_DONE] = RADIOLIB_IRQ_NOT_SUPPORTED; + this->irqMap[RADIOLIB_IRQ_CAD_DETECTED] = RADIOLIB_IRQ_NOT_SUPPORTED; + this->irqMap[RADIOLIB_IRQ_TIMEOUT] = RADIOLIB_SX127X_FLAG_TIMEOUT << 0; + + // try to find the SX127x chip + if(!SX127x::findChip(chipVersions, numVersions)) { + RADIOLIB_DEBUG_BASIC_PRINTLN("No SX127x found!"); + this->mod->term(); + return(RADIOLIB_ERR_CHIP_NOT_FOUND); + } + RADIOLIB_DEBUG_BASIC_PRINTLN("M\tSX127x"); + + // set mode to standby + int16_t state = standby(); + RADIOLIB_ASSERT(state); + + // check currently active modem + if(getActiveModem() != RADIOLIB_SX127X_FSK_OOK) { + // set FSK mode + state = setActiveModem(RADIOLIB_SX127X_FSK_OOK); + RADIOLIB_ASSERT(state); + } + + // enable/disable OOK + state = setOOK(enableOOK); + RADIOLIB_ASSERT(state); + + // set frequency deviation + state = SX127x::setFrequencyDeviation(freqDev); + RADIOLIB_ASSERT(state); + + // set AFC bandwidth + state = SX127x::setAFCBandwidth(rxBw); + RADIOLIB_ASSERT(state); + + // set AFC&AGC trigger to RSSI (both in OOK and FSK) + state = SX127x::setAFCAGCTrigger(RADIOLIB_SX127X_RX_TRIGGER_RSSI_INTERRUPT); + RADIOLIB_ASSERT(state); + + // enable AFC + state = SX127x::setAFC(false); + RADIOLIB_ASSERT(state); + + // set receiver bandwidth + state = SX127x::setRxBandwidth(rxBw); + RADIOLIB_ASSERT(state); + + // set over current protection + state = SX127x::setCurrentLimit(60); + RADIOLIB_ASSERT(state); + + // set preamble length + state = SX127x::setPreambleLength(preambleLength); + RADIOLIB_ASSERT(state); + + // set preamble polarity + state = invertPreamble(false); + RADIOLIB_ASSERT(state); + + // set default sync word + uint8_t syncWord[] = {0x12, 0xAD}; + state = setSyncWord(syncWord, 2); + RADIOLIB_ASSERT(state); + + // disable address filtering + state = disableAddressFiltering(); + RADIOLIB_ASSERT(state); + + // set default RSSI measurement config + state = setRSSIConfig(2); + RADIOLIB_ASSERT(state); + + // set default encoding + state = setEncoding(RADIOLIB_ENCODING_NRZ); + RADIOLIB_ASSERT(state); + + // set default packet length mode + state = variablePacketLengthMode(); + + return(state); +} + +int16_t SX127x::transmit(const uint8_t* data, size_t len, uint8_t addr) { + // set mode to standby + int16_t state = setMode(RADIOLIB_SX127X_STANDBY); + RADIOLIB_ASSERT(state); + + int16_t modem = getActiveModem(); + RadioLibTime_t start = 0; + RadioLibTime_t timeout = 0; + RadioLibTime_t toa = getTimeOnAir(len); + if(modem == RADIOLIB_SX127X_LORA) { + // calculate timeout in ms (150 % of expected time-on-air) + timeout = (toa * 1.5) / 1000; + + } else if(modem == RADIOLIB_SX127X_FSK_OOK) { + // calculate timeout in ms (5ms + 500 % of expected time-on-air) + timeout = 5 + (toa * 5) / 1000; + + } else { + return(RADIOLIB_ERR_UNKNOWN); + + } + + // start transmission + RADIOLIB_DEBUG_BASIC_PRINTLN("Timeout in %lu ms", timeout); + state = startTransmit(data, len, addr); + RADIOLIB_ASSERT(state); + + // wait for packet transmission or timeout + start = this->mod->hal->millis(); + while(!this->mod->hal->digitalRead(this->mod->getIrq())) { + this->mod->hal->yield(); + if(this->mod->hal->millis() - start > timeout) { + finishTransmit(); + return(RADIOLIB_ERR_TX_TIMEOUT); + } + } + + // update data rate + RadioLibTime_t elapsed = this->mod->hal->millis() - start; + this->dataRate = (len*8.0)/((float)elapsed/1000.0); + + return(finishTransmit()); +} + +int16_t SX127x::receive(uint8_t* data, size_t len) { + // set mode to standby + int16_t state = setMode(RADIOLIB_SX127X_STANDBY); + RADIOLIB_ASSERT(state); + + int16_t modem = getActiveModem(); + if(modem == RADIOLIB_SX127X_LORA) { + // set mode to receive + state = startReceive(100, RADIOLIB_IRQ_RX_DEFAULT_FLAGS, RADIOLIB_IRQ_RX_DEFAULT_MASK, len); + RADIOLIB_ASSERT(state); + + // if no DIO1 is provided, use software timeout (100 LoRa symbols, same as hardware timeout) + RadioLibTime_t timeout = 0; + if(this->mod->getGpio() == RADIOLIB_NC) { + float symbolLength = (float) (uint32_t(1) << this->spreadingFactor) / (float) this->bandwidth; + timeout = (RadioLibTime_t)(symbolLength * 100.0); + } + + // wait for packet reception or timeout + RadioLibTime_t start = this->mod->hal->millis(); + while(!this->mod->hal->digitalRead(this->mod->getIrq())) { + this->mod->hal->yield(); + + if(this->mod->getGpio() == RADIOLIB_NC) { + // no GPIO pin provided, use software timeout + if(this->mod->hal->millis() - start > timeout) { + clearIrqFlags(RADIOLIB_SX127X_FLAGS_ALL); + return(RADIOLIB_ERR_RX_TIMEOUT); + } + } else { + // GPIO provided, use that + if(this->mod->hal->digitalRead(this->mod->getGpio())) { + clearIrqFlags(RADIOLIB_SX127X_FLAGS_ALL); + return(RADIOLIB_ERR_RX_TIMEOUT); + } + } + + } + + } else if(modem == RADIOLIB_SX127X_FSK_OOK) { + // calculate timeout in ms (500 % of expected time-on-air) + RadioLibTime_t timeout = (getTimeOnAir(len) * 5) / 1000; + + // set mode to receive + state = startReceive(0, RADIOLIB_IRQ_RX_DEFAULT_FLAGS, RADIOLIB_IRQ_RX_DEFAULT_MASK, len); + RADIOLIB_ASSERT(state); + + // wait for packet reception or timeout + RadioLibTime_t start = this->mod->hal->millis(); + while(!this->mod->hal->digitalRead(this->mod->getIrq())) { + this->mod->hal->yield(); + if(this->mod->hal->millis() - start > timeout) { + clearIrqFlags(RADIOLIB_SX127X_FLAGS_ALL); + return(RADIOLIB_ERR_RX_TIMEOUT); + } + } + } + + // read the received data + state = readData(data, len); + + return(state); +} + +int16_t SX127x::scanChannel() { + // start CAD + int16_t state = startChannelScan(); + RADIOLIB_ASSERT(state); + + // wait for channel activity detected or timeout + while(!this->mod->hal->digitalRead(this->mod->getIrq())) { + this->mod->hal->yield(); + if(this->mod->hal->digitalRead(this->mod->getGpio())) { + return(RADIOLIB_PREAMBLE_DETECTED); + } + } + + return(RADIOLIB_CHANNEL_FREE); +} + +int16_t SX127x::sleep() { + // set RF switch (if present) + this->mod->setRfSwitchState(Module::MODE_IDLE); + + // set mode to sleep + return(setMode(RADIOLIB_SX127X_SLEEP)); +} + +int16_t SX127x::standby() { + // set RF switch (if present) + this->mod->setRfSwitchState(Module::MODE_IDLE); + + // set mode to standby + return(setMode(RADIOLIB_SX127X_STANDBY)); +} + +int16_t SX127x::standby(uint8_t mode) { + (void)mode; + return(standby()); +} + +int16_t SX127x::transmitDirect(uint32_t frf) { + // check modem + if(getActiveModem() != RADIOLIB_SX127X_FSK_OOK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // set RF switch (if present) + this->mod->setRfSwitchState(Module::MODE_TX); + + // user requested to start transmitting immediately (required for RTTY) + if(frf != 0) { + this->mod->SPIwriteRegister(RADIOLIB_SX127X_REG_FRF_MSB, (frf & 0xFF0000) >> 16); + this->mod->SPIwriteRegister(RADIOLIB_SX127X_REG_FRF_MID, (frf & 0x00FF00) >> 8); + this->mod->SPIwriteRegister(RADIOLIB_SX127X_REG_FRF_LSB, frf & 0x0000FF); + + return(setMode(RADIOLIB_SX127X_TX)); + } + + // activate direct mode + int16_t state = directMode(); + RADIOLIB_ASSERT(state); + + // apply fixes to errata + RADIOLIB_ERRATA_SX127X(false); + + // start transmitting + return(setMode(RADIOLIB_SX127X_TX)); +} + +int16_t SX127x::receiveDirect() { + // check modem + if(getActiveModem() != RADIOLIB_SX127X_FSK_OOK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // set RF switch (if present) + this->mod->setRfSwitchState(Module::MODE_RX); + + // activate direct mode + int16_t state = directMode(); + RADIOLIB_ASSERT(state); + + // apply fixes to errata + RADIOLIB_ERRATA_SX127X(true); + + // start receiving + return(setMode(RADIOLIB_SX127X_RX)); +} + +int16_t SX127x::directMode() { + // set mode to standby + int16_t state = setMode(RADIOLIB_SX127X_STANDBY); + RADIOLIB_ASSERT(state); + + // set DIO mapping + state = this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_DIO_MAPPING_1, RADIOLIB_SX127X_DIO1_CONT_DCLK | RADIOLIB_SX127X_DIO2_CONT_DATA, 5, 2); + RADIOLIB_ASSERT(state); + + // enable receiver startup without preamble or RSSI + state = SX127x::setAFCAGCTrigger(RADIOLIB_SX127X_RX_TRIGGER_NONE); + RADIOLIB_ASSERT(state); + + // set continuous mode + return(this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PACKET_CONFIG_2, RADIOLIB_SX127X_DATA_MODE_CONTINUOUS, 6, 6)); +} + +int16_t SX127x::packetMode() { + // check modem + if(getActiveModem() != RADIOLIB_SX127X_FSK_OOK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + return(this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PACKET_CONFIG_2, RADIOLIB_SX127X_DATA_MODE_PACKET, 6, 6)); +} + +int16_t SX127x::startReceive() { + return(this->startReceive(0, RADIOLIB_IRQ_RX_DEFAULT_FLAGS, RADIOLIB_IRQ_RX_DEFAULT_MASK, 0)); +} + +int16_t SX127x::startReceive(uint32_t timeout, RadioLibIrqFlags_t irqFlags, RadioLibIrqFlags_t irqMask, size_t len) { + uint8_t mode = RADIOLIB_SX127X_RXCONTINUOUS; + + // set mode to standby + int16_t state = setMode(RADIOLIB_SX127X_STANDBY); + RADIOLIB_ASSERT(state); + + // set DIO pin mapping + state = this->setIrqFlags(getIrqMapped(irqFlags & irqMask)); + RADIOLIB_ASSERT(state); + + int16_t modem = getActiveModem(); + if(modem == RADIOLIB_SX127X_LORA) { + if(timeout != 0) { + // for non-zero timeout value, change mode to Rx single and set the timeout + mode = RADIOLIB_SX127X_RXSINGLE; + uint8_t msb_sym = (timeout > 0x3FF) ? 0x3 : (uint8_t)(timeout >> 8); + uint8_t lsb_sym = (timeout > 0x3FF) ? 0xFF : (uint8_t)(timeout & 0xFF); + state = this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_MODEM_CONFIG_2, msb_sym, 1, 0); + state |= this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_SYMB_TIMEOUT_LSB, lsb_sym); + RADIOLIB_ASSERT(state); + } + + // in FHSS mode, enable channel change interrupt + if(this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_HOP_PERIOD) > RADIOLIB_SX127X_HOP_PERIOD_OFF) { + state = this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_DIO_MAPPING_1, RADIOLIB_SX127X_DIO1_LORA_FHSS_CHANGE_CHANNEL, 5, 4); + } + + // set expected packet length for SF6 + if(this->spreadingFactor == 6) { + state |= this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PAYLOAD_LENGTH, len); + this->packetLength = len; + } + + // apply fixes to errata + RADIOLIB_ERRATA_SX127X(true); + + // clear interrupt flags + clearIrqFlags(RADIOLIB_SX127X_FLAGS_ALL); + + // set FIFO pointers + state |= this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_FIFO_RX_BASE_ADDR, RADIOLIB_SX127X_FIFO_RX_BASE_ADDR_MAX); + state |= this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_FIFO_ADDR_PTR, RADIOLIB_SX127X_FIFO_RX_BASE_ADDR_MAX); + RADIOLIB_ASSERT(state); + + } else if(modem == RADIOLIB_SX127X_FSK_OOK) { + // clear interrupt flags + clearIrqFlags(RADIOLIB_SX127X_FLAGS_ALL); + + // FSK modem does not distinguish between Rx single and continuous + mode = RADIOLIB_SX127X_RX; + } + + // set RF switch (if present) + this->mod->setRfSwitchState(Module::MODE_RX); + + // set mode to receive + return(setMode(mode)); +} + +void SX127x::setDio0Action(void (*func)(void), uint32_t dir) { + this->mod->hal->attachInterrupt(this->mod->hal->pinToInterrupt(this->mod->getIrq()), func, dir); +} + +void SX127x::clearDio0Action() { + this->mod->hal->detachInterrupt(this->mod->hal->pinToInterrupt(this->mod->getIrq())); +} + +void SX127x::setDio1Action(void (*func)(void), uint32_t dir) { + if(this->mod->getGpio() == RADIOLIB_NC) { + return; + } + this->mod->hal->attachInterrupt(this->mod->hal->pinToInterrupt(this->mod->getGpio()), func, dir); +} + +void SX127x::clearDio1Action() { + if(this->mod->getGpio() == RADIOLIB_NC) { + return; + } + this->mod->hal->detachInterrupt(this->mod->hal->pinToInterrupt(this->mod->getGpio())); +} + +void SX127x::setPacketReceivedAction(void (*func)(void)) { + this->setDio0Action(func, this->mod->hal->GpioInterruptRising); +} + +void SX127x::clearPacketReceivedAction() { + this->clearDio0Action(); +} + +void SX127x::setPacketSentAction(void (*func)(void)) { + this->setDio0Action(func, this->mod->hal->GpioInterruptRising); +} + +void SX127x::clearPacketSentAction() { + this->clearDio0Action(); +} + +void SX127x::setChannelScanAction(void (*func)(void)) { + this->setDio0Action(func, this->mod->hal->GpioInterruptRising); +} + +void SX127x::clearChannelScanAction() { + this->clearDio0Action(); +} + +void SX127x::setFifoEmptyAction(void (*func)(void)) { + // set DIO1 to the FIFO empty event (the register setting is done in startTransmit) + setDio1Action(func, this->mod->hal->GpioInterruptRising); +} + +void SX127x::clearFifoEmptyAction() { + clearDio1Action(); +} + +void SX127x::setFifoFullAction(void (*func)(void)) { + // set the interrupt + this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_FIFO_THRESH, RADIOLIB_SX127X_FIFO_THRESH, 5, 0); + this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_DIO_MAPPING_1, RADIOLIB_SX127X_DIO1_PACK_FIFO_LEVEL, 5, 4); + + // set DIO1 to the FIFO full event + setDio1Action(func, this->mod->hal->GpioInterruptRising); +} + +void SX127x::clearFifoFullAction() { + clearDio1Action(); + this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_DIO_MAPPING_1, 0x00, 5, 4); +} + +bool SX127x::fifoAdd(uint8_t* data, int totalLen, int* remLen) { + // subtract first (this may be the first time we get to modify the remaining length) + *remLen -= RADIOLIB_SX127X_FIFO_THRESH - 1; + + // check if there is still something left to send + if(*remLen <= 0) { + // we're done + return(true); + } + + // calculate the number of bytes we can copy + int len = *remLen; + if(len > RADIOLIB_SX127X_FIFO_THRESH - 1) { + len = RADIOLIB_SX127X_FIFO_THRESH - 1; + } + + // copy the bytes to the FIFO + this->mod->SPIwriteRegisterBurst(RADIOLIB_SX127X_REG_FIFO, &data[totalLen - *remLen], len); + + // we're not done yet + return(false); +} + +bool SX127x::fifoGet(volatile uint8_t* data, int totalLen, volatile int* rcvLen) { + // get pointer to the correct position in data buffer + uint8_t* dataPtr = (uint8_t*)&data[*rcvLen]; + + // check how much data are we still expecting + uint8_t len = RADIOLIB_SX127X_FIFO_THRESH - 1; + if(totalLen - *rcvLen < len) { + // we're nearly at the end + len = totalLen - *rcvLen; + } + + // get the data + this->mod->SPIreadRegisterBurst(RADIOLIB_SX127X_REG_FIFO, len, dataPtr); + *rcvLen = *rcvLen + len; + + // check if we're done + if(*rcvLen >= totalLen) { + return(true); + } + return(false); +} + +int16_t SX127x::startTransmit(const uint8_t* data, size_t len, uint8_t addr) { + // set mode to standby + int16_t state = setMode(RADIOLIB_SX127X_STANDBY); + + int16_t modem = getActiveModem(); + if(modem == RADIOLIB_SX127X_LORA) { + // check packet length + if(len > RADIOLIB_SX127X_MAX_PACKET_LENGTH) { + return(RADIOLIB_ERR_PACKET_TOO_LONG); + } + + // set DIO mapping + if(this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_HOP_PERIOD) > RADIOLIB_SX127X_HOP_PERIOD_OFF) { + this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_DIO_MAPPING_1, RADIOLIB_SX127X_DIO0_LORA_TX_DONE | RADIOLIB_SX127X_DIO1_LORA_FHSS_CHANGE_CHANNEL, 7, 4); + } else { + this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_DIO_MAPPING_1, RADIOLIB_SX127X_DIO0_LORA_TX_DONE, 7, 6); + } + + // apply fixes to errata + RADIOLIB_ERRATA_SX127X(false); + + // clear interrupt flags + clearIrqFlags(RADIOLIB_SX127X_FLAGS_ALL); + + // set packet length + state |= this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PAYLOAD_LENGTH, len); + + // set FIFO pointers + state |= this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_FIFO_TX_BASE_ADDR, RADIOLIB_SX127X_FIFO_TX_BASE_ADDR_MAX); + state |= this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_FIFO_ADDR_PTR, RADIOLIB_SX127X_FIFO_TX_BASE_ADDR_MAX); + + } else if(modem == RADIOLIB_SX127X_FSK_OOK) { + // clear interrupt flags + clearIrqFlags(RADIOLIB_SX127X_FLAGS_ALL); + + // set DIO mapping + if(len > RADIOLIB_SX127X_MAX_PACKET_LENGTH_FSK) { + this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_DIO_MAPPING_1, RADIOLIB_SX127X_DIO1_PACK_FIFO_EMPTY, 5, 4); + } else { + this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_DIO_MAPPING_1, RADIOLIB_SX127X_DIO0_PACK_PACKET_SENT, 7, 6); + } + + // set packet length - increased by 1 when address filter is enabled + uint8_t filter = this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_PACKET_CONFIG_1, 2, 1); + if(this->packetLengthConfig == RADIOLIB_SX127X_PACKET_VARIABLE) { + if((filter == RADIOLIB_SX127X_ADDRESS_FILTERING_NODE) || (filter == RADIOLIB_SX127X_ADDRESS_FILTERING_NODE_BROADCAST)) { + this->mod->SPIwriteRegister(RADIOLIB_SX127X_REG_FIFO, len + 1); + this->mod->SPIwriteRegister(RADIOLIB_SX127X_REG_FIFO, addr); + } else { + this->mod->SPIwriteRegister(RADIOLIB_SX127X_REG_FIFO, len); + } + + } + + } + + // write packet to FIFO + size_t packetLen = len; + if((modem == RADIOLIB_SX127X_FSK_OOK) && (len > RADIOLIB_SX127X_MAX_PACKET_LENGTH_FSK)) { + packetLen = RADIOLIB_SX127X_FIFO_THRESH - 1; + this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_FIFO_THRESH, RADIOLIB_SX127X_TX_START_FIFO_NOT_EMPTY, 7, 7); + } + this->mod->SPIwriteRegisterBurst(RADIOLIB_SX127X_REG_FIFO, const_cast(data), packetLen); + + // set RF switch (if present) + this->mod->setRfSwitchState(Module::MODE_TX); + + // start transmission + state |= setMode(RADIOLIB_SX127X_TX); + RADIOLIB_ASSERT(state); + + return(RADIOLIB_ERR_NONE); +} + +int16_t SX127x::finishTransmit() { + // wait for at least 1 bit at the lowest possible bit rate before clearing IRQ flags + // not doing this and clearing RADIOLIB_SX127X_FLAG_FIFO_OVERRUN will dump the FIFO, + // which can lead to mangling of the last bit (#808) + mod->hal->delayMicroseconds(1000000/1200); + + // clear interrupt flags + clearIrqFlags(RADIOLIB_SX127X_FLAGS_ALL); + + // set mode to standby to disable transmitter/RF switch + return(standby()); +} + +int16_t SX127x::readData(uint8_t* data, size_t len) { + int16_t modem = getActiveModem(); + + // get packet length + size_t length = getPacketLength(); + size_t dumpLen = 0; + if((len != 0) && (len < length)) { + // user requested less data than we got, only return what was requested + dumpLen = length - len; + length = len; + } + + // check payload CRC + int16_t state = RADIOLIB_ERR_NONE; + if(this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_IRQ_FLAGS, 5, 5) == RADIOLIB_SX127X_CLEAR_IRQ_FLAG_PAYLOAD_CRC_ERROR) { + state = RADIOLIB_ERR_CRC_MISMATCH; + } + + if(modem == RADIOLIB_SX127X_LORA) { + // check packet header integrity + if(this->crcEnabled && (state == RADIOLIB_ERR_NONE) && (this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_HOP_CHANNEL, 6, 6) == 0)) { + // CRC is disabled according to packet header and enabled according to user + // most likely damaged packet header + state = RADIOLIB_ERR_LORA_HEADER_DAMAGED; + } + // set FIFO read pointer to the start of the current packet + int16_t addr = this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_FIFO_RX_CURRENT_ADDR); + if (addr >= 0) { + this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_FIFO_ADDR_PTR, addr); + } + + } else if(modem == RADIOLIB_SX127X_FSK_OOK) { + // check address filtering + uint8_t filter = this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_PACKET_CONFIG_1, 2, 1); + if((filter == RADIOLIB_SX127X_ADDRESS_FILTERING_NODE) || (filter == RADIOLIB_SX127X_ADDRESS_FILTERING_NODE_BROADCAST)) { + this->mod->SPIreadRegister(RADIOLIB_SX127X_REG_FIFO); + } + } + + // read packet data + this->mod->SPIreadRegisterBurst(RADIOLIB_SX127X_REG_FIFO, length, data); + + // dump the bytes that weren't requested + if(dumpLen != 0) { + clearFIFO(dumpLen); + } + + // clear internal flag so getPacketLength can return the new packet length + this->packetLengthQueried = false; + + // clear interrupt flags + clearIrqFlags(RADIOLIB_SX127X_FLAGS_ALL); + + return(state); +} + +int16_t SX127x::startChannelScan() { + // check active modem + if(getActiveModem() != RADIOLIB_SX127X_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // set mode to standby + int16_t state = setMode(RADIOLIB_SX127X_STANDBY); + RADIOLIB_ASSERT(state); + + // clear interrupt flags + clearIrqFlags(RADIOLIB_SX127X_FLAGS_ALL); + + // set DIO pin mapping + state = this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_DIO_MAPPING_1, RADIOLIB_SX127X_DIO0_LORA_CAD_DONE | RADIOLIB_SX127X_DIO1_LORA_CAD_DETECTED, 7, 4); + RADIOLIB_ASSERT(state); + + // set RF switch (if present) + this->mod->setRfSwitchState(Module::MODE_RX); + + // set mode to CAD + state = setMode(RADIOLIB_SX127X_CAD); + return(state); +} + +int16_t SX127x::getChannelScanResult() { + if((this->getIRQFlags() & RADIOLIB_SX127X_CLEAR_IRQ_FLAG_CAD_DETECTED) == RADIOLIB_SX127X_CLEAR_IRQ_FLAG_CAD_DETECTED) { + return(RADIOLIB_PREAMBLE_DETECTED); + } + return(RADIOLIB_CHANNEL_FREE); +} + +int16_t SX127x::setSyncWord(uint8_t syncWord) { + // check active modem + if(getActiveModem() != RADIOLIB_SX127X_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // set mode to standby + setMode(RADIOLIB_SX127X_STANDBY); + + // write register + return(this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_SYNC_WORD, syncWord)); +} + +int16_t SX127x::setCurrentLimit(uint8_t currentLimit) { + // check allowed range + if(!(((currentLimit >= 45) && (currentLimit <= 240)) || (currentLimit == 0))) { + return(RADIOLIB_ERR_INVALID_CURRENT_LIMIT); + } + + // set mode to standby + int16_t state = setMode(RADIOLIB_SX127X_STANDBY); + + // set OCP limit + uint8_t raw; + if(currentLimit == 0) { + // limit set to 0, disable OCP + state |= this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_OCP, RADIOLIB_SX127X_OCP_OFF, 5, 5); + } else if(currentLimit <= 120) { + raw = (currentLimit - 45) / 5; + state |= this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_OCP, RADIOLIB_SX127X_OCP_ON | raw, 5, 0); + } else if(currentLimit <= 240) { + raw = (currentLimit + 30) / 10; + state |= this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_OCP, RADIOLIB_SX127X_OCP_ON | raw, 5, 0); + } + return(state); +} + +int16_t SX127x::setPreambleLength(size_t preambleLength) { + // set mode to standby + int16_t state = setMode(RADIOLIB_SX127X_STANDBY); + RADIOLIB_ASSERT(state); + + // check active modem + uint8_t modem = getActiveModem(); + if(modem == RADIOLIB_SX127X_LORA) { + // check allowed range + if(preambleLength < 6) { + return(RADIOLIB_ERR_INVALID_PREAMBLE_LENGTH); + } + + // set preamble length + state = this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PREAMBLE_MSB, (uint8_t)((preambleLength >> 8) & 0xFF)); + state |= this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PREAMBLE_LSB, (uint8_t)(preambleLength & 0xFF)); + return(state); + + } else if(modem == RADIOLIB_SX127X_FSK_OOK) { + // set preamble length (in bytes) + uint16_t numBytes = preambleLength / 8; + state = this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PREAMBLE_MSB_FSK, (uint8_t)((numBytes >> 8) & 0xFF)); + state |= this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PREAMBLE_LSB_FSK, (uint8_t)(numBytes & 0xFF)); + return(state); + } + + return(RADIOLIB_ERR_UNKNOWN); +} + +int16_t SX127x::invertPreamble(bool enable) { + // set mode to standby + int16_t state = setMode(RADIOLIB_SX127X_STANDBY); + RADIOLIB_ASSERT(state); + + // check active modem + uint8_t modem = getActiveModem(); + if(modem == RADIOLIB_SX127X_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } else if(modem == RADIOLIB_SX127X_FSK_OOK) { + // set preamble polarity + uint8_t polarity = enable ? RADIOLIB_SX127X_PREAMBLE_POLARITY_AA : RADIOLIB_SX127X_PREAMBLE_POLARITY_55; + state = this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_SYNC_CONFIG, polarity, 5, 5); + return(state); + } + + return(RADIOLIB_ERR_UNKNOWN); +} + +float SX127x::getFrequencyError(bool autoCorrect) { + int16_t modem = getActiveModem(); + if(modem == RADIOLIB_SX127X_LORA) { + // get raw frequency error + uint32_t raw = (uint32_t)this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_FEI_MSB, 3, 0) << 16; + raw |= (uint16_t)this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_FEI_MID) << 8; + raw |= this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_FEI_LSB); + + uint32_t base = (uint32_t)2 << 23; + float error; + + // check the first bit + if(raw & 0x80000) { + // frequency error is negative + raw |= (uint32_t)0xFFF00000; + raw = ~raw + 1; + error = (((float)raw * (float)base)/32000000.0) * (this->bandwidth/500.0) * -1.0; + } else { + error = (((float)raw * (float)base)/32000000.0) * (this->bandwidth/500.0); + } + + if(autoCorrect) { + // adjust LoRa modem data rate + float ppmOffset = 0.95 * (error/32.0); + this->mod->SPIwriteRegister(0x27, (uint8_t)ppmOffset); + } + + return(error); + + } else if(modem == RADIOLIB_SX127X_FSK_OOK) { + // get raw frequency error + uint16_t raw = (uint16_t)this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_FEI_MSB_FSK) << 8; + raw |= this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_FEI_LSB_FSK); + + uint32_t base = 1; + float error; + + // check the first bit + if(raw & 0x8000) { + // frequency error is negative + raw |= (uint32_t)0xFFF00000; + raw = ~raw + 1; + error = (float)raw * (32000000.0 / (float)(base << 19)) * -1.0; + } else { + error = (float)raw * (32000000.0 / (float)(base << 19)); + } + + return(error); + } + + return(RADIOLIB_ERR_UNKNOWN); +} + +float SX127x::getAFCError() +{ + // check active modem + int16_t modem = getActiveModem(); + if(modem != RADIOLIB_SX127X_FSK_OOK) { + return 0; + } + + // get raw frequency error + int16_t raw = (uint16_t)this->mod->SPIreadRegister(RADIOLIB_SX127X_REG_AFC_MSB) << 8; + raw |= this->mod->SPIreadRegister(RADIOLIB_SX127X_REG_AFC_LSB); + + uint32_t base = 1; + return raw * (32000000.0 / (float)(base << 19)); +} + +float SX127x::getSNR() { + // check active modem + if(getActiveModem() != RADIOLIB_SX127X_LORA) { + return(0); + } + + // get SNR value + int8_t rawSNR = (int8_t)this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_PKT_SNR_VALUE); + return(rawSNR / 4.0); +} + +float SX127x::getDataRate() const { + return(this->dataRate); +} + +int16_t SX127x::setBitRateCommon(float br, uint8_t fracRegAddr) { + // check active modem + if(getActiveModem() != RADIOLIB_SX127X_FSK_OOK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // check allowed bit rate + // datasheet says 1.2 kbps should be the smallest possible, but 0.512 works fine + if(ookEnabled) { + RADIOLIB_CHECK_RANGE(br, 0.5, 32.768002, RADIOLIB_ERR_INVALID_BIT_RATE); // Found that 32.768 is 32.768002 + } else { + RADIOLIB_CHECK_RANGE(br, 0.5, 300.0, RADIOLIB_ERR_INVALID_BIT_RATE); + } + + // set mode to STANDBY + int16_t state = setMode(RADIOLIB_SX127X_STANDBY); + RADIOLIB_ASSERT(state); + + // set bit rate + uint16_t bitRateRaw = (RADIOLIB_SX127X_CRYSTAL_FREQ * 1000.0) / br; + state = this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_BITRATE_MSB, (bitRateRaw & 0xFF00) >> 8, 7, 0); + state |= this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_BITRATE_LSB, bitRateRaw & 0x00FF, 7, 0); + + // set fractional part of bit rate + if(!ookEnabled) { + float bitRateRem = ((RADIOLIB_SX127X_CRYSTAL_FREQ * 1000.0) / (float)br) - (float)bitRateRaw; + uint8_t bitRateFrac = bitRateRem * 16; + state |= this->mod->SPIsetRegValue(fracRegAddr, bitRateFrac, 7, 0); + } + + if(state == RADIOLIB_ERR_NONE) { + this->bitRate = br; + } + return(state); +} + +int16_t SX127x::setFrequencyDeviation(float freqDev) { + // check active modem + if(getActiveModem() != RADIOLIB_SX127X_FSK_OOK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // set frequency deviation to lowest available setting (required for digimodes) + float newFreqDev = freqDev; + if(freqDev < 0.0) { + newFreqDev = 0.6; + } + + // check frequency deviation range + if(!((newFreqDev + this->bitRate/2.0 <= 250.0) && (freqDev <= 200.0))) { + return(RADIOLIB_ERR_INVALID_FREQUENCY_DEVIATION); + } + + // set mode to STANDBY + int16_t state = setMode(RADIOLIB_SX127X_STANDBY); + RADIOLIB_ASSERT(state); + + // set allowed frequency deviation + uint32_t base = 1; + uint32_t FDEV = (newFreqDev * (base << 19)) / 32000; + state = this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_FDEV_MSB, (FDEV & 0xFF00) >> 8, 5, 0); + state |= this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_FDEV_LSB, FDEV & 0x00FF, 7, 0); + return(state); +} + +uint8_t SX127x::calculateBWManExp(float bandwidth) +{ + for(uint8_t e = 7; e >= 1; e--) { + for(int8_t m = 2; m >= 0; m--) { + float point = (RADIOLIB_SX127X_CRYSTAL_FREQ * 1000000.0)/(((4 * m) + 16) * ((uint32_t)1 << (e + 2))); + if(fabsf(bandwidth - ((point / 1000.0) + 0.05)) <= 0.5) { + return((m << 3) | e); + } + } + } + return 0; +} + +int16_t SX127x::setRxBandwidth(float rxBw) { + // check active modem + if(getActiveModem() != RADIOLIB_SX127X_FSK_OOK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + RADIOLIB_CHECK_RANGE(rxBw, 2.6, 250.0, RADIOLIB_ERR_INVALID_RX_BANDWIDTH); + + // set mode to STANDBY + int16_t state = setMode(RADIOLIB_SX127X_STANDBY); + RADIOLIB_ASSERT(state); + + // set Rx bandwidth + return(this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_RX_BW, calculateBWManExp(rxBw), 4, 0)); +} + +int16_t SX127x::setAFCBandwidth(float rxBw) { + // check active modem + if(getActiveModem() != RADIOLIB_SX127X_FSK_OOK){ + return(RADIOLIB_ERR_WRONG_MODEM); + } + + RADIOLIB_CHECK_RANGE(rxBw, 2.6, 250.0, RADIOLIB_ERR_INVALID_RX_BANDWIDTH); + + // set mode to STANDBY + int16_t state = setMode(RADIOLIB_SX127X_STANDBY); + RADIOLIB_ASSERT(state); + + // set AFC bandwidth + return(this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_AFC_BW, calculateBWManExp(rxBw), 4, 0)); +} + +int16_t SX127x::setAFC(bool isEnabled) { + // check active modem + if(getActiveModem() != RADIOLIB_SX127X_FSK_OOK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + //set AFC auto on/off + return(this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_RX_CONFIG, isEnabled ? RADIOLIB_SX127X_AFC_AUTO_ON : RADIOLIB_SX127X_AFC_AUTO_OFF, 4, 4)); +} + +int16_t SX127x::setAFCAGCTrigger(uint8_t trigger) { + if(getActiveModem() != RADIOLIB_SX127X_FSK_OOK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + //set AFC&AGC trigger + return(this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_RX_CONFIG, trigger, 2, 0)); +} + +int16_t SX127x::setSyncWord(uint8_t* syncWord, size_t len) { + // check active modem + uint8_t modem = getActiveModem(); + if(modem == RADIOLIB_SX127X_FSK_OOK) { + + // disable sync word in case len is 0 + if(len == 0) { + int16_t state = this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_SYNC_CONFIG, RADIOLIB_SX127X_SYNC_OFF, 4, 4); + return(state); + } + + RADIOLIB_CHECK_RANGE(len, 1, 8, RADIOLIB_ERR_INVALID_SYNC_WORD); + + // sync word must not contain value 0x00 + for(size_t i = 0; i < len; i++) { + if(syncWord[i] == 0x00) { + return(RADIOLIB_ERR_INVALID_SYNC_WORD); + } + } + + // enable sync word recognition + int16_t state = this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_SYNC_CONFIG, RADIOLIB_SX127X_SYNC_ON, 4, 4); + state |= this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_SYNC_CONFIG, len - 1, 2, 0); + RADIOLIB_ASSERT(state); + + // set sync word + this->mod->SPIwriteRegisterBurst(RADIOLIB_SX127X_REG_SYNC_VALUE_1, syncWord, len); + return(RADIOLIB_ERR_NONE); + + } else if(modem == RADIOLIB_SX127X_LORA) { + // with length set to 1 and LoRa modem active, assume it is the LoRa sync word + if(len > 1) { + return(RADIOLIB_ERR_INVALID_SYNC_WORD); + } + + return(this->setSyncWord(syncWord[0])); + } + + return(RADIOLIB_ERR_WRONG_MODEM); +} + +int16_t SX127x::setNodeAddress(uint8_t nodeAddr) { + // check active modem + if(getActiveModem() != RADIOLIB_SX127X_FSK_OOK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // enable address filtering (node only) + int16_t state = this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PACKET_CONFIG_1, RADIOLIB_SX127X_ADDRESS_FILTERING_NODE, 2, 1); + RADIOLIB_ASSERT(state); + + // set node address + return(this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_NODE_ADRS, nodeAddr)); +} + +int16_t SX127x::setBroadcastAddress(uint8_t broadAddr) { + // check active modem + if(getActiveModem() != RADIOLIB_SX127X_FSK_OOK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // enable address filtering (node + broadcast) + int16_t state = this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PACKET_CONFIG_1, RADIOLIB_SX127X_ADDRESS_FILTERING_NODE_BROADCAST, 2, 1); + RADIOLIB_ASSERT(state); + + // set broadcast address + return(this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_BROADCAST_ADRS, broadAddr)); +} + +int16_t SX127x::disableAddressFiltering() { + // check active modem + if(getActiveModem() != RADIOLIB_SX127X_FSK_OOK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // disable address filtering + int16_t state = this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PACKET_CONFIG_1, RADIOLIB_SX127X_ADDRESS_FILTERING_OFF, 2, 1); + RADIOLIB_ASSERT(state); + + // set node address to default (0x00) + state = this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_NODE_ADRS, 0x00); + RADIOLIB_ASSERT(state); + + // set broadcast address to default (0x00) + return(this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_BROADCAST_ADRS, 0x00)); +} + +int16_t SX127x::setOokThresholdType(uint8_t type) { + // check active modem + if(getActiveModem() != RADIOLIB_SX127X_FSK_OOK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + return(this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_OOK_PEAK, type, 4, 3, 5)); +} + +int16_t SX127x::setOokFixedOrFloorThreshold(uint8_t value) { + // check active modem + if(getActiveModem() != RADIOLIB_SX127X_FSK_OOK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + return(this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_OOK_FIX, value, 7, 0, 5)); +} + +int16_t SX127x::setOokPeakThresholdDecrement(uint8_t value) { + // check active modem + if(getActiveModem() != RADIOLIB_SX127X_FSK_OOK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + return(this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_OOK_AVG, value, 7, 5, 5)); +} + +int16_t SX127x::setOokPeakThresholdStep(uint8_t value) { + // check active modem + if(getActiveModem() != RADIOLIB_SX127X_FSK_OOK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + return(this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_OOK_PEAK, value, 2, 0, 5)); +} + +int16_t SX127x::enableBitSync() { + return(this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_OOK_PEAK, RADIOLIB_SX127X_BIT_SYNC_ON, 5, 5, 5)); +} + +int16_t SX127x::disableBitSync() { + return(this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_OOK_PEAK, RADIOLIB_SX127X_BIT_SYNC_OFF, 5, 5, 5)); +} + +int16_t SX127x::setOOK(bool enableOOK) { + // check active modem + if(getActiveModem() != RADIOLIB_SX127X_FSK_OOK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // set OOK and if successful, save the new setting + int16_t state = RADIOLIB_ERR_NONE; + if(enableOOK) { + state = this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_OP_MODE, RADIOLIB_SX127X_MODULATION_OOK, 6, 5, 5); + state |= SX127x::setAFCAGCTrigger(RADIOLIB_SX127X_RX_TRIGGER_RSSI_INTERRUPT); + } else { + state = this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_OP_MODE, RADIOLIB_SX127X_MODULATION_FSK, 6, 5, 5); + state |= SX127x::setAFCAGCTrigger(RADIOLIB_SX127X_RX_TRIGGER_BOTH); + } + if(state == RADIOLIB_ERR_NONE) { + ookEnabled = enableOOK; + } + + return(state); +} + +int16_t SX127x::setFrequencyRaw(float newFreq) { + int16_t state = RADIOLIB_ERR_NONE; + + // set mode to standby if not FHSS + if(this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_HOP_PERIOD) == RADIOLIB_SX127X_HOP_PERIOD_OFF) { + state = setMode(RADIOLIB_SX127X_STANDBY); + } + + // calculate register values + uint32_t FRF = (newFreq * (uint32_t(1) << RADIOLIB_SX127X_DIV_EXPONENT)) / RADIOLIB_SX127X_CRYSTAL_FREQ; + + // write registers + state |= this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_FRF_MSB, (FRF & 0xFF0000) >> 16); + state |= this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_FRF_MID, (FRF & 0x00FF00) >> 8); + state |= this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_FRF_LSB, FRF & 0x0000FF); + return(state); +} + +size_t SX127x::getPacketLength(bool update) { + int16_t modem = getActiveModem(); + + if(modem == RADIOLIB_SX127X_LORA) { + if(this->spreadingFactor != 6) { + // get packet length for SF7 - SF12 + return(this->mod->SPIreadRegister(RADIOLIB_SX127X_REG_RX_NB_BYTES)); + + } else { + // return the cached value for SF6 + return(this->packetLength); + } + + } else if(modem == RADIOLIB_SX127X_FSK_OOK) { + // get packet length + if(!this->packetLengthQueried && update) { + if (this->packetLengthConfig == RADIOLIB_SX127X_PACKET_VARIABLE) { + this->packetLength = this->mod->SPIreadRegister(RADIOLIB_SX127X_REG_FIFO); + } else { + this->packetLength = this->mod->SPIreadRegister(RADIOLIB_SX127X_REG_PAYLOAD_LENGTH_FSK); + } + this->packetLengthQueried = true; + } + } + + return(this->packetLength); +} + +int16_t SX127x::fixedPacketLengthMode(uint8_t len) { + return(SX127x::setPacketMode(RADIOLIB_SX127X_PACKET_FIXED, len)); +} + +int16_t SX127x::variablePacketLengthMode(uint8_t maxLen) { + return(SX127x::setPacketMode(RADIOLIB_SX127X_PACKET_VARIABLE, maxLen)); +} + +float SX127x::getNumSymbols(size_t len) { + // get symbol length in us + float symbolLength = (float) (uint32_t(1) << this->spreadingFactor) / (float) this->bandwidth; + + // get Low Data Rate optimization flag + float de = 0; + if (symbolLength >= 16.0) { + de = 1; + } + + // get explicit/implicit header enabled flag + float ih = (float) this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_MODEM_CONFIG_1, 0, 0); + + // get CRC enabled flag + float crc = (float) (this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_MODEM_CONFIG_2, 2, 2) >> 2); + + // get number of preamble symbols + float n_pre = (float) ((this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_PREAMBLE_MSB) << 8) | this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_PREAMBLE_LSB)); + + // get number of payload symbols + float n_pay = 8.0 + RADIOLIB_MAX(ceilf((8.0 * (float) len - 4.0 * (float) this->spreadingFactor + 28.0 + 16.0 * crc - 20.0 * ih) / (4.0 * (float) this->spreadingFactor - 8.0 * de)) * (float) this->codingRate, 0.0); + + // add 4.25 symbols for the sync + return(n_pre + n_pay + 4.25f); +} + +RadioLibTime_t SX127x::getTimeOnAir(size_t len) { + // check active modem + uint8_t modem = getActiveModem(); + if (modem == RADIOLIB_SX127X_LORA) { + // get symbol length in us + float symbolLength = (float) (uint32_t(1) << this->spreadingFactor) / (float) this->bandwidth; + + // get number of symbols + float n_sym = getNumSymbols(len); + + // get time-on-air in us + return ceil((double)symbolLength * (double)n_sym) * 1000; + + } else if(modem == RADIOLIB_SX127X_FSK_OOK) { + // get number of bits preamble + float n_pre = (float) ((this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_PREAMBLE_MSB_FSK) << 8) | this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_PREAMBLE_LSB_FSK)) * 8; + // get the number of bits of the sync word + float n_syncWord = (float) (this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_SYNC_CONFIG, 2, 0) + 1) * 8; + // get CRC bits + float crc = (this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_PACKET_CONFIG_1, 4, 4) == RADIOLIB_SX127X_CRC_ON) * 16; + + if (this->packetLengthConfig == RADIOLIB_SX127X_PACKET_FIXED) { + // if packet size fixed -> len = fixed packet length + len = this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_PAYLOAD_LENGTH_FSK); + } else { + // if packet variable -> Add 1 extra byte for payload length + len += 1; + } + + // calculate time-on-air in us {[(length in bytes) * (8 bits / 1 byte)] / [(Bit Rate in kbps) * (1000 bps / 1 kbps)]} * (1000000 us in 1 sec) + return((uint32_t) (((crc + n_syncWord + n_pre + (float) (len * 8)) / (this->bitRate * 1000.0)) * 1000000.0)); + } + + return(RADIOLIB_ERR_UNKNOWN); +} + +RadioLibTime_t SX127x::calculateRxTimeout(RadioLibTime_t timeoutUs) { + // the timeout is given as the number of symbols + // the calling function should provide some extra width, as this number of symbols is truncated to integer + // the order of operators is swapped here to decrease the effects of this truncation error + float symbolLength = (float) (uint32_t(1) << this->spreadingFactor) / (float) this->bandwidth; + RadioLibTime_t numSymbols = (timeoutUs / symbolLength) / 1000; + return(numSymbols); +} + +uint32_t SX127x::getIrqFlags() { + return((uint32_t)this->getIRQFlags()); +} + +int16_t SX127x::setIrqFlags(uint32_t irq) { + // this is a bit convoluted, but unfortunately SX127x IRQ flags are not used to enable/disable that IRQ ... + // in addition, the configuration is often mutually exclusive, so we iterate over the set bits in a loop + uint8_t usedPinFlags = 0; + bool conflict = false; + int16_t modem = getActiveModem(); + int16_t state; + for(uint8_t i = 0; i <= 31; i++) { + // check if the bit is set + uint32_t irqBit = irq & (1UL << i); + if(!irqBit) { + continue; + } + + // if not, decode it + uint8_t dioNum = 0; // DIO pin number and register value to set (address and MSB/LSB can be inferred) + uint8_t regVal = 0; + if(modem == RADIOLIB_SX127X_LORA) { + switch(irqBit) { + case(RADIOLIB_SX127X_CLEAR_IRQ_FLAG_TX_DONE): + dioNum = 0; + regVal = RADIOLIB_SX127X_DIO0_PACK_PACKET_SENT; + break; + case(RADIOLIB_SX127X_CLEAR_IRQ_FLAG_RX_DONE): + dioNum = 0; + regVal = RADIOLIB_SX127X_DIO0_LORA_RX_DONE; + break; + case(RADIOLIB_SX127X_CLEAR_IRQ_FLAG_VALID_HEADER): + dioNum = 3; + regVal = RADIOLIB_SX127X_DIO3_LORA_VALID_HEADER; + break; + case(RADIOLIB_SX127X_CLEAR_IRQ_FLAG_PAYLOAD_CRC_ERROR): + dioNum = 3; + regVal = RADIOLIB_SX127X_DIO3_LORA_PAYLOAD_CRC_ERROR; + break; + case(RADIOLIB_SX127X_CLEAR_IRQ_FLAG_CAD_DONE): + dioNum = 0; + regVal = RADIOLIB_SX127X_DIO0_LORA_CAD_DONE; + break; + case(RADIOLIB_SX127X_CLEAR_IRQ_FLAG_CAD_DETECTED): + dioNum = 1; + regVal = RADIOLIB_SX127X_DIO1_LORA_CAD_DETECTED; + break; + case(RADIOLIB_SX127X_CLEAR_IRQ_FLAG_RX_TIMEOUT): + dioNum = 1; + regVal = RADIOLIB_SX127X_DIO1_LORA_RX_TIMEOUT; + break; + default: + return(RADIOLIB_ERR_UNSUPPORTED); + } + + } else if(modem == RADIOLIB_SX127X_FSK_OOK) { + switch(irqBit) { + case(RADIOLIB_SX127X_FLAG_PACKET_SENT << 8): + dioNum = 0; + regVal = RADIOLIB_SX127X_DIO0_PACK_PACKET_SENT; + break; + case(RADIOLIB_SX127X_FLAG_PAYLOAD_READY << 8): + dioNum = 0; + regVal = RADIOLIB_SX127X_DIO0_PACK_PAYLOAD_READY; + break; + case(RADIOLIB_SX127X_FLAG_PREAMBLE_DETECT << 0): + dioNum = 4; + regVal = RADIOLIB_SX127X_DIO4_PACK_RSSI_PREAMBLE_DETECT; + break; + case(RADIOLIB_SX127X_FLAG_SYNC_ADDRESS_MATCH << 0): + dioNum = 2; + regVal = RADIOLIB_SX127X_DIO2_PACK_SYNC_ADDRESS; + break; + case(RADIOLIB_SX127X_FLAG_TIMEOUT << 0): + dioNum = 2; + regVal = RADIOLIB_SX127X_DIO2_PACK_TIMEOUT; + break; + default: + return(RADIOLIB_ERR_UNSUPPORTED); + } + } + + // check if this DIO pin has been set already + if(usedPinFlags & (1UL << dioNum)) { + // uh oh, this pin is used! + RADIOLIB_DEBUG_PRINTLN("Unable to set IRQ %04x on DIO%d due to conflict!", irqBit, (int)dioNum); + conflict = true; + continue; + } + + // DIO pin is unused, set the flag and configure it + usedPinFlags |= (1UL << dioNum); + uint8_t addr = (dioNum > 3) ? RADIOLIB_SX127X_REG_DIO_MAPPING_2 : RADIOLIB_SX127X_REG_DIO_MAPPING_1; + uint8_t msb = 7 - 2*(dioNum % 4); + state = this->mod->SPIsetRegValue(addr, regVal, msb, msb - 1); + RADIOLIB_ASSERT(state); + } + + // if there was at least one conflict, this flag is set + if(conflict) { + return(RADIOLIB_ERR_INVALID_IRQ); + } + + return(state); +} + +int16_t SX127x::clearIrqFlags(uint32_t irq) { + int16_t modem = getActiveModem(); + if(modem == RADIOLIB_SX127X_LORA) { + this->mod->SPIwriteRegister(RADIOLIB_SX127X_REG_IRQ_FLAGS, (uint8_t)irq); + return(RADIOLIB_ERR_NONE); + + } else if(modem == RADIOLIB_SX127X_FSK_OOK) { + this->mod->SPIwriteRegister(RADIOLIB_SX127X_REG_IRQ_FLAGS_1, (uint8_t)irq); + this->mod->SPIwriteRegister(RADIOLIB_SX127X_REG_IRQ_FLAGS_2, (uint8_t)(irq >> 8)); + return(RADIOLIB_ERR_NONE); + } + + return(RADIOLIB_ERR_UNKNOWN); +} + +int16_t SX127x::setCrcFiltering(bool enable) { + this->crcOn = enable; + + if (enable == true) { + return(this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PACKET_CONFIG_1, RADIOLIB_SX127X_CRC_ON, 4, 4)); + } else { + return(this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PACKET_CONFIG_1, RADIOLIB_SX127X_CRC_OFF, 4, 4)); + } +} + +int16_t SX127x::setRSSIThreshold(float dbm) { + RADIOLIB_CHECK_RANGE(dbm, -127.5, 0, RADIOLIB_ERR_INVALID_RSSI_THRESHOLD); + + return this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_RSSI_THRESH, (uint8_t)(-2.0 * dbm), 7, 0); +} + +int16_t SX127x::setRSSIConfig(uint8_t smoothingSamples, int8_t offset) { + // check active modem + if(getActiveModem() != RADIOLIB_SX127X_FSK_OOK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // set mode to standby + int16_t state = standby(); + RADIOLIB_ASSERT(state); + + // check provided values + if(!(smoothingSamples <= 7)) { + return(RADIOLIB_ERR_INVALID_NUM_SAMPLES); + } + + RADIOLIB_CHECK_RANGE(offset, -16, 15, RADIOLIB_ERR_INVALID_RSSI_OFFSET); + + // calculate the two's complement + uint8_t offsetRaw = RADIOLIB_ABS(offset); + offsetRaw ^= 0x1F; + offsetRaw += 1; + offsetRaw &= 0x1F; + + // set new register values + state = this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_RSSI_CONFIG, offsetRaw << 3, 7, 3); + state |= this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_RSSI_CONFIG, smoothingSamples, 2, 0); + return(state); +} + +int16_t SX127x::setEncoding(uint8_t encoding) { + // check active modem + if(getActiveModem() != RADIOLIB_SX127X_FSK_OOK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // set encoding + switch(encoding) { + case RADIOLIB_ENCODING_NRZ: + return(this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PACKET_CONFIG_1, RADIOLIB_SX127X_DC_FREE_NONE, 6, 5)); + case RADIOLIB_ENCODING_MANCHESTER: + return(this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PACKET_CONFIG_1, RADIOLIB_SX127X_DC_FREE_MANCHESTER, 6, 5)); + case RADIOLIB_ENCODING_WHITENING: + return(this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PACKET_CONFIG_1, RADIOLIB_SX127X_DC_FREE_WHITENING, 6, 5)); + default: + return(RADIOLIB_ERR_INVALID_ENCODING); + } +} + +uint16_t SX127x::getIRQFlags() { + // check active modem + if(getActiveModem() == RADIOLIB_SX127X_LORA) { + // LoRa, just 8-bit value + return((uint16_t)this->mod->SPIreadRegister(RADIOLIB_SX127X_REG_IRQ_FLAGS)); + + } else { + // FSK, the IRQ flags are 16 bits in total + uint16_t flags = ((uint16_t)this->mod->SPIreadRegister(RADIOLIB_SX127X_REG_IRQ_FLAGS_2)) << 8; + flags |= (uint16_t)this->mod->SPIreadRegister(RADIOLIB_SX127X_REG_IRQ_FLAGS_1); + return(flags); + } + +} + +uint8_t SX127x::getModemStatus() { + // check active modem + if(getActiveModem() != RADIOLIB_SX127X_LORA) { + return(0x00); + } + + // read the register + return(this->mod->SPIreadRegister(RADIOLIB_SX127X_REG_MODEM_STAT)); +} + +void SX127x::setRfSwitchPins(uint32_t rxEn, uint32_t txEn) { + this->mod->setRfSwitchPins(rxEn, txEn); +} + +void SX127x::setRfSwitchTable(const uint32_t (&pins)[Module::RFSWITCH_MAX_PINS], const Module::RfSwitchMode_t table[]) { + this->mod->setRfSwitchTable(pins, table); +} + +uint8_t SX127x::randomByte() { + // check active modem + uint8_t rssiValueReg = RADIOLIB_SX127X_REG_RSSI_WIDEBAND; + if(getActiveModem() == RADIOLIB_SX127X_FSK_OOK) { + rssiValueReg = RADIOLIB_SX127X_REG_RSSI_VALUE_FSK; + } + + // set mode to Rx + setMode(RADIOLIB_SX127X_RX); + + // wait a bit for the RSSI reading to stabilise + this->mod->hal->delay(10); + + // read RSSI value 8 times, always keep just the least significant bit + uint8_t randByte = 0x00; + for(uint8_t i = 0; i < 8; i++) { + randByte |= ((this->mod->SPIreadRegister(rssiValueReg) & 0x01) << i); + } + + // set mode to standby + setMode(RADIOLIB_SX127X_STANDBY); + + return(randByte); +} + +int16_t SX127x::getChipVersion() { + return(this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_VERSION)); +} + +int8_t SX127x::getTempRaw() { + int8_t temp = 0; + uint8_t previousOpMode; + uint8_t ival; + + // save current Op Mode + previousOpMode = this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_OP_MODE); + + // check if we need to step out of LoRa mode first + if((previousOpMode & RADIOLIB_SX127X_LORA) == RADIOLIB_SX127X_LORA) { + this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_OP_MODE, (RADIOLIB_SX127X_LORA | RADIOLIB_SX127X_SLEEP)); + } + + // put device in FSK sleep + this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_OP_MODE, (RADIOLIB_SX127X_FSK_OOK | RADIOLIB_SX127X_SLEEP)); + + // put device in FSK RxSynth + this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_OP_MODE, (RADIOLIB_SX127X_FSK_OOK | RADIOLIB_SX127X_FSRX)); + + // enable temperature reading + this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_IMAGE_CAL, RADIOLIB_SX127X_TEMP_MONITOR_ON, 0, 0); + + // wait + this->mod->hal->delayMicroseconds(200); + + // disable temperature reading + this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_IMAGE_CAL, RADIOLIB_SX127X_TEMP_MONITOR_OFF, 0, 0); + + // put device in FSK sleep + this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_OP_MODE, (RADIOLIB_SX127X_FSK_OOK | RADIOLIB_SX127X_SLEEP)); + + // read temperature + ival = this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_TEMP); + + // convert very raw value + if((ival & 0x80) == 0x80) { + temp = 255 - ival; + } else { + temp = -1 * ival; + } + + // check if we need to step back into LoRa mode + if((previousOpMode & RADIOLIB_SX127X_LORA) == RADIOLIB_SX127X_LORA) { + this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_OP_MODE, (RADIOLIB_SX127X_LORA | RADIOLIB_SX127X_SLEEP)); + } + + // reload previous Op Mode + this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_OP_MODE, previousOpMode); + + return(temp); +} + +Module* SX127x::getMod() { + return(this->mod); +} + +int16_t SX127x::config() { + // turn off frequency hopping + int16_t state = this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_HOP_PERIOD, RADIOLIB_SX127X_HOP_PERIOD_OFF); + return(state); +} + +int16_t SX127x::configFSK() { + // set RSSI threshold + int16_t state = this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_RSSI_THRESH, RADIOLIB_SX127X_RSSI_THRESHOLD); + RADIOLIB_ASSERT(state); + + // reset FIFO flag + this->mod->SPIwriteRegister(RADIOLIB_SX127X_REG_IRQ_FLAGS_2, RADIOLIB_SX127X_FLAG_FIFO_OVERRUN); + + // set packet configuration + state = this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PACKET_CONFIG_1, RADIOLIB_SX127X_PACKET_VARIABLE | RADIOLIB_SX127X_DC_FREE_NONE | RADIOLIB_SX127X_CRC_ON | RADIOLIB_SX127X_CRC_AUTOCLEAR_ON | RADIOLIB_SX127X_ADDRESS_FILTERING_OFF | RADIOLIB_SX127X_CRC_WHITENING_TYPE_CCITT, 7, 0); + state |= this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PACKET_CONFIG_2, RADIOLIB_SX127X_DATA_MODE_PACKET | RADIOLIB_SX127X_IO_HOME_OFF, 6, 5); + RADIOLIB_ASSERT(state); + + // set FIFO threshold + state = this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_FIFO_THRESH, RADIOLIB_SX127X_TX_START_FIFO_NOT_EMPTY, 7, 7); + state |= this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_FIFO_THRESH, RADIOLIB_SX127X_FIFO_THRESH, 5, 0); + RADIOLIB_ASSERT(state); + + // disable Rx timeouts + state = this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_RX_TIMEOUT_1, RADIOLIB_SX127X_TIMEOUT_RX_RSSI_OFF); + state |= this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_RX_TIMEOUT_2, RADIOLIB_SX127X_TIMEOUT_RX_PREAMBLE_OFF); + state |= this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_RX_TIMEOUT_3, RADIOLIB_SX127X_TIMEOUT_SIGNAL_SYNC_OFF); + RADIOLIB_ASSERT(state); + + // enable preamble detector + state = this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PREAMBLE_DETECT, RADIOLIB_SX127X_PREAMBLE_DETECTOR_ON | RADIOLIB_SX127X_PREAMBLE_DETECTOR_2_BYTE | RADIOLIB_SX127X_PREAMBLE_DETECTOR_TOL); + + return(state); +} + +int16_t SX127x::setPacketMode(uint8_t mode, uint8_t len) { + // check packet length + if(len > RADIOLIB_SX127X_MAX_PACKET_LENGTH_FSK) { + return(RADIOLIB_ERR_PACKET_TOO_LONG); + } + + // check active modem + if(getActiveModem() != RADIOLIB_SX127X_FSK_OOK) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // set to fixed packet length + int16_t state = this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PACKET_CONFIG_1, mode, 7, 7); + RADIOLIB_ASSERT(state); + + // set length to register + state = this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_PAYLOAD_LENGTH_FSK, len); + RADIOLIB_ASSERT(state); + + // update cached value + this->packetLengthConfig = mode; + return(state); +} + +bool SX127x::findChip(const uint8_t* vers, uint8_t num) { + uint8_t i = 0; + bool flagFound = false; + while((i < 10) && !flagFound) { + // reset the module + reset(); + + // check version register + int16_t version = getChipVersion(); + for(uint8_t j = 0; j < num; j++) { + if(version == vers[j]) { + flagFound = true; + break; + } + } + + if(!flagFound) { + RADIOLIB_DEBUG_BASIC_PRINTLN("SX127x not found! (%d of 10 tries) RADIOLIB_SX127X_REG_VERSION == 0x%04X", i + 1, version); + this->mod->hal->delay(10); + i++; + } + + } + + return(flagFound); +} + +int16_t SX127x::setMode(uint8_t mode) { + uint8_t checkMask = 0xFF; + if((getActiveModem() == RADIOLIB_SX127X_FSK_OOK) && (mode == RADIOLIB_SX127X_RX)) { + // disable checking of RX bit in FSK RX mode, as it sometimes seem to fail (#276) + checkMask = 0xFE; + } + return(this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_OP_MODE, mode, 2, 0, 5, checkMask)); +} + +int16_t SX127x::getActiveModem() { + return(this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_OP_MODE, 7, 7)); +} + +int16_t SX127x::setActiveModem(uint8_t modem) { + // set mode to SLEEP + int16_t state = setMode(RADIOLIB_SX127X_SLEEP); + + // set modem + state |= this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_OP_MODE, modem, 7, 7, 5); + + // set mode to STANDBY + state |= setMode(RADIOLIB_SX127X_STANDBY); + return(state); +} + +void SX127x::clearFIFO(size_t count) { + while(count) { + this->mod->SPIreadRegister(RADIOLIB_SX127X_REG_FIFO); + count--; + } +} + +int16_t SX127x::invertIQ(bool enable) { + // check active modem + if(getActiveModem() != RADIOLIB_SX127X_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // Tx path inversion is swapped, because it seems that setting it according to the datsheet + // will actually lead to the wrong inversion. See https://github.com/jgromes/RadioLib/issues/778 + int16_t state; + if(enable) { + state = this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_INVERT_IQ, RADIOLIB_SX127X_INVERT_IQ_RXPATH_ON, 6, 6); + state |= this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_INVERT_IQ, RADIOLIB_SX127X_INVERT_IQ_TXPATH_OFF, 0, 0); + state |= this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_INVERT_IQ2, RADIOLIB_SX127X_IQ2_ENABLE); + } else { + state = this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_INVERT_IQ, RADIOLIB_SX127X_INVERT_IQ_RXPATH_OFF, 6, 6); + state |= this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_INVERT_IQ, RADIOLIB_SX127X_INVERT_IQ_TXPATH_ON, 0, 0); + state |= this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_INVERT_IQ2, RADIOLIB_SX127X_IQ2_DISABLE); + } + + return(state); +} + +int16_t SX127x::getModem(ModemType_t* modem) { + RADIOLIB_ASSERT_PTR(modem); + + int16_t packetType = getActiveModem(); + switch(packetType) { + case(RADIOLIB_SX127X_LORA): + *modem = ModemType_t::RADIOLIB_MODEM_LORA; + return(RADIOLIB_ERR_NONE); + case(RADIOLIB_SX127X_FSK_OOK): + *modem = ModemType_t::RADIOLIB_MODEM_FSK; + return(RADIOLIB_ERR_NONE); + } + + return(RADIOLIB_ERR_WRONG_MODEM); +} + +#if !RADIOLIB_EXCLUDE_DIRECT_RECEIVE +void SX127x::setDirectAction(void (*func)(void)) { + setDio1Action(func, this->mod->hal->GpioInterruptRising); +} + +void SX127x::readBit(uint32_t pin) { + updateDirectBuffer((uint8_t)this->mod->hal->digitalRead(pin)); +} +#endif + +int16_t SX127x::setFHSSHoppingPeriod(uint8_t freqHoppingPeriod) { + return(this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_HOP_PERIOD, freqHoppingPeriod)); +} + +uint8_t SX127x::getFHSSHoppingPeriod(void) { + return(this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_HOP_PERIOD)); +} + +uint8_t SX127x::getFHSSChannel(void) { + return(this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_HOP_CHANNEL, 5, 0)); +} + +void SX127x::clearFHSSInt(void) { + int16_t modem = getActiveModem(); + if(modem == RADIOLIB_SX127X_LORA) { + this->mod->SPIwriteRegister(RADIOLIB_SX127X_REG_IRQ_FLAGS, RADIOLIB_SX127X_CLEAR_IRQ_FLAG_FHSS_CHANGE_CHANNEL); + } else if(modem == RADIOLIB_SX127X_FSK_OOK) { + return; //These are not the interrupts you are looking for + } +} + +int16_t SX127x::setDIOMapping(uint32_t pin, uint32_t value) { + if (pin > 5) + return RADIOLIB_ERR_INVALID_DIO_PIN; + + if (pin < 4) + return(this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_DIO_MAPPING_1, value, 7 - 2 * pin, 6 - 2 * pin)); + else + return(this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_DIO_MAPPING_2, value, 15 - 2 * pin, 14 - 2 * pin)); +} + +int16_t SX127x::setDIOPreambleDetect(bool usePreambleDetect) { + return this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_DIO_MAPPING_2, (usePreambleDetect) ? RADIOLIB_SX127X_DIO_MAP_PREAMBLE_DETECT : RADIOLIB_SX127X_DIO_MAP_RSSI, 0, 0); +} + +float SX127x::getRSSI(bool packet, bool skipReceive, int16_t offset) { + if(getActiveModem() == RADIOLIB_SX127X_LORA) { + if(packet) { + // LoRa packet mode, get RSSI of the last packet + float lastPacketRSSI = offset + this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_PKT_RSSI_VALUE); + + // spread-spectrum modulation signal can be received below noise floor + // check last packet SNR and if it's less than 0, add it to reported RSSI to get the correct value + float lastPacketSNR = SX127x::getSNR(); + if(lastPacketSNR < 0.0) { + lastPacketRSSI += lastPacketSNR; + } + return(lastPacketRSSI); + + } else { + // LoRa instant, get current RSSI + float currentRSSI = offset + this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_RSSI_VALUE); + return(currentRSSI); + } + + } else { + // for FSK, there is no packet RSSI + + // enable listen mode + if(!skipReceive) { + startReceive(); + } + + // read the value for FSK + float rssi = (float)this->mod->SPIgetRegValue(RADIOLIB_SX127X_REG_RSSI_VALUE_FSK) / -2.0; + + // set mode back to standby + if(!skipReceive) { + standby(); + } + + // return the value + return(rssi); + } +} + +int16_t SX127x::setLowBatteryThreshold(int8_t level, uint32_t pin) { + // check disable + if(level < 0) { + return(this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_LOW_BAT, RADIOLIB_SX127X_LOW_BAT_OFF, 3, 3)); + } + + // enable detector and set the threshold + int16_t state = this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_LOW_BAT, RADIOLIB_SX127X_LOW_BAT_ON | level, 3, 0); + RADIOLIB_ASSERT(state); + + // set DIO mapping + switch(pin) { + case(0): + return(this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_DIO_MAPPING_1, RADIOLIB_SX127X_DIO0_PACK_TEMP_CHANGE_LOW_BAT, 7, 6)); + case(3): + return(this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_DIO_MAPPING_1, RADIOLIB_SX127X_DIO3_CONT_TEMP_CHANGE_LOW_BAT, 1, 0)); + case(4): + return(this->mod->SPIsetRegValue(RADIOLIB_SX127X_REG_DIO_MAPPING_2, RADIOLIB_SX127X_DIO4_PACK_TEMP_CHANGE_LOW_BAT, 7, 6)); + } + return(RADIOLIB_ERR_INVALID_DIO_PIN); +} + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX127x.h b/software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX127x.h new file mode 100644 index 000000000..c54a549f6 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/SX127x/SX127x.h @@ -0,0 +1,1282 @@ +#if !defined(_RADIOLIB_SX127X_H) +#define _RADIOLIB_SX127X_H + +#include "../../TypeDef.h" + +#if !RADIOLIB_EXCLUDE_SX127X + +#include "../../Module.h" + +#include "../../protocols/PhysicalLayer/PhysicalLayer.h" + +// SX127x physical layer properties +#define RADIOLIB_SX127X_FREQUENCY_STEP_SIZE 61.03515625 +#define RADIOLIB_SX127X_MAX_PACKET_LENGTH 255 +#define RADIOLIB_SX127X_MAX_PACKET_LENGTH_FSK 64 +#define RADIOLIB_SX127X_CRYSTAL_FREQ 32.0 +#define RADIOLIB_SX127X_DIV_EXPONENT 19 + +// SX127x series common LoRa registers +#define RADIOLIB_SX127X_REG_FIFO 0x00 +#define RADIOLIB_SX127X_REG_OP_MODE 0x01 +#define RADIOLIB_SX127X_REG_FRF_MSB 0x06 +#define RADIOLIB_SX127X_REG_FRF_MID 0x07 +#define RADIOLIB_SX127X_REG_FRF_LSB 0x08 +#define RADIOLIB_SX127X_REG_PA_CONFIG 0x09 +#define RADIOLIB_SX127X_REG_PA_RAMP 0x0A +#define RADIOLIB_SX127X_REG_OCP 0x0B +#define RADIOLIB_SX127X_REG_LNA 0x0C +#define RADIOLIB_SX127X_REG_FIFO_ADDR_PTR 0x0D +#define RADIOLIB_SX127X_REG_FIFO_TX_BASE_ADDR 0x0E +#define RADIOLIB_SX127X_REG_FIFO_RX_BASE_ADDR 0x0F +#define RADIOLIB_SX127X_REG_FIFO_RX_CURRENT_ADDR 0x10 +#define RADIOLIB_SX127X_REG_IRQ_FLAGS_MASK 0x11 +#define RADIOLIB_SX127X_REG_IRQ_FLAGS 0x12 +#define RADIOLIB_SX127X_REG_RX_NB_BYTES 0x13 +#define RADIOLIB_SX127X_REG_RX_HEADER_CNT_VALUE_MSB 0x14 +#define RADIOLIB_SX127X_REG_RX_HEADER_CNT_VALUE_LSB 0x15 +#define RADIOLIB_SX127X_REG_RX_PACKET_CNT_VALUE_MSB 0x16 +#define RADIOLIB_SX127X_REG_RX_PACKET_CNT_VALUE_LSB 0x17 +#define RADIOLIB_SX127X_REG_MODEM_STAT 0x18 +#define RADIOLIB_SX127X_REG_PKT_SNR_VALUE 0x19 +#define RADIOLIB_SX127X_REG_PKT_RSSI_VALUE 0x1A +#define RADIOLIB_SX127X_REG_RSSI_VALUE 0x1B +#define RADIOLIB_SX127X_REG_HOP_CHANNEL 0x1C +#define RADIOLIB_SX127X_REG_MODEM_CONFIG_1 0x1D +#define RADIOLIB_SX127X_REG_MODEM_CONFIG_2 0x1E +#define RADIOLIB_SX127X_REG_SYMB_TIMEOUT_LSB 0x1F +#define RADIOLIB_SX127X_REG_PREAMBLE_MSB 0x20 +#define RADIOLIB_SX127X_REG_PREAMBLE_LSB 0x21 +#define RADIOLIB_SX127X_REG_PAYLOAD_LENGTH 0x22 +#define RADIOLIB_SX127X_REG_MAX_PAYLOAD_LENGTH 0x23 +#define RADIOLIB_SX127X_REG_HOP_PERIOD 0x24 +#define RADIOLIB_SX127X_REG_FIFO_RX_BYTE_ADDR 0x25 +#define RADIOLIB_SX127X_REG_FEI_MSB 0x28 +#define RADIOLIB_SX127X_REG_FEI_MID 0x29 +#define RADIOLIB_SX127X_REG_FEI_LSB 0x2A +#define RADIOLIB_SX127X_REG_RSSI_WIDEBAND 0x2C +#define RADIOLIB_SX127X_REG_DETECT_OPTIMIZE 0x31 +#define RADIOLIB_SX127X_REG_INVERT_IQ 0x33 +#define RADIOLIB_SX127X_REG_DETECTION_THRESHOLD 0x37 +#define RADIOLIB_SX127X_REG_SYNC_WORD 0x39 +#define RADIOLIB_SX127X_REG_INVERT_IQ2 0x3B +#define RADIOLIB_SX127X_REG_DIO_MAPPING_1 0x40 +#define RADIOLIB_SX127X_REG_DIO_MAPPING_2 0x41 +#define RADIOLIB_SX127X_REG_VERSION 0x42 + +// SX127x common LoRa modem settings +// RADIOLIB_SX127X_REG_OP_MODE MSB LSB DESCRIPTION +#define RADIOLIB_SX127X_FSK_OOK 0b00000000 // 7 7 FSK/OOK mode +#define RADIOLIB_SX127X_LORA 0b10000000 // 7 7 LoRa mode +#define RADIOLIB_SX127X_ACCESS_SHARED_REG_OFF 0b00000000 // 6 6 access LoRa registers (0x0D:0x3F) in LoRa mode +#define RADIOLIB_SX127X_ACCESS_SHARED_REG_ON 0b01000000 // 6 6 access FSK registers (0x0D:0x3F) in LoRa mode +#define RADIOLIB_SX127X_SLEEP 0b00000000 // 2 0 sleep +#define RADIOLIB_SX127X_STANDBY 0b00000001 // 2 0 standby +#define RADIOLIB_SX127X_FSTX 0b00000010 // 2 0 frequency synthesis TX +#define RADIOLIB_SX127X_TX 0b00000011 // 2 0 transmit +#define RADIOLIB_SX127X_FSRX 0b00000100 // 2 0 frequency synthesis RX +#define RADIOLIB_SX127X_RXCONTINUOUS 0b00000101 // 2 0 receive continuous +#define RADIOLIB_SX127X_RXSINGLE 0b00000110 // 2 0 receive single +#define RADIOLIB_SX127X_CAD 0b00000111 // 2 0 channel activity detection + +// RADIOLIB_SX127X_REG_PA_CONFIG +#define RADIOLIB_SX127X_PA_SELECT_RFO 0b00000000 // 7 7 RFO pin output, power limited to +14 dBm +#define RADIOLIB_SX127X_PA_SELECT_BOOST 0b10000000 // 7 7 PA_BOOST pin output, power limited to +20 dBm +#define RADIOLIB_SX127X_OUTPUT_POWER 0b00001111 // 3 0 output power: P_out = 2 + OUTPUT_POWER [dBm] for PA_SELECT_BOOST + // P_out = -1 + OUTPUT_POWER [dBm] for PA_SELECT_RFO + +// RADIOLIB_SX127X_REG_OCP +#define RADIOLIB_SX127X_OCP_OFF 0b00000000 // 5 5 PA overload current protection disabled +#define RADIOLIB_SX127X_OCP_ON 0b00100000 // 5 5 PA overload current protection enabled +#define RADIOLIB_SX127X_OCP_TRIM 0b00001011 // 4 0 OCP current: I_max(OCP_TRIM = 0b1011) = 100 mA + +// RADIOLIB_SX127X_REG_LNA +#define RADIOLIB_SX127X_LNA_GAIN_1 0b00100000 // 7 5 LNA gain setting: max gain +#define RADIOLIB_SX127X_LNA_GAIN_2 0b01000000 // 7 5 . +#define RADIOLIB_SX127X_LNA_GAIN_3 0b01100000 // 7 5 . +#define RADIOLIB_SX127X_LNA_GAIN_4 0b10000000 // 7 5 . +#define RADIOLIB_SX127X_LNA_GAIN_5 0b10100000 // 7 5 . +#define RADIOLIB_SX127X_LNA_GAIN_6 0b11000000 // 7 5 min gain +#define RADIOLIB_SX127X_LNA_BOOST_OFF 0b00000000 // 1 0 default LNA current +#define RADIOLIB_SX127X_LNA_BOOST_ON 0b00000011 // 1 0 150% LNA current + +// RADIOLIB_SX127X_REG_MODEM_CONFIG_2 +#define RADIOLIB_SX127X_SF_6 0b01100000 // 7 4 spreading factor: 64 chips/bit +#define RADIOLIB_SX127X_SF_7 0b01110000 // 7 4 128 chips/bit +#define RADIOLIB_SX127X_SF_8 0b10000000 // 7 4 256 chips/bit +#define RADIOLIB_SX127X_SF_9 0b10010000 // 7 4 512 chips/bit +#define RADIOLIB_SX127X_SF_10 0b10100000 // 7 4 1024 chips/bit +#define RADIOLIB_SX127X_SF_11 0b10110000 // 7 4 2048 chips/bit +#define RADIOLIB_SX127X_SF_12 0b11000000 // 7 4 4096 chips/bit +#define RADIOLIB_SX127X_TX_MODE_SINGLE 0b00000000 // 3 3 single TX +#define RADIOLIB_SX127X_TX_MODE_CONT 0b00001000 // 3 3 continuous TX +#define RADIOLIB_SX127X_RX_TIMEOUT_MSB 0b00000000 // 1 0 + +// RADIOLIB_SX127X_REG_SYMB_TIMEOUT_LSB +#define RADIOLIB_SX127X_RX_TIMEOUT_LSB 0b01100100 // 7 0 10-bit RX operation timeout + +// RADIOLIB_SX127X_REG_PREAMBLE_MSB + REG_PREAMBLE_LSB +#define RADIOLIB_SX127X_PREAMBLE_LENGTH_MSB 0b00000000 // 7 0 2-byte preamble length setting: l_P = PREAMBLE_LENGTH + 4.25 +#define RADIOLIB_SX127X_PREAMBLE_LENGTH_LSB 0b00001000 // 7 0 where l_p = preamble length + +// RADIOLIB_SX127X_REG_DETECT_OPTIMIZE +#define RADIOLIB_SX127X_DETECT_OPTIMIZE_SF_6 0b00000101 // 2 0 SF6 detection optimization +#define RADIOLIB_SX127X_DETECT_OPTIMIZE_SF_7_12 0b00000011 // 2 0 SF7 to SF12 detection optimization + +// RADIOLIB_SX127X_REG_INVERT_IQ +#define RADIOLIB_SX127X_INVERT_IQ_RXPATH_ON 0b01000000 // 6 6 I and Q signals are inverted +#define RADIOLIB_SX127X_INVERT_IQ_RXPATH_OFF 0b00000000 // 6 6 normal mode +#define RADIOLIB_SX127X_INVERT_IQ_TXPATH_ON 0b00000001 // 0 0 I and Q signals are inverted +#define RADIOLIB_SX127X_INVERT_IQ_TXPATH_OFF 0b00000000 // 0 0 normal mode + +// RADIOLIB_SX127X_REG_DETECTION_THRESHOLD +#define RADIOLIB_SX127X_DETECTION_THRESHOLD_SF_6 0b00001100 // 7 0 SF6 detection threshold +#define RADIOLIB_SX127X_DETECTION_THRESHOLD_SF_7_12 0b00001010 // 7 0 SF7 to SF12 detection threshold + +// RADIOLIB_SX127X_REG_PA_DAC +#define RADIOLIB_SX127X_PA_BOOST_OFF 0b00000100 // 2 0 PA_BOOST disabled +#define RADIOLIB_SX127X_PA_BOOST_ON 0b00000111 // 2 0 +20 dBm on PA_BOOST when OUTPUT_POWER = 0b1111 + +// RADIOLIB_SX127X_REG_HOP_PERIOD +#define RADIOLIB_SX127X_HOP_PERIOD_OFF 0b00000000 // 7 0 number of periods between frequency hops; 0 = disabled +#define RADIOLIB_SX127X_HOP_PERIOD_MAX 0b11111111 // 7 0 + +// RADIOLIB_SX127X_REG_IRQ_FLAGS +#define RADIOLIB_SX127X_CLEAR_IRQ_FLAG_RX_TIMEOUT 0b10000000 // 7 7 timeout +#define RADIOLIB_SX127X_CLEAR_IRQ_FLAG_RX_DONE 0b01000000 // 6 6 packet reception complete +#define RADIOLIB_SX127X_CLEAR_IRQ_FLAG_PAYLOAD_CRC_ERROR 0b00100000 // 5 5 payload CRC error +#define RADIOLIB_SX127X_CLEAR_IRQ_FLAG_VALID_HEADER 0b00010000 // 4 4 valid header received +#define RADIOLIB_SX127X_CLEAR_IRQ_FLAG_TX_DONE 0b00001000 // 3 3 payload transmission complete +#define RADIOLIB_SX127X_CLEAR_IRQ_FLAG_CAD_DONE 0b00000100 // 2 2 CAD complete +#define RADIOLIB_SX127X_CLEAR_IRQ_FLAG_FHSS_CHANGE_CHANNEL 0b00000010 // 1 1 FHSS change channel +#define RADIOLIB_SX127X_CLEAR_IRQ_FLAG_CAD_DETECTED 0b00000001 // 0 0 valid LoRa signal detected during CAD operation + +// RADIOLIB_SX127X_REG_IRQ_FLAGS_MASK +#define RADIOLIB_SX127X_MASK_IRQ_FLAG_RX_TIMEOUT 0b01111111 // 7 7 timeout +#define RADIOLIB_SX127X_MASK_IRQ_FLAG_RX_DONE 0b10111111 // 6 6 packet reception complete +#define RADIOLIB_SX127X_MASK_IRQ_FLAG_PAYLOAD_CRC_ERROR 0b11011111 // 5 5 payload CRC error +#define RADIOLIB_SX127X_MASK_IRQ_FLAG_VALID_HEADER 0b11101111 // 4 4 valid header received +#define RADIOLIB_SX127X_MASK_IRQ_FLAG_TX_DONE 0b11110111 // 3 3 payload transmission complete +#define RADIOLIB_SX127X_MASK_IRQ_FLAG_CAD_DONE 0b11111011 // 2 2 CAD complete +#define RADIOLIB_SX127X_MASK_IRQ_FLAG_FHSS_CHANGE_CHANNEL 0b11111101 // 1 1 FHSS change channel +#define RADIOLIB_SX127X_MASK_IRQ_FLAG_CAD_DETECTED 0b11111110 // 0 0 valid LoRa signal detected during CAD operation + +// RADIOLIB_SX127X_REG_FIFO_TX_BASE_ADDR +#define RADIOLIB_SX127X_FIFO_TX_BASE_ADDR_MAX 0b00000000 // 7 0 allocate the entire FIFO buffer for TX only + +// RADIOLIB_SX127X_REG_FIFO_RX_BASE_ADDR +#define RADIOLIB_SX127X_FIFO_RX_BASE_ADDR_MAX 0b00000000 // 7 0 allocate the entire FIFO buffer for RX only + +// RADIOLIB_SX127X_REG_SYNC_WORD +#define RADIOLIB_SX127X_SYNC_WORD 0x12 // 7 0 default LoRa sync word +#define RADIOLIB_SX127X_SYNC_WORD_LORAWAN 0x34 // 7 0 sync word reserved for LoRaWAN networks + +// RADIOLIB_SX127X_REG_INVERT_IQ2 +#define RADIOLIB_SX127X_IQ2_ENABLE 0x19 // 7 0 enable optimize for inverted IQ +#define RADIOLIB_SX127X_IQ2_DISABLE 0x1D // 7 0 reset optimize for inverted IQ + +// SX127x series common FSK registers +// NOTE: FSK register names that are conflicting with LoRa registers are marked with "_FSK" suffix +#define RADIOLIB_SX127X_REG_BITRATE_MSB 0x02 +#define RADIOLIB_SX127X_REG_BITRATE_LSB 0x03 +#define RADIOLIB_SX127X_REG_FDEV_MSB 0x04 +#define RADIOLIB_SX127X_REG_FDEV_LSB 0x05 +#define RADIOLIB_SX127X_REG_RX_CONFIG 0x0D +#define RADIOLIB_SX127X_REG_RSSI_CONFIG 0x0E +#define RADIOLIB_SX127X_REG_RSSI_COLLISION 0x0F +#define RADIOLIB_SX127X_REG_RSSI_THRESH 0x10 +#define RADIOLIB_SX127X_REG_RSSI_VALUE_FSK 0x11 +#define RADIOLIB_SX127X_REG_RX_BW 0x12 +#define RADIOLIB_SX127X_REG_AFC_BW 0x13 +#define RADIOLIB_SX127X_REG_OOK_PEAK 0x14 +#define RADIOLIB_SX127X_REG_OOK_FIX 0x15 +#define RADIOLIB_SX127X_REG_OOK_AVG 0x16 +#define RADIOLIB_SX127X_REG_AFC_FEI 0x1A +#define RADIOLIB_SX127X_REG_AFC_MSB 0x1B +#define RADIOLIB_SX127X_REG_AFC_LSB 0x1C +#define RADIOLIB_SX127X_REG_FEI_MSB_FSK 0x1D +#define RADIOLIB_SX127X_REG_FEI_LSB_FSK 0x1E +#define RADIOLIB_SX127X_REG_PREAMBLE_DETECT 0x1F +#define RADIOLIB_SX127X_REG_RX_TIMEOUT_1 0x20 +#define RADIOLIB_SX127X_REG_RX_TIMEOUT_2 0x21 +#define RADIOLIB_SX127X_REG_RX_TIMEOUT_3 0x22 +#define RADIOLIB_SX127X_REG_RX_DELAY 0x23 +#define RADIOLIB_SX127X_REG_OSC 0x24 +#define RADIOLIB_SX127X_REG_PREAMBLE_MSB_FSK 0x25 +#define RADIOLIB_SX127X_REG_PREAMBLE_LSB_FSK 0x26 +#define RADIOLIB_SX127X_REG_SYNC_CONFIG 0x27 +#define RADIOLIB_SX127X_REG_SYNC_VALUE_1 0x28 +#define RADIOLIB_SX127X_REG_SYNC_VALUE_2 0x29 +#define RADIOLIB_SX127X_REG_SYNC_VALUE_3 0x2A +#define RADIOLIB_SX127X_REG_SYNC_VALUE_4 0x2B +#define RADIOLIB_SX127X_REG_SYNC_VALUE_5 0x2C +#define RADIOLIB_SX127X_REG_SYNC_VALUE_6 0x2D +#define RADIOLIB_SX127X_REG_SYNC_VALUE_7 0x2E +#define RADIOLIB_SX127X_REG_SYNC_VALUE_8 0x2F +#define RADIOLIB_SX127X_REG_PACKET_CONFIG_1 0x30 +#define RADIOLIB_SX127X_REG_PACKET_CONFIG_2 0x31 +#define RADIOLIB_SX127X_REG_PAYLOAD_LENGTH_FSK 0x32 +#define RADIOLIB_SX127X_REG_NODE_ADRS 0x33 +#define RADIOLIB_SX127X_REG_BROADCAST_ADRS 0x34 +#define RADIOLIB_SX127X_REG_FIFO_THRESH 0x35 +#define RADIOLIB_SX127X_REG_SEQ_CONFIG_1 0x36 +#define RADIOLIB_SX127X_REG_SEQ_CONFIG_2 0x37 +#define RADIOLIB_SX127X_REG_TIMER_RESOL 0x38 +#define RADIOLIB_SX127X_REG_TIMER1_COEF 0x39 +#define RADIOLIB_SX127X_REG_TIMER2_COEF 0x3A +#define RADIOLIB_SX127X_REG_IMAGE_CAL 0x3B +#define RADIOLIB_SX127X_REG_TEMP 0x3C +#define RADIOLIB_SX127X_REG_LOW_BAT 0x3D +#define RADIOLIB_SX127X_REG_IRQ_FLAGS_1 0x3E +#define RADIOLIB_SX127X_REG_IRQ_FLAGS_2 0x3F + +// SX127x common FSK modem settings +// RADIOLIB_SX127X_REG_OP_MODE +#define RADIOLIB_SX127X_MODULATION_FSK 0b00000000 // 6 5 FSK modulation scheme +#define RADIOLIB_SX127X_MODULATION_OOK 0b00100000 // 6 5 OOK modulation scheme +#define RADIOLIB_SX127X_RX 0b00000101 // 2 0 receiver mode + +// RADIOLIB_SX127X_REG_BITRATE_MSB + SX127X_REG_BITRATE_LSB +#define RADIOLIB_SX127X_BITRATE_MSB 0x1A // 7 0 bit rate setting: BitRate = F(XOSC)/(BITRATE + BITRATE_FRAC/16) +#define RADIOLIB_SX127X_BITRATE_LSB 0x0B // 7 0 default value: 4.8 kbps + +// RADIOLIB_SX127X_REG_FDEV_MSB + SX127X_REG_FDEV_LSB +#define RADIOLIB_SX127X_FDEV_MSB 0x00 // 5 0 frequency deviation: Fdev = Fstep * FDEV +#define RADIOLIB_SX127X_FDEV_LSB 0x52 // 7 0 default value: 5 kHz + +// RADIOLIB_SX127X_REG_RX_CONFIG +#define RADIOLIB_SX127X_RESTART_RX_ON_COLLISION_OFF 0b00000000 // 7 7 automatic receiver restart disabled (default) +#define RADIOLIB_SX127X_RESTART_RX_ON_COLLISION_ON 0b10000000 // 7 7 automatically restart receiver if it gets saturated or on packet collision +#define RADIOLIB_SX127X_RESTART_RX_WITHOUT_PLL_LOCK 0b01000000 // 6 6 manually restart receiver without frequency change +#define RADIOLIB_SX127X_RESTART_RX_WITH_PLL_LOCK 0b00100000 // 5 5 manually restart receiver with frequency change +#define RADIOLIB_SX127X_AFC_AUTO_OFF 0b00000000 // 4 4 no AFC performed (default) +#define RADIOLIB_SX127X_AFC_AUTO_ON 0b00010000 // 4 4 AFC performed at each receiver startup +#define RADIOLIB_SX127X_AGC_AUTO_OFF 0b00000000 // 3 3 LNA gain set manually by register +#define RADIOLIB_SX127X_AGC_AUTO_ON 0b00001000 // 3 3 LNA gain controlled by AGC +#define RADIOLIB_SX127X_RX_TRIGGER_NONE 0b00000000 // 2 0 receiver startup at: none +#define RADIOLIB_SX127X_RX_TRIGGER_RSSI_INTERRUPT 0b00000001 // 2 0 RSSI interrupt +#define RADIOLIB_SX127X_RX_TRIGGER_PREAMBLE_DETECT 0b00000110 // 2 0 preamble detected +#define RADIOLIB_SX127X_RX_TRIGGER_BOTH 0b00000111 // 2 0 RSSI interrupt and preamble detected + +// RADIOLIB_SX127X_REG_RSSI_CONFIG +#define RADIOLIB_SX127X_RSSI_SMOOTHING_SAMPLES_2 0b00000000 // 2 0 number of samples for RSSI average: 2 +#define RADIOLIB_SX127X_RSSI_SMOOTHING_SAMPLES_4 0b00000001 // 2 0 4 +#define RADIOLIB_SX127X_RSSI_SMOOTHING_SAMPLES_8 0b00000010 // 2 0 8 (default) +#define RADIOLIB_SX127X_RSSI_SMOOTHING_SAMPLES_16 0b00000011 // 2 0 16 +#define RADIOLIB_SX127X_RSSI_SMOOTHING_SAMPLES_32 0b00000100 // 2 0 32 +#define RADIOLIB_SX127X_RSSI_SMOOTHING_SAMPLES_64 0b00000101 // 2 0 64 +#define RADIOLIB_SX127X_RSSI_SMOOTHING_SAMPLES_128 0b00000110 // 2 0 128 +#define RADIOLIB_SX127X_RSSI_SMOOTHING_SAMPLES_256 0b00000111 // 2 0 256 + +// RADIOLIB_SX127X_REG_RSSI_COLLISION +#define RADIOLIB_SX127X_RSSI_COLLISION_THRESHOLD 0x0A // 7 0 RSSI threshold in dB that will be considered a collision, default value: 10 dB + +// RADIOLIB_SX127X_REG_RSSI_THRESH +#define RADIOLIB_SX127X_RSSI_THRESHOLD 0xFF // 7 0 RSSI threshold that will trigger RSSI interrupt, RssiThreshold = RSSI_THRESHOLD / 2 [dBm] + +// RADIOLIB_SX127X_REG_RX_BW +#define RADIOLIB_SX127X_RX_BW_MANT_16 0b00000000 // 4 3 channel filter bandwidth: RxBw = F(XOSC) / (RxBwMant * 2^(RxBwExp + 2)) [kHz] +#define RADIOLIB_SX127X_RX_BW_MANT_20 0b00001000 // 4 3 +#define RADIOLIB_SX127X_RX_BW_MANT_24 0b00010000 // 4 3 default RxBwMant parameter +#define RADIOLIB_SX127X_RX_BW_EXP 0b00000101 // 2 0 default RxBwExp parameter + +// RADIOLIB_SX127X_REG_AFC_BW +#define RADIOLIB_SX127X_RX_BW_MANT_AFC 0b00001000 // 4 3 default RxBwMant parameter used during AFC +#define RADIOLIB_SX127X_RX_BW_EXP_AFC 0b00000011 // 2 0 default RxBwExp parameter used during AFC + +// RADIOLIB_SX127X_REG_OOK_PEAK +#define RADIOLIB_SX127X_BIT_SYNC_OFF 0b00000000 // 5 5 bit synchronizer disabled (not allowed in packet mode) +#define RADIOLIB_SX127X_BIT_SYNC_ON 0b00100000 // 5 5 bit synchronizer enabled (default) +#define RADIOLIB_SX127X_OOK_THRESH_FIXED 0b00000000 // 4 3 OOK threshold type: fixed value +#define RADIOLIB_SX127X_OOK_THRESH_PEAK 0b00001000 // 4 3 peak mode (default) +#define RADIOLIB_SX127X_OOK_THRESH_AVERAGE 0b00010000 // 4 3 average mode +#define RADIOLIB_SX127X_OOK_PEAK_THRESH_STEP_0_5_DB 0b00000000 // 2 0 OOK demodulator step size: 0.5 dB (default) +#define RADIOLIB_SX127X_OOK_PEAK_THRESH_STEP_1_0_DB 0b00000001 // 2 0 1.0 dB +#define RADIOLIB_SX127X_OOK_PEAK_THRESH_STEP_1_5_DB 0b00000010 // 2 0 1.5 dB +#define RADIOLIB_SX127X_OOK_PEAK_THRESH_STEP_2_0_DB 0b00000011 // 2 0 2.0 dB +#define RADIOLIB_SX127X_OOK_PEAK_THRESH_STEP_3_0_DB 0b00000100 // 2 0 3.0 dB +#define RADIOLIB_SX127X_OOK_PEAK_THRESH_STEP_4_0_DB 0b00000101 // 2 0 4.0 dB +#define RADIOLIB_SX127X_OOK_PEAK_THRESH_STEP_5_0_DB 0b00000110 // 2 0 5.0 dB +#define RADIOLIB_SX127X_OOK_PEAK_THRESH_STEP_6_0_DB 0b00000111 // 2 0 6.0 dB + +// RADIOLIB_SX127X_REG_OOK_FIX +#define RADIOLIB_SX127X_OOK_FIXED_THRESHOLD 0x0C // 7 0 default fixed threshold for OOK data slicer + +// RADIOLIB_SX127X_REG_OOK_AVG +#define RADIOLIB_SX127X_OOK_PEAK_THRESH_DEC_1_1_CHIP 0b00000000 // 7 5 OOK demodulator step period: once per chip (default) +#define RADIOLIB_SX127X_OOK_PEAK_THRESH_DEC_1_2_CHIP 0b00100000 // 7 5 once every 2 chips +#define RADIOLIB_SX127X_OOK_PEAK_THRESH_DEC_1_4_CHIP 0b01000000 // 7 5 once every 4 chips +#define RADIOLIB_SX127X_OOK_PEAK_THRESH_DEC_1_8_CHIP 0b01100000 // 7 5 once every 8 chips +#define RADIOLIB_SX127X_OOK_PEAK_THRESH_DEC_2_1_CHIP 0b10000000 // 7 5 2 times per chip +#define RADIOLIB_SX127X_OOK_PEAK_THRESH_DEC_4_1_CHIP 0b10100000 // 7 5 4 times per chip +#define RADIOLIB_SX127X_OOK_PEAK_THRESH_DEC_8_1_CHIP 0b11000000 // 7 5 8 times per chip +#define RADIOLIB_SX127X_OOK_PEAK_THRESH_DEC_16_1_CHIP 0b11100000 // 7 5 16 times per chip +#define RADIOLIB_SX127X_OOK_AVERAGE_OFFSET_0_DB 0b00000000 // 3 2 OOK average threshold offset: 0.0 dB (default) +#define RADIOLIB_SX127X_OOK_AVERAGE_OFFSET_2_DB 0b00000100 // 3 2 2.0 dB +#define RADIOLIB_SX127X_OOK_AVERAGE_OFFSET_4_DB 0b00001000 // 3 2 4.0 dB +#define RADIOLIB_SX127X_OOK_AVERAGE_OFFSET_6_DB 0b00001100 // 3 2 6.0 dB +#define RADIOLIB_SX127X_OOK_AVG_THRESH_FILT_32_PI 0b00000000 // 1 0 OOK average filter coefficient: chip rate / 32*pi +#define RADIOLIB_SX127X_OOK_AVG_THRESH_FILT_8_PI 0b00000001 // 1 0 chip rate / 8*pi +#define RADIOLIB_SX127X_OOK_AVG_THRESH_FILT_4_PI 0b00000010 // 1 0 chip rate / 4*pi (default) +#define RADIOLIB_SX127X_OOK_AVG_THRESH_FILT_2_PI 0b00000011 // 1 0 chip rate / 2*pi + +// RADIOLIB_SX127X_REG_AFC_FEI +#define RADIOLIB_SX127X_AGC_START 0b00010000 // 4 4 manually start AGC sequence +#define RADIOLIB_SX127X_AFC_CLEAR 0b00000010 // 1 1 manually clear AFC register +#define RADIOLIB_SX127X_AFC_AUTO_CLEAR_OFF 0b00000000 // 0 0 AFC register will not be cleared at the start of AFC (default) +#define RADIOLIB_SX127X_AFC_AUTO_CLEAR_ON 0b00000001 // 0 0 AFC register will be cleared at the start of AFC + +// RADIOLIB_SX127X_REG_PREAMBLE_DETECT +#define RADIOLIB_SX127X_PREAMBLE_DETECTOR_OFF 0b00000000 // 7 7 preamble detection disabled +#define RADIOLIB_SX127X_PREAMBLE_DETECTOR_ON 0b10000000 // 7 7 preamble detection enabled (default) +#define RADIOLIB_SX127X_PREAMBLE_DETECTOR_1_BYTE 0b00000000 // 6 5 preamble detection size: 1 byte (default) +#define RADIOLIB_SX127X_PREAMBLE_DETECTOR_2_BYTE 0b00100000 // 6 5 2 bytes +#define RADIOLIB_SX127X_PREAMBLE_DETECTOR_3_BYTE 0b01000000 // 6 5 3 bytes +#define RADIOLIB_SX127X_PREAMBLE_DETECTOR_TOL 0x0A // 4 0 default number of tolerated errors per chip (4 chips per bit) + +// RADIOLIB_SX127X_REG_RX_TIMEOUT_1 +#define RADIOLIB_SX127X_TIMEOUT_RX_RSSI_OFF 0x00 // 7 0 disable receiver timeout when RSSI interrupt doesn't occur (default) + +// RADIOLIB_SX127X_REG_RX_TIMEOUT_2 +#define RADIOLIB_SX127X_TIMEOUT_RX_PREAMBLE_OFF 0x00 // 7 0 disable receiver timeout when preamble interrupt doesn't occur (default) + +// RADIOLIB_SX127X_REG_RX_TIMEOUT_3 +#define RADIOLIB_SX127X_TIMEOUT_SIGNAL_SYNC_OFF 0x00 // 7 0 disable receiver timeout when sync address interrupt doesn't occur (default) + +// RADIOLIB_SX127X_REG_OSC +#define RADIOLIB_SX127X_RC_CAL_START 0b00000000 // 3 3 manually start RC oscillator calibration +#define RADIOLIB_SX127X_CLK_OUT_FXOSC 0b00000000 // 2 0 ClkOut frequency: F(XOSC) +#define RADIOLIB_SX127X_CLK_OUT_FXOSC_2 0b00000001 // 2 0 F(XOSC) / 2 +#define RADIOLIB_SX127X_CLK_OUT_FXOSC_4 0b00000010 // 2 0 F(XOSC) / 4 +#define RADIOLIB_SX127X_CLK_OUT_FXOSC_8 0b00000011 // 2 0 F(XOSC) / 8 +#define RADIOLIB_SX127X_CLK_OUT_FXOSC_16 0b00000100 // 2 0 F(XOSC) / 16 +#define RADIOLIB_SX127X_CLK_OUT_FXOSC_32 0b00000101 // 2 0 F(XOSC) / 32 +#define RADIOLIB_SX127X_CLK_OUT_RC 0b00000110 // 2 0 RC +#define RADIOLIB_SX127X_CLK_OUT_OFF 0b00000111 // 2 0 disabled (default) + +// RADIOLIB_SX127X_REG_PREAMBLE_MSB_FSK + SX127X_REG_PREAMBLE_LSB_FSK +#define RADIOLIB_SX127X_PREAMBLE_SIZE_MSB 0x00 // 7 0 preamble size in bytes +#define RADIOLIB_SX127X_PREAMBLE_SIZE_LSB 0x03 // 7 0 default value: 3 bytes + +// RADIOLIB_SX127X_REG_SYNC_CONFIG +#define RADIOLIB_SX127X_AUTO_RESTART_RX_MODE_OFF 0b00000000 // 7 6 Rx mode restart after packet reception: disabled +#define RADIOLIB_SX127X_AUTO_RESTART_RX_MODE_NO_PLL 0b01000000 // 7 6 enabled, don't wait for PLL lock +#define RADIOLIB_SX127X_AUTO_RESTART_RX_MODE_PLL 0b10000000 // 7 6 enabled, wait for PLL lock (default) +#define RADIOLIB_SX127X_PREAMBLE_POLARITY_AA 0b00000000 // 5 5 preamble polarity: 0xAA = 0b10101010 (default) +#define RADIOLIB_SX127X_PREAMBLE_POLARITY_55 0b00100000 // 5 5 0x55 = 0b01010101 +#define RADIOLIB_SX127X_SYNC_OFF 0b00000000 // 4 4 sync word disabled +#define RADIOLIB_SX127X_SYNC_ON 0b00010000 // 4 4 sync word enabled (default) +#define RADIOLIB_SX127X_SYNC_SIZE 0x03 // 2 0 sync word size in bytes, SyncSize = SYNC_SIZE + 1 bytes + +// RADIOLIB_SX127X_REG_SYNC_VALUE_1 - SX127X_REG_SYNC_VALUE_8 +#define RADIOLIB_SX127X_SYNC_VALUE_1 0x01 // 7 0 sync word: 1st byte (MSB) +#define RADIOLIB_SX127X_SYNC_VALUE_2 0x01 // 7 0 2nd byte +#define RADIOLIB_SX127X_SYNC_VALUE_3 0x01 // 7 0 3rd byte +#define RADIOLIB_SX127X_SYNC_VALUE_4 0x01 // 7 0 4th byte +#define RADIOLIB_SX127X_SYNC_VALUE_5 0x01 // 7 0 5th byte +#define RADIOLIB_SX127X_SYNC_VALUE_6 0x01 // 7 0 6th byte +#define RADIOLIB_SX127X_SYNC_VALUE_7 0x01 // 7 0 7th byte +#define RADIOLIB_SX127X_SYNC_VALUE_8 0x01 // 7 0 8th byte (LSB) + +// RADIOLIB_SX127X_REG_PACKET_CONFIG_1 +#define RADIOLIB_SX127X_PACKET_FIXED 0b00000000 // 7 7 packet format: fixed length +#define RADIOLIB_SX127X_PACKET_VARIABLE 0b10000000 // 7 7 variable length (default) +#define RADIOLIB_SX127X_DC_FREE_NONE 0b00000000 // 6 5 DC-free encoding: disabled (default) +#define RADIOLIB_SX127X_DC_FREE_MANCHESTER 0b00100000 // 6 5 Manchester +#define RADIOLIB_SX127X_DC_FREE_WHITENING 0b01000000 // 6 5 Whitening +#define RADIOLIB_SX127X_CRC_OFF 0b00000000 // 4 4 CRC disabled +#define RADIOLIB_SX127X_CRC_ON 0b00010000 // 4 4 CRC enabled (default) +#define RADIOLIB_SX127X_CRC_AUTOCLEAR_OFF 0b00001000 // 3 3 keep FIFO on CRC mismatch, issue payload ready interrupt +#define RADIOLIB_SX127X_CRC_AUTOCLEAR_ON 0b00000000 // 3 3 clear FIFO on CRC mismatch, do not issue payload ready interrupt +#define RADIOLIB_SX127X_ADDRESS_FILTERING_OFF 0b00000000 // 2 1 address filtering: none (default) +#define RADIOLIB_SX127X_ADDRESS_FILTERING_NODE 0b00000010 // 2 1 node +#define RADIOLIB_SX127X_ADDRESS_FILTERING_NODE_BROADCAST 0b00000100 // 2 1 node or broadcast +#define RADIOLIB_SX127X_CRC_WHITENING_TYPE_CCITT 0b00000000 // 0 0 CRC and whitening algorithms: CCITT CRC with standard whitening (default) +#define RADIOLIB_SX127X_CRC_WHITENING_TYPE_IBM 0b00000001 // 0 0 IBM CRC with alternate whitening + +// RADIOLIB_SX127X_REG_PACKET_CONFIG_2 +#define RADIOLIB_SX127X_DATA_MODE_PACKET 0b01000000 // 6 6 data mode: packet (default) +#define RADIOLIB_SX127X_DATA_MODE_CONTINUOUS 0b00000000 // 6 6 continuous +#define RADIOLIB_SX127X_IO_HOME_OFF 0b00000000 // 5 5 io-homecontrol compatibility disabled (default) +#define RADIOLIB_SX127X_IO_HOME_ON 0b00100000 // 5 5 io-homecontrol compatibility enabled + +// RADIOLIB_SX127X_REG_FIFO_THRESH +#define RADIOLIB_SX127X_TX_START_FIFO_LEVEL 0b00000000 // 7 7 start packet transmission when: number of bytes in FIFO exceeds FIFO_THRESHOLD +#define RADIOLIB_SX127X_TX_START_FIFO_NOT_EMPTY 0b10000000 // 7 7 at least one byte in FIFO (default) +#define RADIOLIB_SX127X_FIFO_THRESH 0x1F // 5 0 FIFO level threshold + +// RADIOLIB_SX127X_REG_SEQ_CONFIG_1 +#define RADIOLIB_SX127X_SEQUENCER_START 0b10000000 // 7 7 manually start sequencer +#define RADIOLIB_SX127X_SEQUENCER_STOP 0b01000000 // 6 6 manually stop sequencer +#define RADIOLIB_SX127X_IDLE_MODE_STANDBY 0b00000000 // 5 5 chip mode during sequencer idle mode: standby (default) +#define RADIOLIB_SX127X_IDLE_MODE_SLEEP 0b00100000 // 5 5 sleep +#define RADIOLIB_SX127X_FROM_START_LP_SELECTION 0b00000000 // 4 3 mode that will be set after starting sequencer: low power selection (default) +#define RADIOLIB_SX127X_FROM_START_RECEIVE 0b00001000 // 4 3 receive +#define RADIOLIB_SX127X_FROM_START_TRANSMIT 0b00010000 // 4 3 transmit +#define RADIOLIB_SX127X_FROM_START_TRANSMIT_FIFO_LEVEL 0b00011000 // 4 3 transmit on a FIFO level interrupt +#define RADIOLIB_SX127X_LP_SELECTION_SEQ_OFF 0b00000000 // 2 2 mode that will be set after exiting low power selection: sequencer off (default) +#define RADIOLIB_SX127X_LP_SELECTION_IDLE 0b00000100 // 2 2 idle state +#define RADIOLIB_SX127X_FROM_IDLE_TRANSMIT 0b00000000 // 1 1 mode that will be set after exiting idle mode: transmit (default) +#define RADIOLIB_SX127X_FROM_IDLE_RECEIVE 0b00000010 // 1 1 receive +#define RADIOLIB_SX127X_FROM_TRANSMIT_LP_SELECTION 0b00000000 // 0 0 mode that will be set after exiting transmit mode: low power selection (default) +#define RADIOLIB_SX127X_FROM_TRANSMIT_RECEIVE 0b00000001 // 0 0 receive + +// RADIOLIB_SX127X_REG_SEQ_CONFIG_2 +#define RADIOLIB_SX127X_FROM_RECEIVE_PACKET_RECEIVED_PAYLOAD 0b00100000 // 7 5 mode that will be set after exiting receive mode: packet received on payload ready interrupt (default) +#define RADIOLIB_SX127X_FROM_RECEIVE_LP_SELECTION 0b01000000 // 7 5 low power selection +#define RADIOLIB_SX127X_FROM_RECEIVE_PACKET_RECEIVED_CRC_OK 0b01100000 // 7 5 packet received on CRC OK interrupt +#define RADIOLIB_SX127X_FROM_RECEIVE_SEQ_OFF_RSSI 0b10000000 // 7 5 sequencer off on RSSI interrupt +#define RADIOLIB_SX127X_FROM_RECEIVE_SEQ_OFF_SYNC_ADDR 0b10100000 // 7 5 sequencer off on sync address interrupt +#define RADIOLIB_SX127X_FROM_RECEIVE_SEQ_OFF_PREAMBLE_DETECT 0b11000000 // 7 5 sequencer off on preamble detect interrupt +#define RADIOLIB_SX127X_FROM_RX_TIMEOUT_RECEIVE 0b00000000 // 4 3 mode that will be set after Rx timeout: receive (default) +#define RADIOLIB_SX127X_FROM_RX_TIMEOUT_TRANSMIT 0b00001000 // 4 3 transmit +#define RADIOLIB_SX127X_FROM_RX_TIMEOUT_LP_SELECTION 0b00010000 // 4 3 low power selection +#define RADIOLIB_SX127X_FROM_RX_TIMEOUT_SEQ_OFF 0b00011000 // 4 3 sequencer off +#define RADIOLIB_SX127X_FROM_PACKET_RECEIVED_SEQ_OFF 0b00000000 // 2 0 mode that will be set after packet received: sequencer off (default) +#define RADIOLIB_SX127X_FROM_PACKET_RECEIVED_TRANSMIT 0b00000001 // 2 0 transmit +#define RADIOLIB_SX127X_FROM_PACKET_RECEIVED_LP_SELECTION 0b00000010 // 2 0 low power selection +#define RADIOLIB_SX127X_FROM_PACKET_RECEIVED_RECEIVE_FS 0b00000011 // 2 0 receive via FS +#define RADIOLIB_SX127X_FROM_PACKET_RECEIVED_RECEIVE 0b00000100 // 2 0 receive + +// RADIOLIB_SX127X_REG_TIMER_RESOL +#define RADIOLIB_SX127X_TIMER1_OFF 0b00000000 // 3 2 timer 1 resolution: disabled (default) +#define RADIOLIB_SX127X_TIMER1_RESOLUTION_64_US 0b00000100 // 3 2 64 us +#define RADIOLIB_SX127X_TIMER1_RESOLUTION_4_1_MS 0b00001000 // 3 2 4.1 ms +#define RADIOLIB_SX127X_TIMER1_RESOLUTION_262_MS 0b00001100 // 3 2 262 ms +#define RADIOLIB_SX127X_TIMER2_OFF 0b00000000 // 3 2 timer 2 resolution: disabled (default) +#define RADIOLIB_SX127X_TIMER2_RESOLUTION_64_US 0b00000001 // 3 2 64 us +#define RADIOLIB_SX127X_TIMER2_RESOLUTION_4_1_MS 0b00000010 // 3 2 4.1 ms +#define RADIOLIB_SX127X_TIMER2_RESOLUTION_262_MS 0b00000011 // 3 2 262 ms + +// RADIOLIB_SX127X_REG_TIMER1_COEF +#define RADIOLIB_SX127X_TIMER1_COEFFICIENT 0xF5 // 7 0 multiplication coefficient for timer 1 + +// RADIOLIB_SX127X_REG_TIMER2_COEF +#define RADIOLIB_SX127X_TIMER2_COEFFICIENT 0x20 // 7 0 multiplication coefficient for timer 2 + +// RADIOLIB_SX127X_REG_IMAGE_CAL +#define RADIOLIB_SX127X_AUTO_IMAGE_CAL_OFF 0b00000000 // 7 7 temperature calibration disabled (default) +#define RADIOLIB_SX127X_AUTO_IMAGE_CAL_ON 0b10000000 // 7 7 temperature calibration enabled +#define RADIOLIB_SX127X_IMAGE_CAL_START 0b01000000 // 6 6 start temperature calibration +#define RADIOLIB_SX127X_IMAGE_CAL_RUNNING 0b00100000 // 5 5 temperature calibration is on-going +#define RADIOLIB_SX127X_IMAGE_CAL_COMPLETE 0b00000000 // 5 5 temperature calibration finished +#define RADIOLIB_SX127X_TEMP_CHANGED 0b00001000 // 3 3 temperature changed more than TEMP_THRESHOLD since last calibration +#define RADIOLIB_SX127X_TEMP_THRESHOLD_5_DEG_C 0b00000000 // 2 1 temperature change threshold: 5 deg. C +#define RADIOLIB_SX127X_TEMP_THRESHOLD_10_DEG_C 0b00000010 // 2 1 10 deg. C (default) +#define RADIOLIB_SX127X_TEMP_THRESHOLD_15_DEG_C 0b00000100 // 2 1 15 deg. C +#define RADIOLIB_SX127X_TEMP_THRESHOLD_20_DEG_C 0b00000110 // 2 1 20 deg. C +#define RADIOLIB_SX127X_TEMP_MONITOR_ON 0b00000000 // 0 0 temperature monitoring enabled (default) +#define RADIOLIB_SX127X_TEMP_MONITOR_OFF 0b00000001 // 0 0 temperature monitoring disabled + +// RADIOLIB_SX127X_REG_LOW_BAT +#define RADIOLIB_SX127X_LOW_BAT_OFF 0b00000000 // 3 3 low battery detector disabled +#define RADIOLIB_SX127X_LOW_BAT_ON 0b00001000 // 3 3 low battery detector enabled +#define RADIOLIB_SX127X_LOW_BAT_THRESHOLD_1_695_V 0b00000000 // 2 0 battery voltage threshold: 1.695 V +#define RADIOLIB_SX127X_LOW_BAT_THRESHOLD_1_764_V 0b00000001 // 2 0 1.764 V +#define RADIOLIB_SX127X_LOW_BAT_THRESHOLD_1_835_V 0b00000010 // 2 0 1.835 V (default) +#define RADIOLIB_SX127X_LOW_BAT_THRESHOLD_1_905_V 0b00000011 // 2 0 1.905 V +#define RADIOLIB_SX127X_LOW_BAT_THRESHOLD_1_976_V 0b00000100 // 2 0 1.976 V +#define RADIOLIB_SX127X_LOW_BAT_THRESHOLD_2_045_V 0b00000101 // 2 0 2.045 V +#define RADIOLIB_SX127X_LOW_BAT_THRESHOLD_2_116_V 0b00000110 // 2 0 2.116 V +#define RADIOLIB_SX127X_LOW_BAT_THRESHOLD_2_185_V 0b00000111 // 2 0 2.185 V + +// RADIOLIB_SX127X_REG_IRQ_FLAGS_1 +#define RADIOLIB_SX127X_FLAG_MODE_READY 0b10000000 // 7 7 requested mode is ready +#define RADIOLIB_SX127X_FLAG_RX_READY 0b01000000 // 6 6 reception ready (after RSSI, AGC, AFC) +#define RADIOLIB_SX127X_FLAG_TX_READY 0b00100000 // 5 5 transmission ready (after PA ramp-up) +#define RADIOLIB_SX127X_FLAG_PLL_LOCK 0b00010000 // 4 4 PLL locked +#define RADIOLIB_SX127X_FLAG_RSSI 0b00001000 // 3 3 RSSI value exceeds RSSI threshold +#define RADIOLIB_SX127X_FLAG_TIMEOUT 0b00000100 // 2 2 timeout occurred +#define RADIOLIB_SX127X_FLAG_PREAMBLE_DETECT 0b00000010 // 1 1 valid preamble was detected +#define RADIOLIB_SX127X_FLAG_SYNC_ADDRESS_MATCH 0b00000001 // 0 0 sync address matched + +// RADIOLIB_SX127X_REG_IRQ_FLAGS_2 +#define RADIOLIB_SX127X_FLAG_FIFO_FULL 0b10000000 // 7 7 FIFO is full +#define RADIOLIB_SX127X_FLAG_FIFO_EMPTY 0b01000000 // 6 6 FIFO is empty +#define RADIOLIB_SX127X_FLAG_FIFO_LEVEL 0b00100000 // 5 5 number of bytes in FIFO exceeds FIFO_THRESHOLD +#define RADIOLIB_SX127X_FLAG_FIFO_OVERRUN 0b00010000 // 4 4 FIFO overrun occurred +#define RADIOLIB_SX127X_FLAG_PACKET_SENT 0b00001000 // 3 3 packet was successfully sent +#define RADIOLIB_SX127X_FLAG_PAYLOAD_READY 0b00000100 // 2 2 packet was successfully received +#define RADIOLIB_SX127X_FLAG_CRC_OK 0b00000010 // 1 1 CRC check passed +#define RADIOLIB_SX127X_FLAG_LOW_BAT 0b00000001 // 0 0 battery voltage dropped below threshold +#define RADIOLIB_SX127X_FLAGS_ALL 0xFFFF + +// RADIOLIB_SX127X_REG_DIO_MAPPING_1 +#define RADIOLIB_SX127X_DIO0_LORA_RX_DONE 0b00000000 // 7 6 +#define RADIOLIB_SX127X_DIO0_LORA_TX_DONE 0b01000000 // 7 6 +#define RADIOLIB_SX127X_DIO0_LORA_CAD_DONE 0b10000000 // 7 6 +#define RADIOLIB_SX127X_DIO0_CONT_MODE_READY 0b11000000 // 7 6 +#define RADIOLIB_SX127X_DIO0_CONT_SYNC_ADDRESS 0b00000000 // 7 6 +#define RADIOLIB_SX127X_DIO0_CONT_RSSI_PREAMBLE_DETECT 0b01000000 // 7 6 +#define RADIOLIB_SX127X_DIO0_CONT_RX_READY 0b10000000 // 7 6 +#define RADIOLIB_SX127X_DIO0_CONT_TX_READY 0b00000000 // 7 6 +#define RADIOLIB_SX127X_DIO0_PACK_PAYLOAD_READY 0b00000000 // 7 6 +#define RADIOLIB_SX127X_DIO0_PACK_PACKET_SENT 0b00000000 // 7 6 +#define RADIOLIB_SX127X_DIO0_PACK_CRC_OK 0b01000000 // 7 6 +#define RADIOLIB_SX127X_DIO0_PACK_TEMP_CHANGE_LOW_BAT 0b11000000 // 7 6 +#define RADIOLIB_SX127X_DIO1_LORA_RX_TIMEOUT 0b00000000 // 5 4 +#define RADIOLIB_SX127X_DIO1_LORA_FHSS_CHANGE_CHANNEL 0b00010000 // 5 4 +#define RADIOLIB_SX127X_DIO1_LORA_CAD_DETECTED 0b00100000 // 5 4 +#define RADIOLIB_SX127X_DIO1_CONT_DCLK 0b00000000 // 5 4 +#define RADIOLIB_SX127X_DIO1_CONT_RSSI_PREAMBLE_DETECT 0b00010000 // 5 4 +#define RADIOLIB_SX127X_DIO1_PACK_FIFO_LEVEL 0b00000000 // 5 4 +#define RADIOLIB_SX127X_DIO1_PACK_FIFO_EMPTY 0b00010000 // 5 4 +#define RADIOLIB_SX127X_DIO1_PACK_FIFO_FULL 0b00100000 // 5 4 +#define RADIOLIB_SX127X_DIO2_LORA_FHSS_CHANGE_CHANNEL 0b00000000 // 3 2 +#define RADIOLIB_SX127X_DIO2_CONT_DATA 0b00000000 // 3 2 +#define RADIOLIB_SX127X_DIO2_PACK_FIFO_FULL 0b00000000 // 3 2 +#define RADIOLIB_SX127X_DIO2_PACK_RX_READY 0b00000100 // 3 2 +#define RADIOLIB_SX127X_DIO2_PACK_TIMEOUT 0b00001000 // 3 2 +#define RADIOLIB_SX127X_DIO2_PACK_SYNC_ADDRESS 0b00011000 // 3 2 +#define RADIOLIB_SX127X_DIO3_LORA_CAD_DONE 0b00000000 // 1 0 +#define RADIOLIB_SX127X_DIO3_LORA_VALID_HEADER 0b00000001 // 1 0 +#define RADIOLIB_SX127X_DIO3_LORA_PAYLOAD_CRC_ERROR 0b00000010 // 1 0 +#define RADIOLIB_SX127X_DIO3_CONT_TIMEOUT 0b00000000 // 1 0 +#define RADIOLIB_SX127X_DIO3_CONT_RSSI_PREAMBLE_DETECT 0b00000001 // 1 0 +#define RADIOLIB_SX127X_DIO3_CONT_TEMP_CHANGE_LOW_BAT 0b00000011 // 1 0 +#define RADIOLIB_SX127X_DIO3_PACK_FIFO_EMPTY 0b00000000 // 1 0 +#define RADIOLIB_SX127X_DIO3_PACK_TX_READY 0b00000001 // 1 0 + +// RADIOLIB_SX127X_REG_DIO_MAPPING_2 +#define RADIOLIB_SX127X_DIO4_LORA_CAD_DETECTED 0b10000000 // 7 6 +#define RADIOLIB_SX127X_DIO4_LORA_PLL_LOCK 0b01000000 // 7 6 +#define RADIOLIB_SX127X_DIO4_CONT_TEMP_CHANGE_LOW_BAT 0b00000000 // 7 6 +#define RADIOLIB_SX127X_DIO4_CONT_PLL_LOCK 0b01000000 // 7 6 +#define RADIOLIB_SX127X_DIO4_CONT_TIMEOUT 0b10000000 // 7 6 +#define RADIOLIB_SX127X_DIO4_CONT_MODE_READY 0b11000000 // 7 6 +#define RADIOLIB_SX127X_DIO4_PACK_TEMP_CHANGE_LOW_BAT 0b00000000 // 7 6 +#define RADIOLIB_SX127X_DIO4_PACK_PLL_LOCK 0b01000000 // 7 6 +#define RADIOLIB_SX127X_DIO4_PACK_TIMEOUT 0b10000000 // 7 6 +#define RADIOLIB_SX127X_DIO4_PACK_RSSI_PREAMBLE_DETECT 0b11000000 // 7 6 +#define RADIOLIB_SX127X_DIO5_LORA_MODE_READY 0b00000000 // 5 4 +#define RADIOLIB_SX127X_DIO5_LORA_CLK_OUT 0b00010000 // 5 4 +#define RADIOLIB_SX127X_DIO5_CONT_CLK_OUT 0b00000000 // 5 4 +#define RADIOLIB_SX127X_DIO5_CONT_PLL_LOCK 0b00010000 // 5 4 +#define RADIOLIB_SX127X_DIO5_CONT_RSSI_PREAMBLE_DETECT 0b00100000 // 5 4 +#define RADIOLIB_SX127X_DIO5_CONT_MODE_READY 0b00110000 // 5 4 +#define RADIOLIB_SX127X_DIO5_PACK_CLK_OUT 0b00000000 // 5 4 +#define RADIOLIB_SX127X_DIO5_PACK_PLL_LOCK 0b00010000 // 5 4 +#define RADIOLIB_SX127X_DIO5_PACK_DATA 0b00100000 // 5 4 +#define RADIOLIB_SX127X_DIO5_PACK_MODE_READY 0b00110000 // 5 4 +#define RADIOLIB_SX127X_DIO_MAP_PREAMBLE_DETECT 0b00000001 // 0 0 +#define RADIOLIB_SX127X_DIO_MAP_RSSI 0b00000000 // 0 0 + +// SX1272_REG_PLL_HOP + SX1278_REG_PLL_HOP +#define RADIOLIB_SX127X_FAST_HOP_OFF 0b00000000 // 7 7 carrier frequency validated when FRF registers are written +#define RADIOLIB_SX127X_FAST_HOP_ON 0b10000000 // 7 7 carrier frequency validated when FS modes are requested + +// SX1272_REG_TCXO + SX1278_REG_TCXO +#define RADIOLIB_SX127X_TCXO_INPUT_EXTERNAL 0b00000000 // 4 4 use external crystal oscillator +#define RADIOLIB_SX127X_TCXO_INPUT_EXTERNAL_CLIPPED 0b00010000 // 4 4 use external crystal oscillator clipped sine connected to XTA pin + +// SX1272_REG_PLL + SX1278_REG_PLL +#define RADIOLIB_SX127X_PLL_BANDWIDTH_75_KHZ 0b00000000 // 7 6 PLL bandwidth: 75 kHz +#define RADIOLIB_SX127X_PLL_BANDWIDTH_150_KHZ 0b01000000 // 7 6 150 kHz +#define RADIOLIB_SX127X_PLL_BANDWIDTH_225_KHZ 0b10000000 // 7 6 225 kHz +#define RADIOLIB_SX127X_PLL_BANDWIDTH_300_KHZ 0b11000000 // 7 6 300 kHz (default) + +/*! + \class SX127x + \brief Base class for SX127x series. All derived classes for SX127x (e.g. SX1278 or SX1272) inherit from this base class. + This class should not be instantiated directly from Arduino sketch, only from its derived classes. +*/ +class SX127x: public PhysicalLayer { + public: + // introduce PhysicalLayer overloads + using PhysicalLayer::transmit; + using PhysicalLayer::receive; + using PhysicalLayer::startTransmit; + using PhysicalLayer::readData; + + // constructor + + /*! + \brief Default constructor. Called internally when creating new LoRa instance. + \param mod Instance of Module that will be used to communicate with the %LoRa chip. + */ + explicit SX127x(Module* mod); + + // basic methods + + /*! + \brief Initialization method. Will be called with appropriate parameters when calling initialization method from derived class. + \param chipVersions Array of possible values in SPI version register. Used to verify the connection and hardware version. + \param numVersions Number of possible chip versions. + \param syncWord %LoRa sync word. + \param preambleLength Length of %LoRa transmission preamble in symbols. + \returns \ref status_codes + */ + int16_t begin(uint8_t* chipVersions, uint8_t numVersions, uint8_t syncWord, uint16_t preambleLength); + + /*! + \brief Reset method. Will reset the chip to the default state using RST pin. Declared pure virtual since SX1272 and SX1278 implementations differ. + */ + virtual void reset() = 0; + + /*! + \brief Initialization method for FSK modem. Will be called with appropriate parameters when calling FSK initialization method from derived class. + \param chipVersions Array of possible values in SPI version register. Used to verify the connection and hardware version. + \param numVersions Number of possible chip versions. + \param freqDev Frequency deviation of the FSK transmission in kHz. + \param rxBw Receiver bandwidth in kHz. + \param preambleLength Length of FSK preamble in bits. + \param enableOOK Flag to specify OOK mode. This modulation is similar to FSK. + \returns \ref status_codes + */ + int16_t beginFSK(uint8_t* chipVersions, uint8_t numVersions, float freqDev, float rxBw, uint16_t preambleLength, bool enableOOK); + + /*! + \brief Binary transmit method. Will transmit arbitrary binary data up to 255 bytes long using %LoRa or up to 63 bytes using FSK modem. + For overloads to transmit Arduino String or C-string, see PhysicalLayer::transmit. + \param data Binary data that will be transmitted. + \param len Length of binary data to transmit (in bytes). + \param addr Node address to transmit the packet to. Only used in FSK mode. + \returns \ref status_codes + */ + int16_t transmit(const uint8_t* data, size_t len, uint8_t addr = 0) override; + + /*! + \brief Binary receive method. Will attempt to receive arbitrary binary data up to 255 bytes long using %LoRa or up to 63 bytes using FSK modem. + For overloads to receive Arduino String, see PhysicalLayer::receive. + \param data Pointer to array to save the received binary data. + \param len Number of bytes that will be received. Must be known in advance for binary transmissions. + \returns \ref status_codes + */ + int16_t receive(uint8_t* data, size_t len) override; + + /*! + \brief Performs scan for valid %LoRa preamble in the current channel. + \returns \ref status_codes + */ + int16_t scanChannel() override; + + /*! + \brief Sets the %LoRa module to sleep to save power. %Module will not be able to transmit or receive any data while in sleep mode. + %Module will wake up automatically when methods like transmit or receive are called. + \returns \ref status_codes + */ + int16_t sleep() override; + + /*! + \brief Sets the %LoRa module to standby. + \returns \ref status_codes + */ + int16_t standby() override; + + /*! + \brief Sets the %LoRa module to standby. + \param mode Standby mode to be used. No effect, implemented only for PhysicalLayer compatibility. + \returns \ref status_codes + */ + int16_t standby(uint8_t mode) override; + + /*! + \brief Enables direct transmission mode on pins DIO1 (clock) and DIO2 (data). + While in direct mode, the module will not be able to transmit or receive packets. Can only be activated in FSK mode. + \param frf 24-bit raw frequency value to start transmitting at. Required for quick frequency shifts in RTTY. + \returns \ref status_codes + */ + int16_t transmitDirect(uint32_t frf = 0) override; + + /*! + \brief Enables direct reception mode on pins DIO1 (clock) and DIO2 (data). + While in direct mode, the module will not be able to transmit or receive packets. Can only be activated in FSK mode. + \returns \ref status_codes + */ + int16_t receiveDirect() override; + + /*! + \brief Disables direct mode and enables packet mode, allowing the module to receive packets. Can only be activated in FSK mode. + \returns \ref status_codes + */ + int16_t packetMode(); + + // interrupt methods + + /*! + \brief Set interrupt service routine function to call when DIO0 activates. + \param func Pointer to interrupt service routine. + \param dir Signal change direction. + */ + void setDio0Action(void (*func)(void), uint32_t dir); + + /*! + \brief Clears interrupt service routine to call when DIO0 activates. + */ + void clearDio0Action(); + + /*! + \brief Set interrupt service routine function to call when DIO1 activates. + \param func Pointer to interrupt service routine. + \param dir Signal change direction. + */ + void setDio1Action(void (*func)(void), uint32_t dir); + + /*! + \brief Clears interrupt service routine to call when DIO1 activates. + */ + void clearDio1Action(); + + /*! + \brief Sets interrupt service routine to call when a packet is received. + \param func ISR to call. + */ + void setPacketReceivedAction(void (*func)(void)) override; + + /*! + \brief Clears interrupt service routine to call when a packet is received. + */ + void clearPacketReceivedAction() override; + + /*! + \brief Sets interrupt service routine to call when a packet is sent. + \param func ISR to call. + */ + void setPacketSentAction(void (*func)(void)) override; + + /*! + \brief Clears interrupt service routine to call when a packet is sent. + */ + void clearPacketSentAction() override; + + /*! + \brief Sets interrupt service routine to call when a channel scan is finished. + \param func ISR to call. + */ + void setChannelScanAction(void (*func)(void)) override; + + /*! + \brief Clears interrupt service routine to call when a channel scan is finished. + */ + void clearChannelScanAction() override; + + /*! + \brief Set interrupt service routine function to call when FIFO is empty. + \param func Pointer to interrupt service routine. + */ + void setFifoEmptyAction(void (*func)(void)); + + /*! + \brief Clears interrupt service routine to call when FIFO is empty. + */ + void clearFifoEmptyAction(); + + /*! + \brief Set interrupt service routine function to call when FIFO is full. + \param func Pointer to interrupt service routine. + */ + void setFifoFullAction(void (*func)(void)); + + /*! + \brief Clears interrupt service routine to call when FIFO is full. + */ + void clearFifoFullAction(); + + /*! + \brief Set interrupt service routine function to call when FIFO is empty. + \param data Pointer to the transmission buffer. + \param totalLen Total number of bytes to transmit. + \param remLen Pointer to a counter holding the number of bytes that have been transmitted so far. + \returns True when a complete packet is sent, false if more data is needed. + */ + bool fifoAdd(uint8_t* data, int totalLen, int* remLen); + + /*! + \brief Set interrupt service routine function to call when FIFO is sufficiently full to read. + \param data Pointer to a buffer that stores the receive data. + \param totalLen Total number of bytes to receive. + \param rcvLen Pointer to a counter holding the number of bytes that have been received so far. + \returns True when a complete packet is received, false if more data is needed. + */ + bool fifoGet(volatile uint8_t* data, int totalLen, volatile int* rcvLen); + + /*! + \brief Interrupt-driven binary transmit method. Will start transmitting arbitrary binary data up to 255 bytes long using %LoRa or up to 63 bytes using FSK modem. + \param data Binary data that will be transmitted. + \param len Length of binary data to transmit (in bytes). + \param addr Node address to transmit the packet to. Only used in FSK mode. + \returns \ref status_codes + */ + int16_t startTransmit(const uint8_t* data, size_t len, uint8_t addr = 0) override; + + /*! + \brief Clean up after transmission is done. + \returns \ref status_codes + */ + int16_t finishTransmit() override; + + /*! + \brief Interrupt-driven receive method with default parameters. + Implemented for compatibility with PhysicalLayer. + \returns \ref status_codes + */ + int16_t startReceive() override; + + /*! + \brief Interrupt-driven receive method, implemented for compatibility with PhysicalLayer. + \param timeout Receive mode type and/or raw timeout value in symbols. + When set to 0, the timeout will be infinite and the device will remain + in Rx mode until explicitly commanded to stop (Rx continuous mode). + When non-zero (maximum 1023), the device will be set to Rx single mode and timeout will be set. + \param irqFlags Sets the IRQ flags, defaults to RX done, RX timeout, CRC error and header error. + \param irqMask Sets the mask of IRQ flags that will trigger DIO1, defaults to RX done. + \param len Expected length of packet to be received. Required for LoRa spreading factor 6. + \returns \ref status_codes + */ + int16_t startReceive(uint32_t timeout, RadioLibIrqFlags_t irqFlags = RADIOLIB_IRQ_RX_DEFAULT_FLAGS, RadioLibIrqFlags_t irqMask = RADIOLIB_IRQ_RX_DEFAULT_MASK, size_t len = 0) override; + + /*! + \brief Reads data that was received after calling startReceive method. When the packet length is not known in advance, + getPacketLength method must be called BEFORE calling readData! + \param data Pointer to array to save the received binary data. + \param len Number of bytes that will be read. When set to 0, the packet length will be retrieved automatically. + When more bytes than received are requested, only the number of bytes requested will be returned. + \returns \ref status_codes + */ + int16_t readData(uint8_t* data, size_t len) override; + + /*! + \brief Interrupt-driven channel activity detection method. DIO0 will be activated when LoRa preamble is detected. + DIO1 will be activated if there's no preamble detected before timeout. + \returns \ref status_codes + */ + int16_t startChannelScan() override; + + /*! + \brief Read the channel scan result. + \returns \ref status_codes + */ + int16_t getChannelScanResult() override; + + // configuration methods + + /*! + \brief Sets %LoRa sync word. Only available in %LoRa mode. + \param syncWord Sync word to be set. + \returns \ref status_codes + */ + int16_t setSyncWord(uint8_t syncWord); + + /*! + \brief Sets current limit for over current protection at transmitter amplifier. Allowed values range from 45 to 120 mA in 5 mA steps and 120 to 240 mA in 10 mA steps. + \param currentLimit Current limit to be set (in mA). + \returns \ref status_codes + */ + int16_t setCurrentLimit(uint8_t currentLimit); + + /*! + \brief Sets %LoRa or FSK preamble length. Allowed values range from 6 to 65535 in %LoRa mode or 0 to 65535 in FSK mode. + \param preambleLength Preamble length to be set (in symbols when in LoRa mode or bits in FSK mode). + \returns \ref status_codes + */ + int16_t setPreambleLength(size_t preambleLength) override; + + /*! + \brief Invert FSK preamble polarity. The default (non-inverted) is 0x55, the inverted is 0xAA. + \param enable Preamble polarity in FSK mode - 0xAA when true, 0x55 when false. + \returns \ref status_codes + */ + int16_t invertPreamble(bool enable); + + /*! + \brief Gets frequency error of the latest received packet. + \param autoCorrect When set to true, frequency will be automatically corrected. + \returns Frequency error in Hz. + */ + float getFrequencyError(bool autoCorrect = false); + + /*! + \brief Gets current AFC error. + \returns Frequency offset from RF in Hz if AFC is enabled and triggered, zero otherwise. + */ + float getAFCError(); + + /*! + \brief Gets signal-to-noise ratio of the latest received packet. Only available in LoRa mode. + \returns Last packet signal-to-noise ratio (SNR). + */ + float getSNR() override; + + /*! + \brief Get data rate of the latest transmitted packet. + \returns Last packet data rate in bps (bits per second). + */ + float getDataRate() const; + + /*! + \brief Sets FSK frequency deviation from carrier frequency. Allowed values depend on bit rate setting and must be lower than 200 kHz. Only available in FSK mode. + \param freqDev Frequency deviation to be set (in kHz). + \returns \ref status_codes + */ + int16_t setFrequencyDeviation(float freqDev) override; + + /*! + \brief Sets FSK receiver bandwidth. Allowed values range from 2.6 to 250 kHz. Only available in FSK mode. + \param rxBw Receiver bandwidth to be set (in kHz). + \returns \ref status_codes + */ + int16_t setRxBandwidth(float rxBw); + + /*! + \brief Sets FSK automatic frequency correction bandwidth. Allowed values range from 2.6 to 250 kHz. Only available in FSK mode. + \param afcBw Receiver AFC bandwidth to be set (in kHz). + \returns \ref status_codes + */ + int16_t setAFCBandwidth(float afcBw); + + /*! + \brief Enables or disables FSK automatic frequency correction(AFC) + \param isEnabled AFC enabled or disabled + \return \ref status_codes + */ + int16_t setAFC(bool isEnabled); + + /*! + \brief Controls trigger of AFC and AGC + \param trigger one from SX127X_RX_TRIGGER_NONE, SX127X_RX_TRIGGER_RSSI_INTERRUPT, SX127X_RX_TRIGGER_PREAMBLE_DETECT, SX127X_RX_TRIGGER_BOTH + \return \ref status_codes + */ + int16_t setAFCAGCTrigger(uint8_t trigger); + + /*! + \brief Sets FSK sync word. Allowed sync words are up to 8 bytes long and can not contain null bytes. Only available in FSK mode. + \param syncWord Sync word array. + \param len Sync word length (in bytes). + \returns \ref status_codes + */ + int16_t setSyncWord(uint8_t* syncWord, size_t len) override; + + /*! + \brief Sets FSK node address. Calling this method will enable address filtering. Only available in FSK mode. + \param nodeAddr Node address to be set. + \returns \ref status_codes + */ + int16_t setNodeAddress(uint8_t nodeAddr); + + /*! + \brief Sets FSK broadcast address. Calling this method will enable address filtering. Only available in FSK mode. + \param broadAddr Broadcast address to be set. + \returns \ref status_codes + */ + int16_t setBroadcastAddress(uint8_t broadAddr); + + /*! + \brief Disables FSK address filtering. + \returns \ref status_codes + */ + int16_t disableAddressFiltering(); + + /*! + \brief Enables/disables OOK modulation instead of FSK. + \param enableOOK Enable (true) or disable (false) OOK. + \returns \ref status_codes + */ + int16_t setOOK(bool enableOOK); + + /*! + \brief Selects the type of threshold in the OOK data slicer. + \param type Threshold type: SX127X_OOK_THRESH_PEAK(default), SX127X_OOK_THRESH_FIXED, SX127X_OOK_THRESH_AVERAGE + \returns \ref status_codes + */ + int16_t setOokThresholdType(uint8_t type); + + /*! + \brief Period of decrement of the RSSI threshold in the OOK demodulator. + \param value Use defines RADIOLIB_SX127X_OOK_PEAK_THRESH_DEC_X_X_CHIP + \returns \ref status_codes + */ + int16_t setOokPeakThresholdDecrement(uint8_t value); + + /*! + \brief Fixed threshold for the Data Slicer in OOK mode or floor threshold for the Data Slicer in OOK when Peak mode is used. + \param value Threshold level in steps of 0.5 dB. + \returns \ref status_codes + */ + int16_t setOokFixedOrFloorThreshold(uint8_t value); + + /*! + \brief Size of each decrement of the RSSI threshold in the OOK demodulator. + \param value Step size: RADIOLIB_SX127X_OOK_PEAK_THRESH_STEP_0_5_DB (default), RADIOLIB_SX127X_OOK_PEAK_THRESH_STEP_1_0_DB, RADIOLIB_SX127X_OOK_PEAK_THRESH_STEP_1_5_DB, RADIOLIB_SX127X_OOK_PEAK_THRESH_STEP_2_0_DB, RADIOLIB_SX127X_OOK_PEAK_THRESH_STEP_3_0_DB, RADIOLIB_SX127X_OOK_PEAK_THRESH_STEP_4_0_DB, RADIOLIB_SX127X_OOK_PEAK_THRESH_STEP_5_0_DB, RADIOLIB_SX127X_OOK_PEAK_THRESH_STEP_6_0_DB + \returns \ref status_codes + */ + int16_t setOokPeakThresholdStep(uint8_t value); + + /*! + \brief Enable Bit synchronizer. + \returns \ref status_codes + */ + int16_t enableBitSync(); + + /*! + \brief Disable Bit synchronizer (not allowed in Packet mode). + \returns \ref status_codes + */ + int16_t disableBitSync(); + + /*! + \brief Query modem for the packet length of received payload. + \param update Update received packet length. Will return cached value when set to false. + \returns Length of last received packet in bytes. + */ + size_t getPacketLength(bool update = true) override; + + /*! + \brief Set modem in fixed packet length mode. Available in FSK mode only. + \param len Packet length. + \returns \ref status_codes + */ + int16_t fixedPacketLengthMode(uint8_t len = RADIOLIB_SX127X_MAX_PACKET_LENGTH_FSK); + + /*! + \brief Set modem in variable packet length mode. Available in FSK mode only. + \param maxLen Maximum packet length. + \returns \ref status_codes + */ + int16_t variablePacketLengthMode(uint8_t maxLen = RADIOLIB_SX127X_MAX_PACKET_LENGTH_FSK); + + /*! + \brief Convert from bytes to LoRa symbols. + \param len Payload length in bytes. + \returns The total number of LoRa symbols, including preamble, sync and possible header. + */ + float getNumSymbols(size_t len); + + /*! + \brief Get expected time-on-air for a given size of payload. + \param len Payload length in bytes. + \returns Expected time-on-air in microseconds. + */ + RadioLibTime_t getTimeOnAir(size_t len) override; + + /*! + \brief Calculate the timeout value for this specific module / series (in number of symbols or units of time) + \param timeoutUs Timeout in microseconds to listen for + \returns Timeout value in a unit that is specific for the used module + */ + RadioLibTime_t calculateRxTimeout(RadioLibTime_t timeoutUs) override; + + /*! + \brief Read currently active IRQ flags. + \returns IRQ flags. + */ + uint32_t getIrqFlags() override; + + /*! + \brief Set interrupt on DIO1 to be sent on a specific IRQ bit (e.g. RxTimeout, CadDone). + NOTE: Unlike other modules that support IRQ abstraction (SX126x, LR11x0, etc.), + SX127x cannot configure multiple IRQs to signal using the same DIOx pin. + This method tries to configure IRQs in a "best effort" approach, and will skip conflicting flags. + RADIOLIB_ERR_INVALID_IRQ will be returned in this case. + \param irq Module-specific IRQ flags. + \returns \ref status_codes + */ + int16_t setIrqFlags(uint32_t irq) override; + + /*! + \brief Clear interrupt on a specific IRQ bit (e.g. RxTimeout, CadDone). + \param irq Module-specific IRQ flags. + \returns \ref status_codes + */ + int16_t clearIrqFlags(uint32_t irq) override; + + /*! + \brief Enable CRC filtering and generation. + \param enable Set or unset CRC filtering and generation. + \returns \ref status_codes + */ + int16_t setCrcFiltering(bool enable = true); + + /*! + \brief Sets RSSI measurement configuration in FSK mode. + \param smoothingSamples Number of samples taken to average the RSSI result. + numSamples = 2 ^ (1 + smoothingSamples), allowed values are in range 0 (2 samples) - 7 (256 samples) + \param offset Signed RSSI offset that will be automatically compensated. 1 dB per LSB, defaults to 0, allowed values are in range -16 dB to +15 dB. + \returns \ref status_codes + */ + int16_t setRSSIConfig(uint8_t smoothingSamples, int8_t offset = 0); + + /*! + \brief Sets transmission encoding. Only available in FSK mode. + Allowed values are RADIOLIB_ENCODING_NRZ, RADIOLIB_ENCODING_MANCHESTER and RADIOLIB_ENCODING_WHITENING. + \param encoding Encoding to be used. + \returns \ref status_codes + */ + int16_t setEncoding(uint8_t encoding) override; + + /*! + \brief Reads currently active IRQ flags, can be used to check which event caused an interrupt. + In LoRa mode, this is the content of SX127X_REG_IRQ_FLAGS register. + In FSK mode, this is the contents of SX127X_REG_IRQ_FLAGS_2 (MSB) and SX127X_REG_IRQ_FLAGS_1 (LSB) registers. + \returns IRQ flags. + */ + uint16_t getIRQFlags(); + + /*! + \brief Reads modem status. Only available in LoRa mode. + \returns Modem status. + */ + uint8_t getModemStatus(); + + /*! + \brief Reads uncalibrated temperature value. This function will change operating mode + and should not be called during Tx, Rx or CAD. + \returns Uncalibrated temperature sensor reading. + */ + int8_t getTempRaw(); + + /*! \copydoc Module::setRfSwitchPins */ + void setRfSwitchPins(uint32_t rxEn, uint32_t txEn); + + /*! \copydoc Module::setRfSwitchTable */ + void setRfSwitchTable(const uint32_t (&pins)[Module::RFSWITCH_MAX_PINS], const Module::RfSwitchMode_t table[]); + + /*! + \brief Get one truly random byte from RSSI noise. + \returns TRNG byte. + */ + uint8_t randomByte() override; + + /*! + \brief Read version SPI register. Should return SX1278_CHIP_VERSION (0x12) or SX1272_CHIP_VERSION (0x22) if SX127x is connected and working. + \returns Version register contents or \ref status_codes + */ + int16_t getChipVersion(); + + /*! + \brief Enable/disable inversion of the I and Q signals + \param enable QI inversion enabled (true) or disabled (false); + \returns \ref status_codes + */ + int16_t invertIQ(bool enable) override; + + /*! + \brief Get modem currently in use by the radio. + \param modem Pointer to a variable to save the retrieved configuration into. + \returns \ref status_codes + */ + int16_t getModem(ModemType_t* modem) override; + + #if !RADIOLIB_EXCLUDE_DIRECT_RECEIVE + /*! + \brief Set interrupt service routine function to call when data bit is received in direct mode. + \param func Pointer to interrupt service routine. + */ + void setDirectAction(void (*func)(void)) override; + + /*! + \brief Function to read and process data bit in direct reception mode. + \param pin Pin on which to read. + */ + void readBit(uint32_t pin) override; + #endif + + /*! + \brief Sets the hopping period and enables FHSS + \param freqHoppingPeriod Integer multiple of symbol periods between hops + \returns \ref status_codes + */ + int16_t setFHSSHoppingPeriod(uint8_t freqHoppingPeriod); + + /*! + \brief Gets FHSS hopping period + \returns 8 bit period + */ + uint8_t getFHSSHoppingPeriod(void); + + /*! + \brief Gets the FHSS channel in use + \returns 6 bit channel number + */ + uint8_t getFHSSChannel(void); + + /*! + \brief Clear the FHSS interrupt + */ + void clearFHSSInt(void); + + /*! + \brief Configure DIO pin mapping to get a given signal on a DIO pin (if available). + \param pin Pin number onto which a signal is to be placed. + \param value The value that indicates which function to place on that pin. See chip datasheet for details. + \returns \ref status_codes + */ + int16_t setDIOMapping(uint32_t pin, uint32_t value) override; + + /*! + \brief Configure DIO mapping to use RSSI or Preamble Detect for pins that support it. + \param usePreambleDetect Whether to use PreambleDetect (true) or RSSI (false) on the pins that are mapped to this function. + \returns \ref status_codes + */ + int16_t setDIOPreambleDetect(bool usePreambleDetect); + + /*! + \brief Sets the RSSI value above which the RSSI interrupt is signaled + \param dbm A dBm value between -127.5 and 0 inclusive + \returns \ref status_codes + */ + int16_t setRSSIThreshold(float dbm); + + /*! + \brief Set low battery indicator threshold. + \param level Battery threshold level (one of RADIOLIB_SX127X_LOW_BAT_THRESHOLD_*), + or -1 to disable the detector. Disabled by default. Note that this will not attach any interrupts! + \param pin DIO pin number which will be used to signal low battery. Only DIO0/4 can be used + (in packet mode) or DIO3/4 (in continuous mode). Ignored when disabling the detector. + \returns \ref status_codes + */ + int16_t setLowBatteryThreshold(int8_t level, uint32_t pin = RADIOLIB_NC); + +#if !RADIOLIB_GODMODE && !RADIOLIB_LOW_LEVEL + protected: +#endif + Module* getMod() override; + +#if !RADIOLIB_GODMODE + protected: +#endif + float frequency = 0; + float bandwidth = 0; + uint8_t spreadingFactor = 0; + size_t packetLength = 0; + uint8_t codingRate = 0; + bool crcEnabled = false; + bool ookEnabled = false; + + int16_t configFSK(); + int16_t getActiveModem(); + int16_t setFrequencyRaw(float newFreq); + int16_t setBitRateCommon(float br, uint8_t fracRegAddr); + float getRSSI(bool packet, bool skipReceive, int16_t offset); + +#if !RADIOLIB_GODMODE + private: +#endif + Module* mod; + + float bitRate = 0; + bool crcOn = true; // default value used in FSK mode + float dataRate = 0; + bool packetLengthQueried = false; // FSK packet length is the first byte in FIFO, length can only be queried once + uint8_t packetLengthConfig = RADIOLIB_SX127X_PACKET_VARIABLE; + + int16_t config(); + int16_t directMode(); + int16_t setPacketMode(uint8_t mode, uint8_t len); + bool findChip(const uint8_t* vers, uint8_t num); + int16_t setMode(uint8_t mode); + int16_t setActiveModem(uint8_t modem); + void clearFIFO(size_t count); // used mostly to clear remaining bytes in FIFO after a packet read + + /*! + \brief Calculate exponent and mantissa values for receiver bandwidth and AFC + \param bandwidth bandwidth to be set (in kHz). + \returns bandwidth in mantissa and exponent format + */ + static uint8_t calculateBWManExp(float bandwidth); + + virtual void errataFix(bool rx) = 0; +}; + +#endif + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/SX128x/SX1280.cpp b/software/firmware/source/libraries/RadioLib/src/modules/SX128x/SX1280.cpp new file mode 100644 index 000000000..fae3516bf --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/SX128x/SX1280.cpp @@ -0,0 +1,184 @@ +#include "SX1280.h" +#include +#if !RADIOLIB_EXCLUDE_SX128X + +SX1280::SX1280(Module* mod) : SX1281(mod) { + +} + +int16_t SX1280::range(bool master, uint32_t addr, uint16_t calTable[3][6]) { + // start ranging + int16_t state = startRanging(master, addr, calTable); + RADIOLIB_ASSERT(state); + + // wait until ranging is finished + Module* mod = this->getMod(); + RadioLibTime_t start = mod->hal->millis(); + while(!mod->hal->digitalRead(mod->getIrq())) { + mod->hal->yield(); + if(mod->hal->millis() - start > 10000) { + clearIrqStatus(); + standby(); + return(RADIOLIB_ERR_RANGING_TIMEOUT); + } + } + + // clear interrupt flags + state = clearIrqStatus(); + RADIOLIB_ASSERT(state); + + // set mode to standby + state = standby(); + + return(state); +} + +int16_t SX1280::startRanging(bool master, uint32_t addr, uint16_t calTable[3][6]) { + // check active modem + uint8_t modem = getPacketType(); + if(!((modem == RADIOLIB_SX128X_PACKET_TYPE_LORA) || (modem == RADIOLIB_SX128X_PACKET_TYPE_RANGING))) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // set mode to standby + int16_t state = standby(); + RADIOLIB_ASSERT(state); + + // ensure modem is set to ranging + if(modem == RADIOLIB_SX128X_PACKET_TYPE_LORA) { + state = setPacketType(RADIOLIB_SX128X_PACKET_TYPE_RANGING); + RADIOLIB_ASSERT(state); + } + + // set modulation parameters + state = setModulationParams(this->spreadingFactor, this->bandwidth, this->codingRateLoRa); + RADIOLIB_ASSERT(state); + + // set packet parameters + state = setPacketParamsLoRa(this->preambleLengthLoRa, this->headerType, this->payloadLen, this->crcLoRa); + RADIOLIB_ASSERT(state); + + // check all address bits + if(!master) { + uint8_t regValue; + state = readRegister(RADIOLIB_SX128X_REG_SLAVE_RANGING_ADDRESS_WIDTH, ®Value, 1); + RADIOLIB_ASSERT(state); + regValue &= 0b00111111; + regValue |= 0b11000000; + state = writeRegister(RADIOLIB_SX128X_REG_SLAVE_RANGING_ADDRESS_WIDTH, ®Value, 1); + RADIOLIB_ASSERT(state); + } + + // set remaining parameter values + uint32_t addrReg = RADIOLIB_SX128X_REG_SLAVE_RANGING_ADDRESS_BYTE_3; + uint32_t irqMask = RADIOLIB_SX128X_IRQ_RANGING_SLAVE_RESP_DONE | RADIOLIB_SX128X_IRQ_RANGING_SLAVE_REQ_DISCARD; + uint32_t irqDio1 = RADIOLIB_SX128X_IRQ_RANGING_SLAVE_RESP_DONE; + if(master) { + addrReg = RADIOLIB_SX128X_REG_MASTER_RANGING_ADDRESS_BYTE_3; + irqMask = RADIOLIB_SX128X_IRQ_RANGING_MASTER_RES_VALID | RADIOLIB_SX128X_IRQ_RANGING_MASTER_TIMEOUT; + irqDio1 = RADIOLIB_SX128X_IRQ_RANGING_MASTER_RES_VALID; + } + + // set ranging address + uint8_t addrBuff[] = { (uint8_t)((addr >> 24) & 0xFF), (uint8_t)((addr >> 16) & 0xFF), (uint8_t)((addr >> 8) & 0xFF), (uint8_t)(addr & 0xFF) }; + state = writeRegister(addrReg, addrBuff, 4); + RADIOLIB_ASSERT(state); + + // set DIO mapping + state = setDioIrqParams(irqMask, irqDio1); + RADIOLIB_ASSERT(state); + + // this is the default calibration from AN1200.29 + uint16_t calTbl[3][6] = { + { 10299, 10271, 10244, 10242, 10230, 10246 }, + { 11486, 11474, 11453, 11426, 11417, 11401 }, + { 13308, 13493, 13528, 13515, 13430, 13376 } + }; + + // check if user provided some custom calibration + if(calTable != NULL) { + memcpy(calTbl, calTable, sizeof(calTbl)); + } + + // set calibration values + uint8_t index = (this->spreadingFactor >> 4) - 5; + uint16_t val = 0; + switch(this->bandwidth) { + case(RADIOLIB_SX128X_LORA_BW_406_25): + val = calTbl[0][index]; + break; + case(RADIOLIB_SX128X_LORA_BW_812_50): + val = calTbl[1][index]; + break; + case(RADIOLIB_SX128X_LORA_BW_1625_00): + val = calTbl[2][index]; + break; + default: + return(RADIOLIB_ERR_INVALID_BANDWIDTH); + } + uint8_t calBuff[] = { (uint8_t)((val >> 8) & 0xFF), (uint8_t)(val & 0xFF) }; + state = writeRegister(RADIOLIB_SX128X_REG_RANGING_CALIBRATION_MSB, calBuff, 2); + RADIOLIB_ASSERT(state); + + // set role and start ranging + if(master) { + state = setRangingRole(RADIOLIB_SX128X_RANGING_ROLE_MASTER); + RADIOLIB_ASSERT(state); + + state = setTx(RADIOLIB_SX128X_TX_TIMEOUT_NONE); + RADIOLIB_ASSERT(state); + + } else { + state = setRangingRole(RADIOLIB_SX128X_RANGING_ROLE_SLAVE); + RADIOLIB_ASSERT(state); + + state = setRx(RADIOLIB_SX128X_RX_TIMEOUT_INF); + RADIOLIB_ASSERT(state); + + } + + return(state); +} + +float SX1280::getRangingResult() { + // set mode to standby XOSC + int16_t state = standby(RADIOLIB_SX128X_STANDBY_XOSC); + RADIOLIB_ASSERT(state); + + // enable clock + uint8_t data[4]; + state = readRegister(RADIOLIB_SX128X_REG_RANGING_LORA_CLOCK_ENABLE, data, 1); + RADIOLIB_ASSERT(state); + + data[0] |= (1 << 1); + state = writeRegister(RADIOLIB_SX128X_REG_RANGING_LORA_CLOCK_ENABLE, data, 1); + RADIOLIB_ASSERT(state); + + // set result type to filtered + state = readRegister(RADIOLIB_SX128X_REG_RANGING_TYPE, data, 1); + RADIOLIB_ASSERT(state); + + data[0] &= 0xCF; + data[0] |= (1 << 4); + state = writeRegister(RADIOLIB_SX128X_REG_RANGING_TYPE, data, 1); + RADIOLIB_ASSERT(state); + + // read the register values + state = readRegister(RADIOLIB_SX128X_REG_RANGING_RESULT_MSB, &data[0], 1); + RADIOLIB_ASSERT(state); + state = readRegister(RADIOLIB_SX128X_REG_RANGING_RESULT_MID, &data[1], 1); + RADIOLIB_ASSERT(state); + state = readRegister(RADIOLIB_SX128X_REG_RANGING_RESULT_LSB, &data[2], 1); + RADIOLIB_ASSERT(state); + + // set mode to standby RC + state = standby(); + RADIOLIB_ASSERT(state); + + // calculate the real result + uint32_t uraw = ((uint32_t)data[0] << 16) | ((uint32_t)data[1] << 8) | data[2]; + int32_t raw = (uraw & ((1UL << 23) - 1)) | (uraw >> 23 << 31); + return((float)raw * 150.0 / (4.096 * this->bandwidthKhz)); +} + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/SX128x/SX1280.h b/software/firmware/source/libraries/RadioLib/src/modules/SX128x/SX1280.h new file mode 100644 index 000000000..e06faec5e --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/SX128x/SX1280.h @@ -0,0 +1,56 @@ +#if !defined(_RADIOLIB_SX1280_H) +#define _RADIOLIB_SX1280_H + +#include "../../TypeDef.h" + +#if !RADIOLIB_EXCLUDE_SX128X + +#include "../../Module.h" +#include "SX128x.h" +#include "SX1281.h" + +/*! + \class SX1280 + \brief Derived class for %SX1280 modules. +*/ +class SX1280: public SX1281 { + public: + /*! + \brief Default constructor. + \param mod Instance of Module that will be used to communicate with the radio. + */ + SX1280(Module* mod); // cppcheck-suppress noExplicitConstructor + + /*! + \brief Blocking ranging method. + \param master Whether to execute ranging in master mode (true) or slave mode (false). + \param addr Ranging address to be used. + \param calTable Ranging calibration table - set to NULL to use the default. + \returns \ref status_codes + */ + int16_t range(bool master, uint32_t addr, uint16_t calTable[3][6] = NULL); + + /*! + \brief Interrupt-driven ranging method. + \param master Whether to execute ranging in master mode (true) or slave mode (false). + \param addr Ranging address to be used. + \param calTable Ranging calibration table - set to NULL to use the default. + \returns \ref status_codes + */ + int16_t startRanging(bool master, uint32_t addr, uint16_t calTable[3][6] = NULL); + + /*! + \brief Gets ranging result of the last ranging exchange. + \returns Ranging result in meters. + */ + float getRangingResult(); + +#if !RADIOLIB_GODMODE + private: +#endif + +}; + +#endif + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/SX128x/SX1281.cpp b/software/firmware/source/libraries/RadioLib/src/modules/SX128x/SX1281.cpp new file mode 100644 index 000000000..71e7476e7 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/SX128x/SX1281.cpp @@ -0,0 +1,8 @@ +#include "SX1281.h" +#if !RADIOLIB_EXCLUDE_SX128X + +SX1281::SX1281(Module* mod) : SX128x(mod) { + +} + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/SX128x/SX1281.h b/software/firmware/source/libraries/RadioLib/src/modules/SX128x/SX1281.h new file mode 100644 index 000000000..3d697dad4 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/SX128x/SX1281.h @@ -0,0 +1,31 @@ +#if !defined(_RADIOLIB_SX1281_H) +#define _RADIOLIB_SX1281_H + +#include "../../TypeDef.h" + +#if !RADIOLIB_EXCLUDE_SX128X + +#include "../../Module.h" +#include "SX128x.h" + +/*! + \class SX1281 + \brief Derived class for %SX1281 modules. +*/ +class SX1281: public SX128x { + public: + /*! + \brief Default constructor. + \param mod Instance of Module that will be used to communicate with the radio. + */ + SX1281(Module* mod); // cppcheck-suppress noExplicitConstructor + +#if !RADIOLIB_GODMODE + private: +#endif + +}; + +#endif + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/SX128x/SX1282.cpp b/software/firmware/source/libraries/RadioLib/src/modules/SX128x/SX1282.cpp new file mode 100644 index 000000000..d58ba3d37 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/SX128x/SX1282.cpp @@ -0,0 +1,9 @@ +#include "SX1282.h" +#if !RADIOLIB_EXCLUDE_SX128X + +/// \todo implement advanced ranging +SX1282::SX1282(Module* mod) : SX1280(mod) { + +} + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/SX128x/SX1282.h b/software/firmware/source/libraries/RadioLib/src/modules/SX128x/SX1282.h new file mode 100644 index 000000000..dc51ba9d8 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/SX128x/SX1282.h @@ -0,0 +1,32 @@ +#if !defined(_RADIOLIB_SX1282_H) +#define _RADIOLIB_SX1282_H + +#include "../../TypeDef.h" + +#if !RADIOLIB_EXCLUDE_SX128X + +#include "../../Module.h" +#include "SX128x.h" +#include "SX1280.h" + +/*! + \class SX1282 + \brief Derived class for %SX1282 modules. +*/ +class SX1282: public SX1280 { + public: + /*! + \brief Default constructor. + \param mod Instance of Module that will be used to communicate with the radio. + */ + SX1282(Module* mod); // cppcheck-suppress noExplicitConstructor + +#if !RADIOLIB_GODMODE + private: +#endif + +}; + +#endif + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/SX128x/SX128x.cpp b/software/firmware/source/libraries/RadioLib/src/modules/SX128x/SX128x.cpp new file mode 100644 index 000000000..37b936194 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/SX128x/SX128x.cpp @@ -0,0 +1,1678 @@ +#include "SX128x.h" +#include +#if !RADIOLIB_EXCLUDE_SX128X + +SX128x::SX128x(Module* mod) : PhysicalLayer(RADIOLIB_SX128X_FREQUENCY_STEP_SIZE, RADIOLIB_SX128X_MAX_PACKET_LENGTH) { + this->mod = mod; + this->irqMap[RADIOLIB_IRQ_TX_DONE] = RADIOLIB_SX128X_IRQ_TX_DONE; + this->irqMap[RADIOLIB_IRQ_RX_DONE] = RADIOLIB_SX128X_IRQ_RX_DONE; + this->irqMap[RADIOLIB_IRQ_PREAMBLE_DETECTED] = RADIOLIB_SX128X_IRQ_PREAMBLE_DETECTED; + this->irqMap[RADIOLIB_IRQ_SYNC_WORD_VALID] = RADIOLIB_SX128X_IRQ_SYNC_WORD_VALID; + this->irqMap[RADIOLIB_IRQ_HEADER_VALID] = RADIOLIB_SX128X_IRQ_HEADER_VALID; + this->irqMap[RADIOLIB_IRQ_HEADER_ERR] = RADIOLIB_SX128X_IRQ_HEADER_ERROR; + this->irqMap[RADIOLIB_IRQ_CRC_ERR] = RADIOLIB_SX128X_IRQ_CRC_ERROR; + this->irqMap[RADIOLIB_IRQ_CAD_DONE] = RADIOLIB_SX128X_IRQ_CAD_DONE; + this->irqMap[RADIOLIB_IRQ_CAD_DETECTED] = RADIOLIB_SX128X_IRQ_CAD_DETECTED; + this->irqMap[RADIOLIB_IRQ_TIMEOUT] = RADIOLIB_SX128X_IRQ_RX_TX_TIMEOUT; +} + +int16_t SX128x::begin(float freq, float bw, uint8_t sf, uint8_t cr, uint8_t syncWord, int8_t pwr, uint16_t preambleLength) { + // set module properties + this->mod->init(); + this->mod->hal->pinMode(this->mod->getIrq(), this->mod->hal->GpioModeInput); + this->mod->hal->pinMode(this->mod->getGpio(), this->mod->hal->GpioModeInput); + this->mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_ADDR] = Module::BITS_16; + this->mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_CMD] = Module::BITS_8; + this->mod->spiConfig.statusPos = 1; + this->mod->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_READ] = RADIOLIB_SX128X_CMD_READ_REGISTER; + this->mod->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_WRITE] = RADIOLIB_SX128X_CMD_WRITE_REGISTER; + this->mod->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_NOP] = RADIOLIB_SX128X_CMD_NOP; + this->mod->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_STATUS] = RADIOLIB_SX128X_CMD_GET_STATUS; + this->mod->spiConfig.stream = true; + this->mod->spiConfig.parseStatusCb = SPIparseStatus; + RADIOLIB_DEBUG_BASIC_PRINTLN("M\tSX128x"); + + // initialize LoRa modulation variables + this->bandwidthKhz = bw; + this->spreadingFactor = RADIOLIB_SX128X_LORA_SF_9; + this->codingRateLoRa = RADIOLIB_SX128X_LORA_CR_4_7; + + // initialize LoRa packet variables + this->preambleLengthLoRa = preambleLength; + this->headerType = RADIOLIB_SX128X_LORA_HEADER_EXPLICIT; + this->payloadLen = 0xFF; + this->crcLoRa = RADIOLIB_SX128X_LORA_CRC_ON; + + // reset the module and verify startup + int16_t state = reset(); + RADIOLIB_ASSERT(state); + + // set mode to standby + state = standby(); + RADIOLIB_ASSERT(state); + + // configure settings not accessible by API + state = config(RADIOLIB_SX128X_PACKET_TYPE_LORA); + RADIOLIB_ASSERT(state); + + // configure publicly accessible settings + state = setFrequency(freq); + RADIOLIB_ASSERT(state); + + state = setBandwidth(bw); + RADIOLIB_ASSERT(state); + + state = setSpreadingFactor(sf); + RADIOLIB_ASSERT(state); + + state = setCodingRate(cr); + RADIOLIB_ASSERT(state); + + state = setSyncWord(syncWord); + RADIOLIB_ASSERT(state); + + state = setPreambleLength(preambleLength); + RADIOLIB_ASSERT(state); + + state = setOutputPower(pwr); + RADIOLIB_ASSERT(state); + + return(state); +} + +int16_t SX128x::beginGFSK(float freq, uint16_t br, float freqDev, int8_t pwr, uint16_t preambleLength) { + // set module properties + this->mod->init(); + this->mod->hal->pinMode(this->mod->getIrq(), this->mod->hal->GpioModeInput); + this->mod->hal->pinMode(this->mod->getGpio(), this->mod->hal->GpioModeInput); + this->mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_ADDR] = Module::BITS_16; + this->mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_CMD] = Module::BITS_8; + this->mod->spiConfig.statusPos = 1; + this->mod->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_READ] = RADIOLIB_SX128X_CMD_READ_REGISTER; + this->mod->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_WRITE] = RADIOLIB_SX128X_CMD_WRITE_REGISTER; + this->mod->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_NOP] = RADIOLIB_SX128X_CMD_NOP; + this->mod->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_STATUS] = RADIOLIB_SX128X_CMD_GET_STATUS; + this->mod->spiConfig.stream = true; + this->mod->spiConfig.parseStatusCb = SPIparseStatus; + RADIOLIB_DEBUG_BASIC_PRINTLN("M\tSX128x"); + + // initialize GFSK modulation variables + this->bitRateKbps = br; + this->bitRate = RADIOLIB_SX128X_BLE_GFSK_BR_0_800_BW_2_4; + this->modIndexReal = 1.0; + this->modIndex = RADIOLIB_SX128X_BLE_GFSK_MOD_IND_1_00; + this->shaping = RADIOLIB_SX128X_BLE_GFSK_BT_0_5; + + // initialize GFSK packet variables + this->preambleLengthGFSK = preambleLength; + this->syncWordLen = 2; + this->syncWordMatch = RADIOLIB_SX128X_GFSK_FLRC_SYNC_WORD_1; + this->crcGFSK = RADIOLIB_SX128X_GFSK_FLRC_CRC_2_BYTE; + this->whitening = RADIOLIB_SX128X_GFSK_BLE_WHITENING_ON; + + // reset the module and verify startup + int16_t state = reset(); + RADIOLIB_ASSERT(state); + + // set mode to standby + state = standby(); + RADIOLIB_ASSERT(state); + + // configure settings not accessible by API + state = config(RADIOLIB_SX128X_PACKET_TYPE_GFSK); + RADIOLIB_ASSERT(state); + + // configure publicly accessible settings + state = setFrequency(freq); + RADIOLIB_ASSERT(state); + + state = setBitRate(br); + RADIOLIB_ASSERT(state); + + state = setFrequencyDeviation(freqDev); + RADIOLIB_ASSERT(state); + + state = setOutputPower(pwr); + RADIOLIB_ASSERT(state); + + state = setPreambleLength(preambleLength); + RADIOLIB_ASSERT(state); + + state = setDataShaping(RADIOLIB_SHAPING_0_5); + RADIOLIB_ASSERT(state); + + // set publicly accessible settings that are not a part of begin method + uint8_t sync[] = { 0x12, 0xAD }; + state = setSyncWord(sync, 2); + RADIOLIB_ASSERT(state); + + state = setEncoding(RADIOLIB_ENCODING_NRZ); + RADIOLIB_ASSERT(state); + + return(state); +} + +int16_t SX128x::beginBLE(float freq, uint16_t br, float freqDev, int8_t pwr, uint8_t dataShaping) { + // set module properties + this->mod->init(); + this->mod->hal->pinMode(this->mod->getIrq(), this->mod->hal->GpioModeInput); + this->mod->hal->pinMode(this->mod->getGpio(), this->mod->hal->GpioModeInput); + this->mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_ADDR] = Module::BITS_16; + this->mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_CMD] = Module::BITS_8; + this->mod->spiConfig.statusPos = 1; + this->mod->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_READ] = RADIOLIB_SX128X_CMD_READ_REGISTER; + this->mod->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_WRITE] = RADIOLIB_SX128X_CMD_WRITE_REGISTER; + this->mod->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_NOP] = RADIOLIB_SX128X_CMD_NOP; + this->mod->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_STATUS] = RADIOLIB_SX128X_CMD_GET_STATUS; + this->mod->spiConfig.stream = true; + this->mod->spiConfig.parseStatusCb = SPIparseStatus; + RADIOLIB_DEBUG_BASIC_PRINTLN("M\tSX128x"); + + // initialize BLE modulation variables + this->bitRateKbps = br; + this->bitRate = RADIOLIB_SX128X_BLE_GFSK_BR_0_800_BW_2_4; + this->modIndexReal = 1.0; + this->modIndex = RADIOLIB_SX128X_BLE_GFSK_MOD_IND_1_00; + this->shaping = RADIOLIB_SX128X_BLE_GFSK_BT_0_5; + + // initialize BLE packet variables + this->crcGFSK = RADIOLIB_SX128X_BLE_CRC_3_BYTE; + this->whitening = RADIOLIB_SX128X_GFSK_BLE_WHITENING_ON; + + // reset the module and verify startup + int16_t state = reset(); + RADIOLIB_ASSERT(state); + + // set mode to standby + state = standby(); + RADIOLIB_ASSERT(state); + + // configure settings not accessible by API + state = config(RADIOLIB_SX128X_PACKET_TYPE_BLE); + RADIOLIB_ASSERT(state); + + // configure publicly accessible settings + state = setFrequency(freq); + RADIOLIB_ASSERT(state); + + state = setBitRate(br); + RADIOLIB_ASSERT(state); + + state = setFrequencyDeviation(freqDev); + RADIOLIB_ASSERT(state); + + state = setOutputPower(pwr); + RADIOLIB_ASSERT(state); + + state = setDataShaping(dataShaping); + RADIOLIB_ASSERT(state); + + return(state); +} + +int16_t SX128x::beginFLRC(float freq, uint16_t br, uint8_t cr, int8_t pwr, uint16_t preambleLength, uint8_t dataShaping) { + // set module properties + this->mod->init(); + this->mod->hal->pinMode(this->mod->getIrq(), this->mod->hal->GpioModeInput); + this->mod->hal->pinMode(this->mod->getGpio(), this->mod->hal->GpioModeInput); + this->mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_ADDR] = Module::BITS_16; + this->mod->spiConfig.widths[RADIOLIB_MODULE_SPI_WIDTH_CMD] = Module::BITS_8; + this->mod->spiConfig.statusPos = 1; + this->mod->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_READ] = RADIOLIB_SX128X_CMD_READ_REGISTER; + this->mod->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_WRITE] = RADIOLIB_SX128X_CMD_WRITE_REGISTER; + this->mod->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_NOP] = RADIOLIB_SX128X_CMD_NOP; + this->mod->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_STATUS] = RADIOLIB_SX128X_CMD_GET_STATUS; + this->mod->spiConfig.stream = true; + this->mod->spiConfig.parseStatusCb = SPIparseStatus; + RADIOLIB_DEBUG_BASIC_PRINTLN("M\tSX128x"); + + // initialize FLRC modulation variables + this->bitRateKbps = br; + this->bitRate = RADIOLIB_SX128X_FLRC_BR_0_650_BW_0_6; + this->codingRateFLRC = RADIOLIB_SX128X_FLRC_CR_3_4; + this->shaping = RADIOLIB_SX128X_FLRC_BT_0_5; + + // initialize FLRC packet variables + this->preambleLengthGFSK = preambleLength; + this->syncWordLen = 2; + this->syncWordMatch = RADIOLIB_SX128X_GFSK_FLRC_SYNC_WORD_1; + this->crcGFSK = RADIOLIB_SX128X_GFSK_FLRC_CRC_2_BYTE; + this->whitening = RADIOLIB_SX128X_GFSK_BLE_WHITENING_OFF; + + // reset the module and verify startup + int16_t state = reset(); + RADIOLIB_ASSERT(state); + + // set mode to standby + state = standby(); + RADIOLIB_ASSERT(state); + + // configure settings not accessible by API + state = config(RADIOLIB_SX128X_PACKET_TYPE_FLRC); + RADIOLIB_ASSERT(state); + + // configure publicly accessible settings + state = setFrequency(freq); + RADIOLIB_ASSERT(state); + + state = setBitRate(br); + RADIOLIB_ASSERT(state); + + state = setCodingRate(cr); + RADIOLIB_ASSERT(state); + + state = setOutputPower(pwr); + RADIOLIB_ASSERT(state); + + state = setPreambleLength(preambleLength); + RADIOLIB_ASSERT(state); + + state = setDataShaping(dataShaping); + RADIOLIB_ASSERT(state); + + // set publicly accessible settings that are not a part of begin method + uint8_t sync[] = { 0x2D, 0x01, 0x4B, 0x1D}; + state = setSyncWord(sync, 4); + RADIOLIB_ASSERT(state); + + return(state); +} + +int16_t SX128x::reset(bool verify) { + // run the reset sequence - same as SX126x, as SX128x docs don't seem to mention this + this->mod->hal->pinMode(this->mod->getRst(), this->mod->hal->GpioModeOutput); + this->mod->hal->digitalWrite(this->mod->getRst(), this->mod->hal->GpioLevelLow); + this->mod->hal->delay(1); + this->mod->hal->digitalWrite(this->mod->getRst(), this->mod->hal->GpioLevelHigh); + + // return immediately when verification is disabled + if(!verify) { + return(RADIOLIB_ERR_NONE); + } + + // set mode to standby + RadioLibTime_t start = this->mod->hal->millis(); + while(true) { + // try to set mode to standby + int16_t state = standby(); + if(state == RADIOLIB_ERR_NONE) { + // standby command successful + return(RADIOLIB_ERR_NONE); + } + + // standby command failed, check timeout and try again + if(this->mod->hal->millis() - start >= 3000) { + // timed out, possibly incorrect wiring + return(state); + } + + // wait a bit to not spam the module + this->mod->hal->delay(10); + } +} + +int16_t SX128x::transmit(const uint8_t* data, size_t len, uint8_t addr) { + // check packet length + if(len > RADIOLIB_SX128X_MAX_PACKET_LENGTH) { + return(RADIOLIB_ERR_PACKET_TOO_LONG); + } + + // check active modem + uint8_t modem = getPacketType(); + if(modem == RADIOLIB_SX128X_PACKET_TYPE_RANGING) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // set mode to standby + int16_t state = standby(); + RADIOLIB_ASSERT(state); + + // calculate timeout in ms (500% of expected time-on-air) + RadioLibTime_t timeout = (getTimeOnAir(len) * 5) / 1000; + RADIOLIB_DEBUG_BASIC_PRINTLN("Timeout in %lu ms", timeout); + + // start transmission + state = startTransmit(data, len, addr); + RADIOLIB_ASSERT(state); + + // wait for packet transmission or timeout + RadioLibTime_t start = this->mod->hal->millis(); + while(!this->mod->hal->digitalRead(this->mod->getIrq())) { + this->mod->hal->yield(); + if(this->mod->hal->millis() - start > timeout) { + finishTransmit(); + return(RADIOLIB_ERR_TX_TIMEOUT); + } + } + + return(finishTransmit()); +} + +int16_t SX128x::receive(uint8_t* data, size_t len) { + // check active modem + uint8_t modem = getPacketType(); + if(modem == RADIOLIB_SX128X_PACKET_TYPE_RANGING) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // set mode to standby + int16_t state = standby(); + RADIOLIB_ASSERT(state); + + // calculate timeout (1000% of expected time-on-air) + RadioLibTime_t timeout = getTimeOnAir(len) * 10; + RADIOLIB_DEBUG_BASIC_PRINTLN("Timeout in %lu ms", timeout); + + // start reception + uint32_t timeoutValue = (uint32_t)((float)timeout / 15.625); + state = startReceive(timeoutValue); + RADIOLIB_ASSERT(state); + + // wait for packet reception or timeout + bool softTimeout = false; + RadioLibTime_t start = this->mod->hal->millis(); + while(!this->mod->hal->digitalRead(this->mod->getIrq())) { + this->mod->hal->yield(); + // safety check, the timeout should be done by the radio + if(this->mod->hal->millis() - start > timeout) { + softTimeout = true; + break; + } + } + + // if it was a timeout, this will return an error code + state = standby(); + if((state != RADIOLIB_ERR_NONE) && (state != RADIOLIB_ERR_SPI_CMD_TIMEOUT)) { + return(state); + } + + // check whether this was a timeout or not + if((getIrqStatus() & RADIOLIB_SX128X_IRQ_RX_TX_TIMEOUT) || softTimeout) { + standby(); + clearIrqStatus(); + return(RADIOLIB_ERR_RX_TIMEOUT); + } + + // read the received data + return(readData(data, len)); +} + +int16_t SX128x::transmitDirect(uint32_t frf) { + // set RF switch (if present) + this->mod->setRfSwitchState(Module::MODE_TX); + + // user requested to start transmitting immediately (required for RTTY) + int16_t state = RADIOLIB_ERR_NONE; + if(frf != 0) { + state = setRfFrequency(frf); + } + RADIOLIB_ASSERT(state); + + // start transmitting + return(this->mod->SPIwriteStream(RADIOLIB_SX128X_CMD_SET_TX_CONTINUOUS_WAVE, NULL, 0)); +} + +int16_t SX128x::receiveDirect() { + // set RF switch (if present) + this->mod->setRfSwitchState(Module::MODE_RX); + + // SX128x is unable to output received data directly + return(RADIOLIB_ERR_UNKNOWN); +} + +int16_t SX128x::scanChannel() { + ChannelScanConfig_t cfg = { + .cad = { + .symNum = RADIOLIB_SX128X_CAD_PARAM_DEFAULT, + .detPeak = 0, + .detMin = 0, + .exitMode = 0, + .timeout = 0, + .irqFlags = RADIOLIB_IRQ_CAD_DEFAULT_FLAGS, + .irqMask = RADIOLIB_IRQ_CAD_DEFAULT_MASK, + }, + }; + return(this->scanChannel(cfg)); +} + +int16_t SX128x::scanChannel(const ChannelScanConfig_t &config) { + // set mode to CAD + int16_t state = startChannelScan(config); + RADIOLIB_ASSERT(state); + + // wait for channel activity detected or timeout + while(!this->mod->hal->digitalRead(this->mod->getIrq())) { + this->mod->hal->yield(); + } + + // check CAD result + return(getChannelScanResult()); +} + +int16_t SX128x::sleep() { + return(SX128x::sleep(true)); +} + +int16_t SX128x::sleep(bool retainConfig) { + // set RF switch (if present) + this->mod->setRfSwitchState(Module::MODE_IDLE); + + uint8_t sleepConfig = RADIOLIB_SX128X_SLEEP_DATA_BUFFER_RETAIN | RADIOLIB_SX128X_SLEEP_DATA_RAM_RETAIN; + if(!retainConfig) { + sleepConfig = RADIOLIB_SX128X_SLEEP_DATA_BUFFER_FLUSH | RADIOLIB_SX128X_SLEEP_DATA_RAM_FLUSH; + } + int16_t state = this->mod->SPIwriteStream(RADIOLIB_SX128X_CMD_SAVE_CONTEXT, 0, 1, false, false); + RADIOLIB_ASSERT(state); + state = this->mod->SPIwriteStream(RADIOLIB_SX128X_CMD_SET_SLEEP, &sleepConfig, 1, false, false); + + // wait for SX128x to safely enter sleep mode + this->mod->hal->delay(1); + + return(state); +} + +int16_t SX128x::standby() { + return(SX128x::standby(RADIOLIB_SX128X_STANDBY_RC)); +} + +int16_t SX128x::standby(uint8_t mode, bool wakeup) { + // set RF switch (if present) + this->mod->setRfSwitchState(Module::MODE_IDLE); + + if(wakeup) { + // pull NSS low to wake up + this->mod->hal->digitalWrite(this->mod->getCs(), this->mod->hal->GpioLevelLow); + } + + uint8_t data[] = { mode }; + return(this->mod->SPIwriteStream(RADIOLIB_SX128X_CMD_SET_STANDBY, data, 1)); +} + +void SX128x::setDio1Action(void (*func)(void)) { + this->mod->hal->attachInterrupt(this->mod->hal->pinToInterrupt(this->mod->getIrq()), func, this->mod->hal->GpioInterruptRising); +} + +void SX128x::clearDio1Action() { + this->mod->hal->detachInterrupt(this->mod->hal->pinToInterrupt(this->mod->getIrq())); +} + +void SX128x::setPacketReceivedAction(void (*func)(void)) { + this->setDio1Action(func); +} + +void SX128x::clearPacketReceivedAction() { + this->clearDio1Action(); +} + +void SX128x::setPacketSentAction(void (*func)(void)) { + this->setDio1Action(func); +} + +void SX128x::clearPacketSentAction() { + this->clearDio1Action(); +} + +int16_t SX128x::startTransmit(const uint8_t* data, size_t len, uint8_t addr) { + // suppress unused variable warning + (void)addr; + + // check packet length + if(len > RADIOLIB_SX128X_MAX_PACKET_LENGTH) { + return(RADIOLIB_ERR_PACKET_TOO_LONG); + } + + // set packet Length + int16_t state = RADIOLIB_ERR_NONE; + uint8_t modem = getPacketType(); + if(modem == RADIOLIB_SX128X_PACKET_TYPE_LORA) { + state = setPacketParamsLoRa(this->preambleLengthLoRa, this->headerType, len, this->crcLoRa, this->invertIQEnabled); + } else if((modem == RADIOLIB_SX128X_PACKET_TYPE_GFSK) || (modem == RADIOLIB_SX128X_PACKET_TYPE_FLRC)) { + state = setPacketParamsGFSK(this->preambleLengthGFSK, this->syncWordLen, this->syncWordMatch, this->crcGFSK, this->whitening, len); + } else if(modem == RADIOLIB_SX128X_PACKET_TYPE_BLE) { + state = setPacketParamsBLE(this->connectionState, this->crcBLE, this->bleTestPayload, this->whitening); + } else { + return(RADIOLIB_ERR_WRONG_MODEM); + } + RADIOLIB_ASSERT(state); + + // update output power + state = setTxParams(this->power); + RADIOLIB_ASSERT(state); + + // set buffer pointers + state = setBufferBaseAddress(); + RADIOLIB_ASSERT(state); + + // write packet to buffer + if(modem == RADIOLIB_SX128X_PACKET_TYPE_BLE) { + // first 2 bytes of BLE payload are PDU header + state = writeBuffer(const_cast(data), len, 2); + RADIOLIB_ASSERT(state); + } else { + state = writeBuffer(const_cast(data), len); + RADIOLIB_ASSERT(state); + } + + // set DIO mapping + state = setDioIrqParams(RADIOLIB_SX128X_IRQ_TX_DONE | RADIOLIB_SX128X_IRQ_RX_TX_TIMEOUT, RADIOLIB_SX128X_IRQ_TX_DONE); + RADIOLIB_ASSERT(state); + + // clear interrupt flags + state = clearIrqStatus(); + RADIOLIB_ASSERT(state); + + // set RF switch (if present) + this->mod->setRfSwitchState(Module::MODE_TX); + + // start transmission + state = setTx(RADIOLIB_SX128X_TX_TIMEOUT_NONE); + RADIOLIB_ASSERT(state); + + // wait for BUSY to go low (= PA ramp up done) + while(this->mod->hal->digitalRead(this->mod->getGpio())) { + this->mod->hal->yield(); + } + + return(state); +} + +int16_t SX128x::finishTransmit() { + // clear interrupt flags + clearIrqStatus(); + + // set mode to standby to disable transmitter/RF switch + return(standby()); +} + +int16_t SX128x::startReceive() { + return(this->startReceive(RADIOLIB_SX128X_RX_TIMEOUT_INF, RADIOLIB_IRQ_RX_DEFAULT_FLAGS, RADIOLIB_IRQ_RX_DEFAULT_MASK, 0)); +} + +int16_t SX128x::startReceive(uint16_t timeout, RadioLibIrqFlags_t irqFlags, RadioLibIrqFlags_t irqMask, size_t len) { + (void)len; + + // check active modem + if(getPacketType() == RADIOLIB_SX128X_PACKET_TYPE_RANGING) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // set DIO mapping + if(timeout != RADIOLIB_SX128X_RX_TIMEOUT_INF) { + irqMask |= (1UL << RADIOLIB_IRQ_TIMEOUT); + } + + int16_t state = setDioIrqParams(getIrqMapped(irqFlags), getIrqMapped(irqMask)); + RADIOLIB_ASSERT(state); + + // set buffer pointers + state = setBufferBaseAddress(); + RADIOLIB_ASSERT(state); + + // clear interrupt flags + state = clearIrqStatus(); + RADIOLIB_ASSERT(state); + + // set implicit mode and expected len if applicable + if((this->headerType == RADIOLIB_SX128X_LORA_HEADER_IMPLICIT) && (getPacketType() == RADIOLIB_SX128X_PACKET_TYPE_LORA)) { + state = setPacketParamsLoRa(this->preambleLengthLoRa, this->headerType, this->payloadLen, this->crcLoRa, this->invertIQEnabled); + RADIOLIB_ASSERT(state); + } + + // set RF switch (if present) + this->mod->setRfSwitchState(Module::MODE_RX); + + // set mode to receive + state = setRx(timeout); + + return(state); +} + +int16_t SX128x::readData(uint8_t* data, size_t len) { + // check active modem + if(getPacketType() == RADIOLIB_SX128X_PACKET_TYPE_RANGING) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // set mode to standby + int16_t state = standby(); + RADIOLIB_ASSERT(state); + + // check integrity CRC + uint16_t irq = getIrqStatus(); + int16_t crcState = RADIOLIB_ERR_NONE; + // Report CRC mismatch when there's a payload CRC error, or a header error and no valid header (to avoid false alarm from previous packet) + if((irq & RADIOLIB_SX128X_IRQ_CRC_ERROR) || ((irq & RADIOLIB_SX128X_IRQ_HEADER_ERROR) && !(irq & RADIOLIB_SX128X_IRQ_HEADER_VALID))) { + crcState = RADIOLIB_ERR_CRC_MISMATCH; + } + + // get packet length and Rx buffer offset + uint8_t offset = 0; + size_t length = getPacketLength(true, &offset); + if((len != 0) && (len < length)) { + // user requested less data than we got, only return what was requested + length = len; + } + + // read packet data starting at offset + state = readBuffer(data, length, offset); + RADIOLIB_ASSERT(state); + + // clear interrupt flags + state = clearIrqStatus(); + + // check if CRC failed - this is done after reading data to give user the option to keep them + RADIOLIB_ASSERT(crcState); + + return(state); +} + +uint32_t SX128x::getIrqFlags() { + return((uint32_t)this->getIrqStatus()); +} + +int16_t SX128x::setIrqFlags(uint32_t irq) { + return(this->setDioIrqParams(irq, irq)); +} + +int16_t SX128x::clearIrqFlags(uint32_t irq) { + return(this->clearIrqStatus(irq)); +} + +int16_t SX128x::startChannelScan() { + ChannelScanConfig_t cfg = { + .cad = { + .symNum = RADIOLIB_SX128X_CAD_PARAM_DEFAULT, + .detPeak = 0, + .detMin = 0, + .exitMode = 0, + .timeout = 0, + .irqFlags = RADIOLIB_IRQ_CAD_DEFAULT_FLAGS, + .irqMask = RADIOLIB_IRQ_CAD_DEFAULT_MASK, + }, + }; + return(this->startChannelScan(cfg)); +} + +int16_t SX128x::startChannelScan(const ChannelScanConfig_t &config) { + // check active modem + if(getPacketType() != RADIOLIB_SX128X_PACKET_TYPE_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // set mode to standby + int16_t state = standby(); + RADIOLIB_ASSERT(state); + + // set DIO pin mapping + state = setDioIrqParams(getIrqMapped(config.cad.irqFlags), getIrqMapped(config.cad.irqMask)); + RADIOLIB_ASSERT(state); + + // clear interrupt flags + state = clearIrqStatus(); + RADIOLIB_ASSERT(state); + + // set RF switch (if present) + this->mod->setRfSwitchState(Module::MODE_RX); + + // set mode to CAD + return(setCad(config.cad.symNum)); +} + +int16_t SX128x::getChannelScanResult() { + // check active modem + if(getPacketType() != RADIOLIB_SX128X_PACKET_TYPE_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // check CAD result + uint16_t cadResult = getIrqStatus(); + int16_t state = RADIOLIB_ERR_UNKNOWN; + if(cadResult & RADIOLIB_SX128X_IRQ_CAD_DETECTED) { + // detected some LoRa activity + state = RADIOLIB_LORA_DETECTED; + } else if(cadResult & RADIOLIB_SX128X_IRQ_CAD_DONE) { + // channel is free + state = RADIOLIB_CHANNEL_FREE; + } + + clearIrqStatus(); + return(state); +} + +int16_t SX128x::setFrequency(float freq) { + RADIOLIB_CHECK_RANGE(freq, 2400.0, 2500.0, RADIOLIB_ERR_INVALID_FREQUENCY); + + // calculate raw value + uint32_t frf = (freq * (uint32_t(1) << RADIOLIB_SX128X_DIV_EXPONENT)) / RADIOLIB_SX128X_CRYSTAL_FREQ; + return(setRfFrequency(frf)); +} + +int16_t SX128x::setBandwidth(float bw) { + // check active modem + uint8_t modem = getPacketType(); + if(modem == RADIOLIB_SX128X_PACKET_TYPE_LORA) { + // check range for LoRa + RADIOLIB_CHECK_RANGE(bw, 203.125, 1625.0, RADIOLIB_ERR_INVALID_BANDWIDTH); + } else if(modem == RADIOLIB_SX128X_PACKET_TYPE_RANGING) { + // check range for ranging + RADIOLIB_CHECK_RANGE(bw, 406.25, 1625.0, RADIOLIB_ERR_INVALID_BANDWIDTH); + } else { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + if(fabsf(bw - 203.125) <= 0.001) { + this->bandwidth = RADIOLIB_SX128X_LORA_BW_203_125; + } else if(fabsf(bw - 406.25) <= 0.001) { + this->bandwidth = RADIOLIB_SX128X_LORA_BW_406_25; + } else if(fabsf(bw - 812.5) <= 0.001) { + this->bandwidth = RADIOLIB_SX128X_LORA_BW_812_50; + } else if(fabsf(bw - 1625.0) <= 0.001) { + this->bandwidth = RADIOLIB_SX128X_LORA_BW_1625_00; + } else { + return(RADIOLIB_ERR_INVALID_BANDWIDTH); + } + + // update modulation parameters + this->bandwidthKhz = bw; + return(setModulationParams(this->spreadingFactor, this->bandwidth, this->codingRateLoRa)); +} + +int16_t SX128x::setSpreadingFactor(uint8_t sf) { + // check active modem + uint8_t modem = getPacketType(); + if(modem == RADIOLIB_SX128X_PACKET_TYPE_LORA) { + // check range for LoRa + RADIOLIB_CHECK_RANGE(sf, 5, 12, RADIOLIB_ERR_INVALID_SPREADING_FACTOR); + } else if(modem == RADIOLIB_SX128X_PACKET_TYPE_RANGING) { + // check range for ranging + RADIOLIB_CHECK_RANGE(sf, 5, 10, RADIOLIB_ERR_INVALID_SPREADING_FACTOR); + } else { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // update modulation parameters + this->spreadingFactor = sf << 4; + int16_t state = setModulationParams(this->spreadingFactor, this->bandwidth, this->codingRateLoRa); + RADIOLIB_ASSERT(state); + + // update mystery register in LoRa mode - SX1280 datasheet v3.0 section 13.4.1 + if(modem == RADIOLIB_SX128X_PACKET_TYPE_LORA) { + uint8_t data = 0; + if((this->spreadingFactor == RADIOLIB_SX128X_LORA_SF_5) || (this->spreadingFactor == RADIOLIB_SX128X_LORA_SF_6)) { + data = 0x1E; + } else if((this->spreadingFactor == RADIOLIB_SX128X_LORA_SF_7) || (this->spreadingFactor == RADIOLIB_SX128X_LORA_SF_8)) { + data = 0x37; + } else { + data = 0x32; + } + state = SX128x::writeRegister(RADIOLIB_SX128X_REG_LORA_SF_CONFIG, &data, 1); + } + + return(state); +} + +int16_t SX128x::setCodingRate(uint8_t cr, bool longInterleaving) { + // check active modem + uint8_t modem = getPacketType(); + + // LoRa/ranging + if((modem == RADIOLIB_SX128X_PACKET_TYPE_LORA) || (modem == RADIOLIB_SX128X_PACKET_TYPE_RANGING)) { + RADIOLIB_CHECK_RANGE(cr, 5, 8, RADIOLIB_ERR_INVALID_CODING_RATE); + + // update modulation parameters + if(longInterleaving && (modem == RADIOLIB_SX128X_PACKET_TYPE_LORA)) { + this->codingRateLoRa = cr; + } else { + this->codingRateLoRa = cr - 4; + } + return(setModulationParams(this->spreadingFactor, this->bandwidth, this->codingRateLoRa)); + + // FLRC + } else if(modem == RADIOLIB_SX128X_PACKET_TYPE_FLRC) { + RADIOLIB_CHECK_RANGE(cr, 2, 4, RADIOLIB_ERR_INVALID_CODING_RATE); + + // update modulation parameters + this->codingRateFLRC = (cr - 2) * 2; + return(setModulationParams(this->bitRate, this->codingRateFLRC, this->shaping)); + } + + return(RADIOLIB_ERR_WRONG_MODEM); +} + +int16_t SX128x::setOutputPower(int8_t pwr) { + // check if power value is configurable + int16_t state = checkOutputPower(pwr, NULL); + RADIOLIB_ASSERT(state); + + this->power = pwr + 18; + return(setTxParams(this->power)); +} + +int16_t SX128x::checkOutputPower(int8_t pwr, int8_t* clipped) { + if(clipped) { + *clipped = RADIOLIB_MAX(-18, RADIOLIB_MIN(13, pwr)); + } + RADIOLIB_CHECK_RANGE(pwr, -18, 13, RADIOLIB_ERR_INVALID_OUTPUT_POWER); + return(RADIOLIB_ERR_NONE); +} + +int16_t SX128x::setModem(ModemType_t modem) { + switch(modem) { + case(ModemType_t::RADIOLIB_MODEM_LORA): { + return(this->begin()); + } break; + case(ModemType_t::RADIOLIB_MODEM_FSK): { + return(this->beginGFSK()); + } break; + default: + return(RADIOLIB_ERR_WRONG_MODEM); + } +} + +int16_t SX128x::getModem(ModemType_t* modem) { + RADIOLIB_ASSERT_PTR(modem); + + uint8_t packetType = getPacketType(); + switch(packetType) { + case(RADIOLIB_SX128X_PACKET_TYPE_LORA): + *modem = ModemType_t::RADIOLIB_MODEM_LORA; + return(RADIOLIB_ERR_NONE); + case(RADIOLIB_SX128X_PACKET_TYPE_GFSK): + *modem = ModemType_t::RADIOLIB_MODEM_FSK; + return(RADIOLIB_ERR_NONE); + } + + return(RADIOLIB_ERR_WRONG_MODEM); +} + +int16_t SX128x::setPreambleLength(uint32_t preambleLength) { + uint8_t modem = getPacketType(); + if((modem == RADIOLIB_SX128X_PACKET_TYPE_LORA) || (modem == RADIOLIB_SX128X_PACKET_TYPE_RANGING)) { + // LoRa or ranging + RADIOLIB_CHECK_RANGE(preambleLength, 2, 491520, RADIOLIB_ERR_INVALID_PREAMBLE_LENGTH); + + // check preamble length is even - no point even trying odd numbers + if(preambleLength % 2 != 0) { + return(RADIOLIB_ERR_INVALID_PREAMBLE_LENGTH); + } + + // calculate exponent and mantissa values (use the next longer preamble if there's no exact match) + uint8_t e = 1; + uint8_t m = 1; + uint32_t len = 0; + for(; e <= 15; e++) { + for(m = 1; m <= 15; m++) { + len = m * (uint32_t(1) << e); + if(len >= preambleLength) { + break; + } + } + if(len >= preambleLength) { + break; + } + } + + // update packet parameters + this->preambleLengthLoRa = (e << 4) | m; + return(setPacketParamsLoRa(this->preambleLengthLoRa, this->headerType, this->payloadLen, this->crcLoRa, this->invertIQEnabled)); + + } else if((modem == RADIOLIB_SX128X_PACKET_TYPE_GFSK) || (modem == RADIOLIB_SX128X_PACKET_TYPE_FLRC)) { + // GFSK or FLRC + RADIOLIB_CHECK_RANGE(preambleLength, 4, 32, RADIOLIB_ERR_INVALID_PREAMBLE_LENGTH); + + // check preamble length is multiple of 4 + if(preambleLength % 4 != 0) { + return(RADIOLIB_ERR_INVALID_PREAMBLE_LENGTH); + } + + // update packet parameters + this->preambleLengthGFSK = ((preambleLength / 4) - 1) << 4; + return(setPacketParamsGFSK(this->preambleLengthGFSK, this->syncWordLen, this->syncWordMatch, this->crcGFSK, this->whitening)); + } + + return(RADIOLIB_ERR_WRONG_MODEM); +} + +int16_t SX128x::setDataRate(DataRate_t dr) { + // check active modem + uint8_t modem = getPacketType(); + int16_t state = RADIOLIB_ERR_NONE; + if (modem == RADIOLIB_SX128X_PACKET_TYPE_LORA) { + state = this->setBandwidth(dr.lora.bandwidth); + RADIOLIB_ASSERT(state); + state = this->setSpreadingFactor(dr.lora.spreadingFactor); + RADIOLIB_ASSERT(state); + state = this->setCodingRate(dr.lora.codingRate); + } else { + return(RADIOLIB_ERR_WRONG_MODEM); + } + return(state); +} + +int16_t SX128x::setBitRate(float br) { + // check active modem + uint8_t modem = getPacketType(); + + // GFSK/BLE + if((modem == RADIOLIB_SX128X_PACKET_TYPE_GFSK) || (modem == RADIOLIB_SX128X_PACKET_TYPE_BLE)) { + if((uint16_t)br == 125) { + this->bitRate = RADIOLIB_SX128X_BLE_GFSK_BR_0_125_BW_0_3; + } else if((uint16_t)br == 250) { + this->bitRate = RADIOLIB_SX128X_BLE_GFSK_BR_0_250_BW_0_6; + } else if((uint16_t)br == 400) { + this->bitRate = RADIOLIB_SX128X_BLE_GFSK_BR_0_400_BW_1_2; + } else if((uint16_t)br == 500) { + this->bitRate = RADIOLIB_SX128X_BLE_GFSK_BR_0_500_BW_1_2; + } else if((uint16_t)br == 800) { + this->bitRate = RADIOLIB_SX128X_BLE_GFSK_BR_0_800_BW_2_4; + } else if((uint16_t)br == 1000) { + this->bitRate = RADIOLIB_SX128X_BLE_GFSK_BR_1_000_BW_2_4; + } else if((uint16_t)br == 1600) { + this->bitRate = RADIOLIB_SX128X_BLE_GFSK_BR_1_600_BW_2_4; + } else if((uint16_t)br == 2000) { + this->bitRate = RADIOLIB_SX128X_BLE_GFSK_BR_2_000_BW_2_4; + } else { + return(RADIOLIB_ERR_INVALID_BIT_RATE); + } + + // update modulation parameters + this->bitRateKbps = (uint16_t)br; + return(setModulationParams(this->bitRate, this->modIndex, this->shaping)); + + // FLRC + } else if(modem == RADIOLIB_SX128X_PACKET_TYPE_FLRC) { + if((uint16_t)br == 260) { + this->bitRate = RADIOLIB_SX128X_FLRC_BR_0_260_BW_0_3; + } else if((uint16_t)br == 325) { + this->bitRate = RADIOLIB_SX128X_FLRC_BR_0_325_BW_0_3; + } else if((uint16_t)br == 520) { + this->bitRate = RADIOLIB_SX128X_FLRC_BR_0_520_BW_0_6; + } else if((uint16_t)br == 650) { + this->bitRate = RADIOLIB_SX128X_FLRC_BR_0_650_BW_0_6; + } else if((uint16_t)br == 1000) { + this->bitRate = RADIOLIB_SX128X_FLRC_BR_1_000_BW_1_2; + } else if((uint16_t)br == 1300) { + this->bitRate = RADIOLIB_SX128X_FLRC_BR_1_300_BW_1_2; + } else { + return(RADIOLIB_ERR_INVALID_BIT_RATE); + } + + // update modulation parameters + this->bitRateKbps = (uint16_t)br; + return(setModulationParams(this->bitRate, this->codingRateFLRC, this->shaping)); + + } + + return(RADIOLIB_ERR_WRONG_MODEM); +} + +int16_t SX128x::setFrequencyDeviation(float freqDev) { + // check active modem + uint8_t modem = getPacketType(); + if(!((modem == RADIOLIB_SX128X_PACKET_TYPE_GFSK) || (modem == RADIOLIB_SX128X_PACKET_TYPE_BLE))) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // set frequency deviation to lowest available setting (required for digimodes) + float newFreqDev = freqDev; + if(freqDev < 0.0) { + newFreqDev = 62.5; + } + + RADIOLIB_CHECK_RANGE(newFreqDev, 62.5, 1000.0, RADIOLIB_ERR_INVALID_FREQUENCY_DEVIATION); + + // override for the lowest possible frequency deviation - required for some PhysicalLayer protocols + if(newFreqDev == 0.0) { + this->modIndex = RADIOLIB_SX128X_BLE_GFSK_MOD_IND_0_35; + this->bitRate = RADIOLIB_SX128X_BLE_GFSK_BR_0_125_BW_0_3; + return(setModulationParams(this->bitRate, this->modIndex, this->shaping)); + } + + // update modulation parameters + uint8_t modInd = (uint8_t)((8.0 * (newFreqDev / (float)this->bitRateKbps)) - 1.0); + if(modInd > RADIOLIB_SX128X_BLE_GFSK_MOD_IND_4_00) { + return(RADIOLIB_ERR_INVALID_MODULATION_PARAMETERS); + } + + // update modulation parameters + this->modIndex = modInd; + return(setModulationParams(this->bitRate, this->modIndex, this->shaping)); +} + +int16_t SX128x::setDataShaping(uint8_t sh) { + // check active modem + uint8_t modem = getPacketType(); + if(!((modem == RADIOLIB_SX128X_PACKET_TYPE_GFSK) || (modem == RADIOLIB_SX128X_PACKET_TYPE_BLE) || (modem == RADIOLIB_SX128X_PACKET_TYPE_FLRC))) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // set data this->shaping + switch(sh) { + case RADIOLIB_SHAPING_NONE: + this->shaping = RADIOLIB_SX128X_BLE_GFSK_BT_OFF; + break; + case RADIOLIB_SHAPING_0_5: + this->shaping = RADIOLIB_SX128X_BLE_GFSK_BT_0_5; + break; + case RADIOLIB_SHAPING_1_0: + this->shaping = RADIOLIB_SX128X_BLE_GFSK_BT_1_0; + break; + default: + return(RADIOLIB_ERR_INVALID_DATA_SHAPING); + } + + // update modulation parameters + if((modem == RADIOLIB_SX128X_PACKET_TYPE_GFSK) || (modem == RADIOLIB_SX128X_PACKET_TYPE_BLE)) { + return(setModulationParams(this->bitRate, this->modIndex, this->shaping)); + } else { + return(setModulationParams(this->bitRate, this->codingRateFLRC, this->shaping)); + } +} + +int16_t SX128x::setSyncWord(const uint8_t* syncWord, uint8_t len) { + // check active modem + uint8_t modem = getPacketType(); + if(!((modem == RADIOLIB_SX128X_PACKET_TYPE_GFSK) || (modem == RADIOLIB_SX128X_PACKET_TYPE_FLRC))) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + if(modem == RADIOLIB_SX128X_PACKET_TYPE_GFSK) { + // GFSK can use up to 5 bytes as sync word + if(len > 5) { + return(RADIOLIB_ERR_INVALID_SYNC_WORD); + } + + // calculate sync word length parameter value + if(len > 0) { + this->syncWordLen = (len - 1)*2; + } + + } else { + // FLRC requires 32-bit sync word + if(!((len == 0) || (len == 4))) { + return(RADIOLIB_ERR_INVALID_SYNC_WORD); + } + + // save sync word length parameter value + this->syncWordLen = len; + } + + // reverse sync word byte order + uint8_t syncWordBuff[] = { 0x00, 0x00, 0x00, 0x00, 0x00 }; + for(uint8_t i = 0; i < len; i++) { + syncWordBuff[4 - i] = syncWord[i]; + } + + // update sync word + int16_t state = SX128x::writeRegister(RADIOLIB_SX128X_REG_SYNC_WORD_1_BYTE_4, syncWordBuff, 5); + RADIOLIB_ASSERT(state); + + // update packet parameters + if(this->syncWordLen == 0) { + this->syncWordMatch = RADIOLIB_SX128X_GFSK_FLRC_SYNC_WORD_OFF; + } else { + /// \todo add support for multiple sync words + this->syncWordMatch = RADIOLIB_SX128X_GFSK_FLRC_SYNC_WORD_1; + } + return(setPacketParamsGFSK(this->preambleLengthGFSK, this->syncWordLen, this->syncWordMatch, this->crcGFSK, this->whitening)); +} + +int16_t SX128x::setSyncWord(uint8_t syncWord, uint8_t controlBits) { + // check active modem + if(getPacketType() != RADIOLIB_SX128X_PACKET_TYPE_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // update register + uint8_t data[2] = {(uint8_t)((syncWord & 0xF0) | ((controlBits & 0xF0) >> 4)), (uint8_t)(((syncWord & 0x0F) << 4) | (controlBits & 0x0F))}; + return(writeRegister(RADIOLIB_SX128X_REG_LORA_SYNC_WORD_MSB, data, 2)); +} + +int16_t SX128x::setCRC(uint8_t len, uint32_t initial, uint16_t polynomial) { + // check active modem + uint8_t modem = getPacketType(); + + int16_t state; + if((modem == RADIOLIB_SX128X_PACKET_TYPE_GFSK) || (modem == RADIOLIB_SX128X_PACKET_TYPE_FLRC)) { + // update packet parameters + if(modem == RADIOLIB_SX128X_PACKET_TYPE_GFSK) { + if(len > 2) { + return(RADIOLIB_ERR_INVALID_CRC_CONFIGURATION); + } + } else { + if(len > 3) { + return(RADIOLIB_ERR_INVALID_CRC_CONFIGURATION); + } + } + this->crcGFSK = len << 4; + state = setPacketParamsGFSK(this->preambleLengthGFSK, this->syncWordLen, this->syncWordMatch, this->crcGFSK, this->whitening); + RADIOLIB_ASSERT(state); + + // set initial CRC value + uint8_t data[] = { (uint8_t)((initial >> 8) & 0xFF), (uint8_t)(initial & 0xFF) }; + state = writeRegister(RADIOLIB_SX128X_REG_CRC_INITIAL_MSB, data, 2); + RADIOLIB_ASSERT(state); + + // set CRC polynomial + data[0] = (uint8_t)((polynomial >> 8) & 0xFF); + data[1] = (uint8_t)(polynomial & 0xFF); + state = writeRegister(RADIOLIB_SX128X_REG_CRC_POLYNOMIAL_MSB, data, 2); + return(state); + + } else if(modem == RADIOLIB_SX128X_PACKET_TYPE_BLE) { + // update packet parameters + if(len == 0) { + this->crcBLE = RADIOLIB_SX128X_BLE_CRC_OFF; + } else if(len == 3) { + this->crcBLE = RADIOLIB_SX128X_BLE_CRC_3_BYTE; + } else { + return(RADIOLIB_ERR_INVALID_CRC_CONFIGURATION); + } + state = setPacketParamsBLE(this->connectionState, this->crcBLE, this->bleTestPayload, this->whitening); + RADIOLIB_ASSERT(state); + + // set initial CRC value + uint8_t data[] = { (uint8_t)((initial >> 16) & 0xFF), (uint8_t)((initial >> 8) & 0xFF), (uint8_t)(initial & 0xFF) }; + state = writeRegister(RADIOLIB_SX128X_REG_BLE_CRC_INITIAL_MSB, data, 3); + return(state); + + } else if((modem == RADIOLIB_SX128X_PACKET_TYPE_LORA) || (modem == RADIOLIB_SX128X_PACKET_TYPE_RANGING)) { + // update packet parameters + if(len == 0) { + this->crcLoRa = RADIOLIB_SX128X_LORA_CRC_OFF; + } else if(len == 2) { + this->crcLoRa = RADIOLIB_SX128X_LORA_CRC_ON; + } else { + return(RADIOLIB_ERR_INVALID_CRC_CONFIGURATION); + } + state = setPacketParamsLoRa(this->preambleLengthLoRa, this->headerType, this->payloadLen, this->crcLoRa, this->invertIQEnabled); + return(state); + } + + return(RADIOLIB_ERR_UNKNOWN); +} + +int16_t SX128x::setWhitening(bool enabled) { + // check active modem + uint8_t modem = getPacketType(); + if(!((modem == RADIOLIB_SX128X_PACKET_TYPE_GFSK) || (modem == RADIOLIB_SX128X_PACKET_TYPE_BLE))) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // update packet parameters + if(enabled) { + this->whitening = RADIOLIB_SX128X_GFSK_BLE_WHITENING_ON; + } else { + this->whitening = RADIOLIB_SX128X_GFSK_BLE_WHITENING_OFF; + } + + if(modem == RADIOLIB_SX128X_PACKET_TYPE_GFSK) { + return(setPacketParamsGFSK(this->preambleLengthGFSK, this->syncWordLen, this->syncWordMatch, this->crcGFSK, this->whitening)); + } + return(setPacketParamsBLE(this->connectionState, this->crcBLE, this->bleTestPayload, this->whitening)); +} + +int16_t SX128x::setAccessAddress(uint32_t addr) { + // check active modem + if(getPacketType() != RADIOLIB_SX128X_PACKET_TYPE_BLE) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // set the address + uint8_t addrBuff[] = { (uint8_t)((addr >> 24) & 0xFF), (uint8_t)((addr >> 16) & 0xFF), (uint8_t)((addr >> 8) & 0xFF), (uint8_t)(addr & 0xFF) }; + return(SX128x::writeRegister(RADIOLIB_SX128X_REG_ACCESS_ADDRESS_BYTE_3, addrBuff, 4)); +} + +int16_t SX128x::setHighSensitivityMode(bool enable) { + // read the current registers + uint8_t RxGain = 0; + int16_t state = readRegister(RADIOLIB_SX128X_REG_GAIN_MODE, &RxGain, 1); + RADIOLIB_ASSERT(state); + + if(enable) { + RxGain |= 0xC0; // Set bits 6 and 7 + } else { + RxGain &= ~0xC0; // Unset bits 6 and 7 + } + + // update all values + state = writeRegister(RADIOLIB_SX128X_REG_GAIN_MODE, &RxGain, 1); + return(state); +} + +int16_t SX128x::setGainControl(uint8_t gain) { + // read the current registers + uint8_t ManualGainSetting = 0; + int16_t state = readRegister(RADIOLIB_SX128X_REG_MANUAL_GAIN_CONTROL_ENABLE_2, &ManualGainSetting, 1); + RADIOLIB_ASSERT(state); + uint8_t LNAGainValue = 0; + state = readRegister(RADIOLIB_SX128X_REG_MANUAL_GAIN_SETTING, &LNAGainValue, 1); + RADIOLIB_ASSERT(state); + uint8_t LNAGainControl = 0; + state = readRegister(RADIOLIB_SX128X_REG_MANUAL_GAIN_CONTROL_ENABLE_1, &LNAGainControl, 1); + RADIOLIB_ASSERT(state); + + // set the gain + if (gain > 0 && gain < 14) { + // Set manual gain + ManualGainSetting &= ~0x01; // Set bit 0 to 0 (Enable Manual Gain Control) + LNAGainValue &= 0xF0; // Bits 0, 1, 2 and 3 to 0 + LNAGainValue |= gain; // Set bits 0, 1, 2 and 3 to Manual Gain Setting (1-13) + LNAGainControl |= 0x80; // Set bit 7 to 1 (Enable Manual Gain Control) + } else { + // Set automatic gain if 0 or out of range + ManualGainSetting |= 0x01; // Set bit 0 to 1 (Enable Automatic Gain Control) + LNAGainValue &= 0xF0; // Bits 0, 1, 2 and 3 to 0 + LNAGainValue |= 0x0A; // Set bits 0, 1, 2 and 3 to Manual Gain Setting (1-13) + LNAGainControl &= ~0x80; // Set bit 7 to 0 (Enable Automatic Gain Control) + } + + // update all values + state = writeRegister(RADIOLIB_SX128X_REG_MANUAL_GAIN_CONTROL_ENABLE_2, &ManualGainSetting, 1); + RADIOLIB_ASSERT(state); + state = writeRegister(RADIOLIB_SX128X_REG_MANUAL_GAIN_SETTING, &LNAGainValue, 1); + RADIOLIB_ASSERT(state); + state = writeRegister(RADIOLIB_SX128X_REG_MANUAL_GAIN_CONTROL_ENABLE_1, &LNAGainControl, 1); + return(state); +} + +float SX128x::getRSSI() { + // get packet status + uint8_t packetStatus[5]; + this->mod->SPIreadStream(RADIOLIB_SX128X_CMD_GET_PACKET_STATUS, packetStatus, 5); + + // check active modem + uint8_t modem = getPacketType(); + if((modem == RADIOLIB_SX128X_PACKET_TYPE_LORA) || (modem == RADIOLIB_SX128X_PACKET_TYPE_RANGING)) { + // LoRa or ranging + uint8_t rssiSync = packetStatus[0]; + float rssiMeasured = -1.0 * rssiSync/2.0; + float snr = getSNR(); + if(snr <= 0.0) { + return(rssiMeasured - snr); + } else { + return(rssiMeasured); + } + } else { + // GFSK, BLE or FLRC + uint8_t rssiSync = packetStatus[1]; + return(-1.0 * rssiSync/2.0); + } +} + +float SX128x::getRSSI(bool packet) { + if (!packet) { + // get instantaneous RSSI value + uint8_t data[3] = {0, 0, 0}; // RssiInst, Status, RFU + this->mod->SPIreadStream(RADIOLIB_SX128X_CMD_GET_RSSI_INST, data, 3); + return ((float)data[0] / (-2.0)); + } else { + return this->getRSSI(); + } +} + +float SX128x::getSNR() { + // check active modem + uint8_t modem = getPacketType(); + if(!((modem == RADIOLIB_SX128X_PACKET_TYPE_LORA) || (modem == RADIOLIB_SX128X_PACKET_TYPE_RANGING))) { + return(0.0); + } + + // get packet status + uint8_t packetStatus[5]; + this->mod->SPIreadStream(RADIOLIB_SX128X_CMD_GET_PACKET_STATUS, packetStatus, 5); + + // calculate real SNR + uint8_t snr = packetStatus[1]; + if(snr < 128) { + return(snr/4.0); + } else { + return((snr - 256)/4.0); + } +} + +float SX128x::getFrequencyError() { + // check active modem + uint8_t modem = getPacketType(); + if(!((modem == RADIOLIB_SX128X_PACKET_TYPE_LORA) || (modem == RADIOLIB_SX128X_PACKET_TYPE_RANGING))) { + return(0.0); + } + + // read the raw frequency error register values + uint8_t efeRaw[3] = {0}; + int16_t state = readRegister(RADIOLIB_SX128X_REG_FEI_MSB, &efeRaw[0], 1); + RADIOLIB_ASSERT(state); + state = readRegister(RADIOLIB_SX128X_REG_FEI_MID, &efeRaw[1], 1); + RADIOLIB_ASSERT(state); + state = readRegister(RADIOLIB_SX128X_REG_FEI_LSB, &efeRaw[2], 1); + RADIOLIB_ASSERT(state); + uint32_t efe = ((uint32_t) efeRaw[0] << 16) | ((uint32_t) efeRaw[1] << 8) | efeRaw[2]; + efe &= 0x0FFFFF; + + float error = 0; + + // check the first bit + if (efe & 0x80000) { + // frequency error is negative + efe |= (uint32_t) 0xFFF00000; + efe = ~efe + 1; + error = 1.55 * (float) efe / (1600.0 / (float) this->bandwidthKhz) * -1.0; + } else { + error = 1.55 * (float) efe / (1600.0 / (float) this->bandwidthKhz); + } + + return(error); +} + +size_t SX128x::getPacketLength(bool update) { + return(this->getPacketLength(update, NULL)); +} + +size_t SX128x::getPacketLength(bool update, uint8_t* offset) { + (void)update; + + // in implicit mode, return the cached value + if((getPacketType() == RADIOLIB_SX128X_PACKET_TYPE_LORA) && (this->headerType == RADIOLIB_SX128X_LORA_HEADER_IMPLICIT)) { + return(this->payloadLen); + } + + uint8_t rxBufStatus[2] = {0, 0}; + this->mod->SPIreadStream(RADIOLIB_SX128X_CMD_GET_RX_BUFFER_STATUS, rxBufStatus, 2); + + if(offset) { *offset = rxBufStatus[1]; } + + return((size_t)rxBufStatus[0]); +} + +RadioLibTime_t SX128x::getTimeOnAir(size_t len) { + // check active modem + uint8_t modem = getPacketType(); + if(modem == RADIOLIB_SX128X_PACKET_TYPE_LORA) { + // calculate number of symbols + float N_symbol = 0; + uint8_t sf = this->spreadingFactor >> 4; + if(this->codingRateLoRa <= RADIOLIB_SX128X_LORA_CR_4_8) { + // legacy coding rate - nice and simple + + // get SF coefficients + float coeff1 = 0; + int16_t coeff2 = 0; + int16_t coeff3 = 0; + if(sf < 7) { + // SF5, SF6 + coeff1 = 6.25; + coeff2 = 4*sf; + coeff3 = 4*sf; + } else if(sf < 11) { + // SF7. SF8, SF9, SF10 + coeff1 = 4.25; + coeff2 = 4*sf + 8; + coeff3 = 4*sf; + } else { + // SF11, SF12 + coeff1 = 4.25; + coeff2 = 4*sf + 8; + coeff3 = 4*(sf - 2); + } + + // get CRC length + int16_t N_bitCRC = 16; + if(this->crcLoRa == RADIOLIB_SX128X_LORA_CRC_OFF) { + N_bitCRC = 0; + } + + // get header length + int16_t N_symbolHeader = 20; + if(this->headerType == RADIOLIB_SX128X_LORA_HEADER_IMPLICIT) { + N_symbolHeader = 0; + } + + // calculate number of LoRa preamble symbols + uint32_t N_symbolPreamble = (this->preambleLengthLoRa & 0x0F) * (uint32_t(1) << ((this->preambleLengthLoRa & 0xF0) >> 4)); + + // calculate the number of symbols + N_symbol = (float)N_symbolPreamble + coeff1 + 8.0 + ceilf((float)RADIOLIB_MAX((int16_t)(8 * len + N_bitCRC - coeff2 + N_symbolHeader), (int16_t)0) / (float)coeff3) * (float)(this->codingRateLoRa + 4); + + } else { + // long interleaving - abandon hope all ye who enter here + /// \todo implement this mess - SX1280 datasheet v3.0 section 7.4.4.2 + + } + + // get time-on-air in us + return(((uint32_t(1) << sf) / this->bandwidthKhz) * N_symbol * 1000.0); + + } else { + return(((uint32_t)len * 8 * 1000) / this->bitRateKbps); + } + +} + +int16_t SX128x::implicitHeader(size_t len) { + return(setHeaderType(RADIOLIB_SX128X_LORA_HEADER_IMPLICIT, len)); +} + +int16_t SX128x::explicitHeader() { + return(setHeaderType(RADIOLIB_SX128X_LORA_HEADER_EXPLICIT)); +} + +int16_t SX128x::setEncoding(uint8_t encoding) { + return(setWhitening(encoding)); +} + +void SX128x::setRfSwitchPins(uint32_t rxEn, uint32_t txEn) { + this->mod->setRfSwitchPins(rxEn, txEn); +} + +void SX128x::setRfSwitchTable(const uint32_t (&pins)[Module::RFSWITCH_MAX_PINS], const Module::RfSwitchMode_t table[]) { + this->mod->setRfSwitchTable(pins, table); +} + +uint8_t SX128x::randomByte() { + // it's unclear whether SX128x can measure RSSI while not receiving a packet + // this method is implemented only for PhysicalLayer compatibility + return(0); +} + +int16_t SX128x::invertIQ(bool enable) { + if(getPacketType() != RADIOLIB_SX128X_PACKET_TYPE_LORA) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + if(enable) { + this->invertIQEnabled = RADIOLIB_SX128X_LORA_IQ_INVERTED; + } else { + this->invertIQEnabled = RADIOLIB_SX128X_LORA_IQ_STANDARD; + } + + return(setPacketParamsLoRa(this->preambleLengthLoRa, this->headerType, this->payloadLen, this->crcLoRa, this->invertIQEnabled)); +} + +#if !RADIOLIB_EXCLUDE_DIRECT_RECEIVE +void SX128x::setDirectAction(void (*func)(void)) { + // SX128x is unable to perform direct mode reception + // this method is implemented only for PhysicalLayer compatibility + (void)func; +} + +void SX128x::readBit(uint32_t pin) { + // SX128x is unable to perform direct mode reception + // this method is implemented only for PhysicalLayer compatibility + (void)pin; +} +#endif + +Module* SX128x::getMod() { + return(this->mod); +} + +uint8_t SX128x::getStatus() { + uint8_t data = 0; + this->mod->SPIreadStream(RADIOLIB_SX128X_CMD_GET_STATUS, &data, 0); + return(data); +} + +int16_t SX128x::writeRegister(uint16_t addr, uint8_t* data, uint8_t numBytes) { + this->mod->SPIwriteRegisterBurst(addr, data, numBytes); + return(RADIOLIB_ERR_NONE); +} + +int16_t SX128x::readRegister(uint16_t addr, uint8_t* data, uint8_t numBytes) { + // send the command + this->mod->SPIreadRegisterBurst(addr, numBytes, data); + + // check the status + int16_t state = this->mod->SPIcheckStream(); + return(state); +} + +int16_t SX128x::writeBuffer(uint8_t* data, uint8_t numBytes, uint8_t offset) { + uint8_t cmd[] = { RADIOLIB_SX128X_CMD_WRITE_BUFFER, offset }; + return(this->mod->SPIwriteStream(cmd, 2, data, numBytes)); +} + +int16_t SX128x::readBuffer(uint8_t* data, uint8_t numBytes, uint8_t offset) { + uint8_t cmd[] = { RADIOLIB_SX128X_CMD_READ_BUFFER, offset }; + return(this->mod->SPIreadStream(cmd, 2, data, numBytes)); +} + +int16_t SX128x::setTx(uint16_t periodBaseCount, uint8_t periodBase) { + uint8_t data[] = { periodBase, (uint8_t)((periodBaseCount >> 8) & 0xFF), (uint8_t)(periodBaseCount & 0xFF) }; + return(this->mod->SPIwriteStream(RADIOLIB_SX128X_CMD_SET_TX, data, 3)); +} + +int16_t SX128x::setRx(uint16_t periodBaseCount, uint8_t periodBase) { + uint8_t data[] = { periodBase, (uint8_t)((periodBaseCount >> 8) & 0xFF), (uint8_t)(periodBaseCount & 0xFF) }; + return(this->mod->SPIwriteStream(RADIOLIB_SX128X_CMD_SET_RX, data, 3)); +} + +int16_t SX128x::setCad(uint8_t symbolNum) { + // configure parameters + int16_t state = this->mod->SPIwriteStream(RADIOLIB_SX128X_CMD_SET_CAD_PARAMS, &symbolNum, 1); + RADIOLIB_ASSERT(state); + + // start CAD + return(this->mod->SPIwriteStream(RADIOLIB_SX128X_CMD_SET_CAD, NULL, 0)); +} + +uint8_t SX128x::getPacketType() { + uint8_t data = 0xFF; + this->mod->SPIreadStream(RADIOLIB_SX128X_CMD_GET_PACKET_TYPE, &data, 1); + return(data); +} + +int16_t SX128x::setRfFrequency(uint32_t frf) { + uint8_t data[] = { (uint8_t)((frf >> 16) & 0xFF), (uint8_t)((frf >> 8) & 0xFF), (uint8_t)(frf & 0xFF) }; + return(this->mod->SPIwriteStream(RADIOLIB_SX128X_CMD_SET_RF_FREQUENCY, data, 3)); +} + +int16_t SX128x::setTxParams(uint8_t pwr, uint8_t rampTime) { + uint8_t data[] = { pwr, rampTime }; + return(this->mod->SPIwriteStream(RADIOLIB_SX128X_CMD_SET_TX_PARAMS, data, 2)); +} + +int16_t SX128x::setBufferBaseAddress(uint8_t txBaseAddress, uint8_t rxBaseAddress) { + uint8_t data[] = { txBaseAddress, rxBaseAddress }; + return(this->mod->SPIwriteStream(RADIOLIB_SX128X_CMD_SET_BUFFER_BASE_ADDRESS, data, 2)); +} + +int16_t SX128x::setModulationParams(uint8_t modParam1, uint8_t modParam2, uint8_t modParam3) { + uint8_t data[] = { modParam1, modParam2, modParam3 }; + return(this->mod->SPIwriteStream(RADIOLIB_SX128X_CMD_SET_MODULATION_PARAMS, data, 3)); +} + +int16_t SX128x::setPacketParamsGFSK(uint8_t preambleLen, uint8_t syncLen, uint8_t syncMatch, uint8_t crcLen, uint8_t whiten, uint8_t payLen, uint8_t hdrType) { + uint8_t data[] = { preambleLen, syncLen, syncMatch, hdrType, payLen, crcLen, whiten }; + return(this->mod->SPIwriteStream(RADIOLIB_SX128X_CMD_SET_PACKET_PARAMS, data, 7)); +} + +int16_t SX128x::setPacketParamsBLE(uint8_t connState, uint8_t crcLen, uint8_t bleTest, uint8_t whiten) { + uint8_t data[] = { connState, crcLen, bleTest, whiten, 0x00, 0x00, 0x00 }; + return(this->mod->SPIwriteStream(RADIOLIB_SX128X_CMD_SET_PACKET_PARAMS, data, 7)); +} + +int16_t SX128x::setPacketParamsLoRa(uint8_t preambleLen, uint8_t hdrType, uint8_t payLen, uint8_t crc, uint8_t invIQ) { + uint8_t data[] = { preambleLen, hdrType, payLen, crc, invIQ, 0x00, 0x00 }; + return(this->mod->SPIwriteStream(RADIOLIB_SX128X_CMD_SET_PACKET_PARAMS, data, 7)); +} + +int16_t SX128x::setDioIrqParams(uint16_t irqMask, uint16_t dio1Mask, uint16_t dio2Mask, uint16_t dio3Mask) { + uint8_t data[] = { (uint8_t)((irqMask >> 8) & 0xFF), (uint8_t)(irqMask & 0xFF), + (uint8_t)((dio1Mask >> 8) & 0xFF), (uint8_t)(dio1Mask & 0xFF), + (uint8_t)((dio2Mask >> 8) & 0xFF), (uint8_t)(dio2Mask & 0xFF), + (uint8_t)((dio3Mask >> 8) & 0xFF), (uint8_t)(dio3Mask & 0xFF) }; + return(this->mod->SPIwriteStream(RADIOLIB_SX128X_CMD_SET_DIO_IRQ_PARAMS, data, 8)); +} + +uint16_t SX128x::getIrqStatus() { + uint8_t data[] = { 0x00, 0x00 }; + this->mod->SPIreadStream(RADIOLIB_SX128X_CMD_GET_IRQ_STATUS, data, 2); + return(((uint16_t)(data[0]) << 8) | data[1]); +} + +int16_t SX128x::clearIrqStatus(uint16_t clearIrqParams) { + uint8_t data[] = { (uint8_t)((clearIrqParams >> 8) & 0xFF), (uint8_t)(clearIrqParams & 0xFF) }; + return(this->mod->SPIwriteStream(RADIOLIB_SX128X_CMD_CLEAR_IRQ_STATUS, data, 2)); +} + +int16_t SX128x::setRangingRole(uint8_t role) { + uint8_t data[] = { role }; + return(this->mod->SPIwriteStream(RADIOLIB_SX128X_CMD_SET_RANGING_ROLE, data, 1)); +} + +int16_t SX128x::setPacketType(uint8_t type) { + uint8_t data[] = { type }; + return(this->mod->SPIwriteStream(RADIOLIB_SX128X_CMD_SET_PACKET_TYPE, data, 1)); +} + +int16_t SX128x::setHeaderType(uint8_t hdrType, size_t len) { + // check active modem + uint8_t modem = getPacketType(); + if(!((modem == RADIOLIB_SX128X_PACKET_TYPE_LORA) || (modem == RADIOLIB_SX128X_PACKET_TYPE_RANGING))) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // update packet parameters + this->headerType = hdrType; + this->payloadLen = len; + return(setPacketParamsLoRa(this->preambleLengthLoRa, this->headerType, this->payloadLen, this->crcLoRa, this->invertIQEnabled)); +} + +int16_t SX128x::config(uint8_t modem) { + // reset buffer base address + int16_t state = setBufferBaseAddress(); + RADIOLIB_ASSERT(state); + + // set modem + uint8_t data[1]; + data[0] = modem; + state = this->mod->SPIwriteStream(RADIOLIB_SX128X_CMD_SET_PACKET_TYPE, data, 1); + RADIOLIB_ASSERT(state); + + // set CAD parameters + data[0] = RADIOLIB_SX128X_CAD_ON_8_SYMB; + state = this->mod->SPIwriteStream(RADIOLIB_SX128X_CMD_SET_CAD_PARAMS, data, 1); + RADIOLIB_ASSERT(state); + + // set regulator mode to DC-DC + data[0] = RADIOLIB_SX128X_REGULATOR_DC_DC; + state = this->mod->SPIwriteStream(RADIOLIB_SX128X_CMD_SET_REGULATOR_MODE, data, 1); + RADIOLIB_ASSERT(state); + + return(RADIOLIB_ERR_NONE); +} + +int16_t SX128x::SPIparseStatus(uint8_t in) { + if((in & 0b00011100) == RADIOLIB_SX128X_STATUS_CMD_TIMEOUT) { + return(RADIOLIB_ERR_SPI_CMD_TIMEOUT); + } else if((in & 0b00011100) == RADIOLIB_SX128X_STATUS_CMD_ERROR) { + return(RADIOLIB_ERR_SPI_CMD_INVALID); + } else if((in & 0b00011100) == RADIOLIB_SX128X_STATUS_CMD_FAILED) { + return(RADIOLIB_ERR_SPI_CMD_FAILED); + } else if((in == 0x00) || (in == 0xFF)) { + return(RADIOLIB_ERR_CHIP_NOT_FOUND); + } + return(RADIOLIB_ERR_NONE); +} + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/SX128x/SX128x.h b/software/firmware/source/libraries/RadioLib/src/modules/SX128x/SX128x.h new file mode 100644 index 000000000..ae1d46876 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/SX128x/SX128x.h @@ -0,0 +1,945 @@ +#if !defined(_RADIOLIB_SX128X_H) +#define _RADIOLIB_SX128X_H + +#include "../../TypeDef.h" + +#if !RADIOLIB_EXCLUDE_SX128X + +#include "../../Module.h" + +#include "../../protocols/PhysicalLayer/PhysicalLayer.h" + +// SX128X physical layer properties +#define RADIOLIB_SX128X_FREQUENCY_STEP_SIZE 198.3642578 +#define RADIOLIB_SX128X_MAX_PACKET_LENGTH 255 +#define RADIOLIB_SX128X_CRYSTAL_FREQ 52.0 +#define RADIOLIB_SX128X_DIV_EXPONENT 18 + +// SX128X SPI commands +#define RADIOLIB_SX128X_CMD_NOP 0x00 +#define RADIOLIB_SX128X_CMD_GET_STATUS 0xC0 +#define RADIOLIB_SX128X_CMD_WRITE_REGISTER 0x18 +#define RADIOLIB_SX128X_CMD_READ_REGISTER 0x19 +#define RADIOLIB_SX128X_CMD_WRITE_BUFFER 0x1A +#define RADIOLIB_SX128X_CMD_READ_BUFFER 0x1B +#define RADIOLIB_SX128X_CMD_SAVE_CONTEXT 0xD5 +#define RADIOLIB_SX128X_CMD_SET_SLEEP 0x84 +#define RADIOLIB_SX128X_CMD_SET_STANDBY 0x80 +#define RADIOLIB_SX128X_CMD_SET_FS 0xC1 +#define RADIOLIB_SX128X_CMD_SET_TX 0x83 +#define RADIOLIB_SX128X_CMD_SET_RX 0x82 +#define RADIOLIB_SX128X_CMD_SET_RX_DUTY_CYCLE 0x94 +#define RADIOLIB_SX128X_CMD_SET_CAD 0xC5 +#define RADIOLIB_SX128X_CMD_SET_TX_CONTINUOUS_WAVE 0xD1 +#define RADIOLIB_SX128X_CMD_SET_TX_CONTINUOUS_PREAMBLE 0xD2 +#define RADIOLIB_SX128X_CMD_SET_PACKET_TYPE 0x8A +#define RADIOLIB_SX128X_CMD_GET_PACKET_TYPE 0x03 +#define RADIOLIB_SX128X_CMD_SET_RF_FREQUENCY 0x86 +#define RADIOLIB_SX128X_CMD_SET_TX_PARAMS 0x8E +#define RADIOLIB_SX128X_CMD_SET_CAD_PARAMS 0x88 +#define RADIOLIB_SX128X_CMD_SET_BUFFER_BASE_ADDRESS 0x8F +#define RADIOLIB_SX128X_CMD_SET_MODULATION_PARAMS 0x8B +#define RADIOLIB_SX128X_CMD_SET_PACKET_PARAMS 0x8C +#define RADIOLIB_SX128X_CMD_GET_RX_BUFFER_STATUS 0x17 +#define RADIOLIB_SX128X_CMD_GET_PACKET_STATUS 0x1D +#define RADIOLIB_SX128X_CMD_GET_RSSI_INST 0x1F +#define RADIOLIB_SX128X_CMD_SET_DIO_IRQ_PARAMS 0x8D +#define RADIOLIB_SX128X_CMD_GET_IRQ_STATUS 0x15 +#define RADIOLIB_SX128X_CMD_CLEAR_IRQ_STATUS 0x97 +#define RADIOLIB_SX128X_CMD_SET_REGULATOR_MODE 0x96 +#define RADIOLIB_SX128X_CMD_SET_SAVE_CONTEXT 0xD5 +#define RADIOLIB_SX128X_CMD_SET_AUTO_TX 0x98 +#define RADIOLIB_SX128X_CMD_SET_AUTO_FS 0x9E +#define RADIOLIB_SX128X_CMD_SET_PERF_COUNTER_MODE 0x9C +#define RADIOLIB_SX128X_CMD_SET_LONG_PREAMBLE 0x9B +#define RADIOLIB_SX128X_CMD_SET_UART_SPEED 0x9D +#define RADIOLIB_SX128X_CMD_SET_RANGING_ROLE 0xA3 +#define RADIOLIB_SX128X_CMD_SET_ADVANCED_RANGING 0x9A + +// SX128X register map +#define RADIOLIB_SX128X_REG_GAIN_MODE 0x0891 +#define RADIOLIB_SX128X_REG_MANUAL_GAIN_CONTROL_ENABLE_2 0x0895 +#define RADIOLIB_SX128X_REG_MANUAL_GAIN_SETTING 0x089E +#define RADIOLIB_SX128X_REG_MANUAL_GAIN_CONTROL_ENABLE_1 0x089F +#define RADIOLIB_SX128X_REG_SYNCH_PEAK_ATTENUATION 0x08C2 +#define RADIOLIB_SX128X_REG_LORA_FIXED_PAYLOAD_LENGTH 0x0901 +#define RADIOLIB_SX128X_REG_LORA_HEADER_MODE 0x0903 +#define RADIOLIB_SX128X_REG_MASTER_RANGING_ADDRESS_BYTE_3 0x0912 +#define RADIOLIB_SX128X_REG_MASTER_RANGING_ADDRESS_BYTE_2 0x0913 +#define RADIOLIB_SX128X_REG_MASTER_RANGING_ADDRESS_BYTE_1 0x0914 +#define RADIOLIB_SX128X_REG_MASTER_RANGING_ADDRESS_BYTE_0 0x0915 +#define RADIOLIB_SX128X_REG_SLAVE_RANGING_ADDRESS_BYTE_3 0x0916 +#define RADIOLIB_SX128X_REG_SLAVE_RANGING_ADDRESS_BYTE_2 0x0917 +#define RADIOLIB_SX128X_REG_SLAVE_RANGING_ADDRESS_BYTE_1 0x0918 +#define RADIOLIB_SX128X_REG_SLAVE_RANGING_ADDRESS_BYTE_0 0x0919 +#define RADIOLIB_SX128X_REG_RANGING_FILTER_WINDOW_SIZE 0x091E +#define RADIOLIB_SX128X_REG_RANGING_FILTER_RESET 0x0923 +#define RADIOLIB_SX128X_REG_RANGING_TYPE 0x0924 +#define RADIOLIB_SX128X_REG_LORA_SF_CONFIG 0x0925 +#define RADIOLIB_SX128X_REG_RANGING_ADDRESS_SWITCH 0x0927 +#define RADIOLIB_SX128X_REG_RANGING_CALIBRATION_BYTE_2 0x092B +#define RADIOLIB_SX128X_REG_RANGING_CALIBRATION_MSB 0x092C +#define RADIOLIB_SX128X_REG_RANGING_CALIBRATION_LSB 0x092D +#define RADIOLIB_SX128X_REG_SLAVE_RANGING_ADDRESS_WIDTH 0x0931 +#define RADIOLIB_SX128X_REG_FREQ_ERROR_CORRECTION 0x093C +#define RADIOLIB_SX128X_REG_LORA_SYNC_WORD_MSB 0x0944 +#define RADIOLIB_SX128X_REG_LORA_SYNC_WORD_LSB 0x0945 +#define RADIOLIB_SX128X_REG_RANGING_FILTER_RSSI_OFFSET 0x0953 +#define RADIOLIB_SX128X_REG_FEI_MSB 0x0954 +#define RADIOLIB_SX128X_REG_FEI_MID 0x0955 +#define RADIOLIB_SX128X_REG_FEI_LSB 0x0956 +#define RADIOLIB_SX128X_REG_RANGING_ADDRESS_MSB 0x095F +#define RADIOLIB_SX128X_REG_RANGING_ADDRESS_LSB 0x0960 +#define RADIOLIB_SX128X_REG_RANGING_RESULT_MSB 0x0961 +#define RADIOLIB_SX128X_REG_RANGING_RESULT_MID 0x0962 +#define RADIOLIB_SX128X_REG_RANGING_RESULT_LSB 0x0963 +#define RADIOLIB_SX128X_REG_RANGING_RSSI 0x0964 +#define RADIOLIB_SX128X_REG_RANGING_LORA_CLOCK_ENABLE 0x097F +#define RADIOLIB_SX128X_REG_PACKET_PREAMBLE_SETTINGS 0x09C1 +#define RADIOLIB_SX128X_REG_WHITENING_INITIAL_VALUE 0x09C5 +#define RADIOLIB_SX128X_REG_CRC_POLYNOMIAL_MSB 0x09C6 +#define RADIOLIB_SX128X_REG_CRC_POLYNOMIAL_LSB 0x09C7 +#define RADIOLIB_SX128X_REG_CRC_INITIAL_MSB 0x09C8 +#define RADIOLIB_SX128X_REG_CRC_INITIAL_LSB 0x09C9 +#define RADIOLIB_SX128X_REG_BLE_CRC_INITIAL_MSB 0x09C7 +#define RADIOLIB_SX128X_REG_BLE_CRC_INITIAL_MID (RADIOLIB_SX128X_REG_CRC_INITIAL_MSB) +#define RADIOLIB_SX128X_REG_BLE_CRC_INITIAL_LSB (RADIOLIB_SX128X_REG_CRC_INITIAL_LSB) +#define RADIOLIB_SX128X_REG_SYNCH_ADDRESS_CONTROL 0x09CD +#define RADIOLIB_SX128X_REG_SYNC_WORD_1_BYTE_4 0x09CE +#define RADIOLIB_SX128X_REG_SYNC_WORD_1_BYTE_3 0x09CF +#define RADIOLIB_SX128X_REG_SYNC_WORD_1_BYTE_2 0x09D0 +#define RADIOLIB_SX128X_REG_SYNC_WORD_1_BYTE_1 0x09D1 +#define RADIOLIB_SX128X_REG_SYNC_WORD_1_BYTE_0 0x09D2 +#define RADIOLIB_SX128X_REG_SYNC_WORD_2_BYTE_4 0x09D3 +#define RADIOLIB_SX128X_REG_SYNC_WORD_2_BYTE_3 0x09D4 +#define RADIOLIB_SX128X_REG_SYNC_WORD_2_BYTE_2 0x09D5 +#define RADIOLIB_SX128X_REG_SYNC_WORD_2_BYTE_1 0x09D6 +#define RADIOLIB_SX128X_REG_SYNC_WORD_2_BYTE_0 0x09D7 +#define RADIOLIB_SX128X_REG_SYNC_WORD_3_BYTE_4 0x09D8 +#define RADIOLIB_SX128X_REG_SYNC_WORD_3_BYTE_3 0x09D9 +#define RADIOLIB_SX128X_REG_SYNC_WORD_3_BYTE_2 0x09DA +#define RADIOLIB_SX128X_REG_SYNC_WORD_3_BYTE_1 0x09DB +#define RADIOLIB_SX128X_REG_SYNC_WORD_3_BYTE_0 0x09DC +#define RADIOLIB_SX128X_REG_ACCESS_ADDRESS_BYTE_3 (RADIOLIB_SX128X_REG_SYNC_WORD_1_BYTE_3) +#define RADIOLIB_SX128X_REG_ACCESS_ADDRESS_BYTE_2 (RADIOLIB_SX128X_REG_SYNC_WORD_1_BYTE_2) +#define RADIOLIB_SX128X_REG_ACCESS_ADDRESS_BYTE_1 (RADIOLIB_SX128X_REG_SYNC_WORD_1_BYTE_1) +#define RADIOLIB_SX128X_REG_ACCESS_ADDRESS_BYTE_0 (RADIOLIB_SX128X_REG_SYNC_WORD_1_BYTE_0) + +// SX128X SPI command variables +//RADIOLIB_SX128X_CMD_GET_STATUS MSB LSB DESCRIPTION +#define RADIOLIB_SX128X_STATUS_MODE_STDBY_RC 0b01000000 // 7 5 current chip mode: STDBY_RC +#define RADIOLIB_SX128X_STATUS_MODE_STDBY_XOSC 0b01100000 // 7 5 STDBY_XOSC +#define RADIOLIB_SX128X_STATUS_MODE_FS 0b10000000 // 7 5 FS +#define RADIOLIB_SX128X_STATUS_MODE_RX 0b10100000 // 7 5 Rx +#define RADIOLIB_SX128X_STATUS_MODE_TX 0b11000000 // 7 5 Tx +#define RADIOLIB_SX128X_STATUS_CMD_PROCESSED 0b00000100 // 4 2 command status: processing OK +#define RADIOLIB_SX128X_STATUS_DATA_AVAILABLE 0b00001000 // 4 2 data available +#define RADIOLIB_SX128X_STATUS_CMD_TIMEOUT 0b00001100 // 4 2 timeout +#define RADIOLIB_SX128X_STATUS_CMD_ERROR 0b00010000 // 4 2 processing error +#define RADIOLIB_SX128X_STATUS_CMD_FAILED 0b00010100 // 4 2 failed to execute +#define RADIOLIB_SX128X_STATUS_TX_DONE 0b00011000 // 4 2 transmission finished +#define RADIOLIB_SX128X_STATUS_BUSY 0b00000001 // 0 0 chip busy +#define RADIOLIB_SX128X_STATUS_SPI_FAILED 0b11111111 // 7 0 SPI transaction failed + +//RADIOLIB_SX128X_CMD_SET_SLEEP +#define RADIOLIB_SX128X_SLEEP_DATA_BUFFER_FLUSH 0b00000000 // 1 1 data buffer behavior in sleep mode: flush +#define RADIOLIB_SX128X_SLEEP_DATA_BUFFER_RETAIN 0b00000010 // 1 1 retain +#define RADIOLIB_SX128X_SLEEP_DATA_RAM_FLUSH 0b00000000 // 0 0 data RAM (configuration) behavior in sleep mode: flush +#define RADIOLIB_SX128X_SLEEP_DATA_RAM_RETAIN 0b00000001 // 0 0 retain + +//RADIOLIB_SX128X_CMD_SET_STANDBY +#define RADIOLIB_SX128X_STANDBY_RC 0x00 // 7 0 standby mode: 13 MHz RC oscillator +#define RADIOLIB_SX128X_STANDBY_XOSC 0x01 // 7 0 52 MHz crystal oscillator + +//RADIOLIB_SX128X_CMD_SET_TX + RADIOLIB_SX128X_CMD_SET_RX + RADIOLIB_SX128X_CMD_SET_RX_DUTY_CYCLE +#define RADIOLIB_SX128X_PERIOD_BASE_15_625_US 0x00 // 7 0 time period step: 15.625 us +#define RADIOLIB_SX128X_PERIOD_BASE_62_5_US 0x01 // 7 0 62.5 us +#define RADIOLIB_SX128X_PERIOD_BASE_1_MS 0x02 // 7 0 1 ms +#define RADIOLIB_SX128X_PERIOD_BASE_4_MS 0x03 // 7 0 4 ms + +//RADIOLIB_SX128X_CMD_SET_TX +#define RADIOLIB_SX128X_TX_TIMEOUT_NONE 0x0000 // 15 0 Tx timeout duration: no timeout (Tx single mode) + +//RADIOLIB_SX128X_CMD_SET_RX +#define RADIOLIB_SX128X_RX_TIMEOUT_NONE 0x0000 // 15 0 Rx timeout duration: no timeout (Rx single mode) +#define RADIOLIB_SX128X_RX_TIMEOUT_INF 0xFFFF // 15 0 infinite (Rx continuous mode) + +//RADIOLIB_SX128X_CMD_SET_PACKET_TYPE +#define RADIOLIB_SX128X_PACKET_TYPE_GFSK 0x00 // 7 0 packet type: (G)FSK +#define RADIOLIB_SX128X_PACKET_TYPE_LORA 0x01 // 7 0 LoRa +#define RADIOLIB_SX128X_PACKET_TYPE_RANGING 0x02 // 7 0 ranging engine +#define RADIOLIB_SX128X_PACKET_TYPE_FLRC 0x03 // 7 0 FLRC +#define RADIOLIB_SX128X_PACKET_TYPE_BLE 0x04 // 7 0 BLE + +//RADIOLIB_SX128X_CMD_SET_TX_PARAMS +#define RADIOLIB_SX128X_PA_RAMP_02_US 0x00 // 7 0 PA ramp time: 2 us +#define RADIOLIB_SX128X_PA_RAMP_04_US 0x20 // 7 0 4 us +#define RADIOLIB_SX128X_PA_RAMP_06_US 0x40 // 7 0 6 us +#define RADIOLIB_SX128X_PA_RAMP_08_US 0x60 // 7 0 8 us +#define RADIOLIB_SX128X_PA_RAMP_10_US 0x80 // 7 0 10 us +#define RADIOLIB_SX128X_PA_RAMP_12_US 0xA0 // 7 0 12 us +#define RADIOLIB_SX128X_PA_RAMP_16_US 0xC0 // 7 0 16 us +#define RADIOLIB_SX128X_PA_RAMP_20_US 0xE0 // 7 0 20 us + +//RADIOLIB_SX128X_CMD_SET_CAD_PARAMS +#define RADIOLIB_SX128X_CAD_ON_1_SYMB 0x00 // 7 0 number of symbols used for CAD: 1 +#define RADIOLIB_SX128X_CAD_ON_2_SYMB 0x20 // 7 0 2 +#define RADIOLIB_SX128X_CAD_ON_4_SYMB 0x40 // 7 0 4 +#define RADIOLIB_SX128X_CAD_ON_8_SYMB 0x60 // 7 0 8 +#define RADIOLIB_SX128X_CAD_ON_16_SYMB 0x80 // 7 0 16 +#define RADIOLIB_SX128X_CAD_PARAM_DEFAULT RADIOLIB_SX128X_CAD_ON_8_SYMB + +//RADIOLIB_SX128X_CMD_SET_MODULATION_PARAMS +#define RADIOLIB_SX128X_BLE_GFSK_BR_2_000_BW_2_4 0x04 // 7 0 GFSK/BLE bit rate and bandwidth setting: 2.0 Mbps 2.4 MHz +#define RADIOLIB_SX128X_BLE_GFSK_BR_1_600_BW_2_4 0x28 // 7 0 1.6 Mbps 2.4 MHz +#define RADIOLIB_SX128X_BLE_GFSK_BR_1_000_BW_2_4 0x4C // 7 0 1.0 Mbps 2.4 MHz +#define RADIOLIB_SX128X_BLE_GFSK_BR_1_000_BW_1_2 0x45 // 7 0 1.0 Mbps 1.2 MHz +#define RADIOLIB_SX128X_BLE_GFSK_BR_0_800_BW_2_4 0x70 // 7 0 0.8 Mbps 2.4 MHz +#define RADIOLIB_SX128X_BLE_GFSK_BR_0_800_BW_1_2 0x69 // 7 0 0.8 Mbps 1.2 MHz +#define RADIOLIB_SX128X_BLE_GFSK_BR_0_500_BW_1_2 0x8D // 7 0 0.5 Mbps 1.2 MHz +#define RADIOLIB_SX128X_BLE_GFSK_BR_0_500_BW_0_6 0x86 // 7 0 0.5 Mbps 0.6 MHz +#define RADIOLIB_SX128X_BLE_GFSK_BR_0_400_BW_1_2 0xB1 // 7 0 0.4 Mbps 1.2 MHz +#define RADIOLIB_SX128X_BLE_GFSK_BR_0_400_BW_0_6 0xAA // 7 0 0.4 Mbps 0.6 MHz +#define RADIOLIB_SX128X_BLE_GFSK_BR_0_250_BW_0_6 0xCE // 7 0 0.25 Mbps 0.6 MHz +#define RADIOLIB_SX128X_BLE_GFSK_BR_0_250_BW_0_3 0xC7 // 7 0 0.25 Mbps 0.3 MHz +#define RADIOLIB_SX128X_BLE_GFSK_BR_0_125_BW_0_3 0xEF // 7 0 0.125 Mbps 0.3 MHz +#define RADIOLIB_SX128X_BLE_GFSK_MOD_IND_0_35 0x00 // 7 0 GFSK/BLE modulation index: 0.35 +#define RADIOLIB_SX128X_BLE_GFSK_MOD_IND_0_50 0x01 // 7 0 0.50 +#define RADIOLIB_SX128X_BLE_GFSK_MOD_IND_0_75 0x02 // 7 0 0.75 +#define RADIOLIB_SX128X_BLE_GFSK_MOD_IND_1_00 0x03 // 7 0 1.00 +#define RADIOLIB_SX128X_BLE_GFSK_MOD_IND_1_25 0x04 // 7 0 1.25 +#define RADIOLIB_SX128X_BLE_GFSK_MOD_IND_1_50 0x05 // 7 0 1.50 +#define RADIOLIB_SX128X_BLE_GFSK_MOD_IND_1_75 0x06 // 7 0 1.75 +#define RADIOLIB_SX128X_BLE_GFSK_MOD_IND_2_00 0x07 // 7 0 2.00 +#define RADIOLIB_SX128X_BLE_GFSK_MOD_IND_2_25 0x08 // 7 0 2.25 +#define RADIOLIB_SX128X_BLE_GFSK_MOD_IND_2_50 0x09 // 7 0 2.50 +#define RADIOLIB_SX128X_BLE_GFSK_MOD_IND_2_75 0x0A // 7 0 2.75 +#define RADIOLIB_SX128X_BLE_GFSK_MOD_IND_3_00 0x0B // 7 0 3.00 +#define RADIOLIB_SX128X_BLE_GFSK_MOD_IND_3_25 0x0C // 7 0 3.25 +#define RADIOLIB_SX128X_BLE_GFSK_MOD_IND_3_50 0x0D // 7 0 3.50 +#define RADIOLIB_SX128X_BLE_GFSK_MOD_IND_3_75 0x0E // 7 0 3.75 +#define RADIOLIB_SX128X_BLE_GFSK_MOD_IND_4_00 0x0F // 7 0 4.00 +#define RADIOLIB_SX128X_BLE_GFSK_BT_OFF 0x00 // 7 0 GFSK Gaussian filter BT product: filter disabled +#define RADIOLIB_SX128X_BLE_GFSK_BT_1_0 0x10 // 7 0 1.0 +#define RADIOLIB_SX128X_BLE_GFSK_BT_0_5 0x20 // 7 0 0.5 +#define RADIOLIB_SX128X_FLRC_BR_1_300_BW_1_2 0x45 // 7 0 FLRC bit rate and bandwidth setting: 1.3 Mbps 1.2 MHz +#define RADIOLIB_SX128X_FLRC_BR_1_000_BW_1_2 0x69 // 7 0 1.04 Mbps 1.2 MHz +#define RADIOLIB_SX128X_FLRC_BR_0_650_BW_0_6 0x86 // 7 0 0.65 Mbps 0.6 MHz +#define RADIOLIB_SX128X_FLRC_BR_0_520_BW_0_6 0xAA // 7 0 0.52 Mbps 0.6 MHz +#define RADIOLIB_SX128X_FLRC_BR_0_325_BW_0_3 0xC7 // 7 0 0.325 Mbps 0.3 MHz +#define RADIOLIB_SX128X_FLRC_BR_0_260_BW_0_3 0xEB // 7 0 0.260 Mbps 0.3 MHz +#define RADIOLIB_SX128X_FLRC_CR_1_2 0x00 // 7 0 FLRC coding rate: 1/2 +#define RADIOLIB_SX128X_FLRC_CR_3_4 0x02 // 7 0 3/4 +#define RADIOLIB_SX128X_FLRC_CR_1_0 0x04 // 7 0 1/1 +#define RADIOLIB_SX128X_FLRC_BT_OFF 0x00 // 7 0 FLRC Gaussian filter BT product: filter disabled +#define RADIOLIB_SX128X_FLRC_BT_1_0 0x10 // 7 0 1.0 +#define RADIOLIB_SX128X_FLRC_BT_0_5 0x20 // 7 0 0.5 +#define RADIOLIB_SX128X_LORA_SF_5 0x50 // 7 0 LoRa spreading factor: 5 +#define RADIOLIB_SX128X_LORA_SF_6 0x60 // 7 0 6 +#define RADIOLIB_SX128X_LORA_SF_7 0x70 // 7 0 7 +#define RADIOLIB_SX128X_LORA_SF_8 0x80 // 7 0 8 +#define RADIOLIB_SX128X_LORA_SF_9 0x90 // 7 0 9 +#define RADIOLIB_SX128X_LORA_SF_10 0xA0 // 7 0 10 +#define RADIOLIB_SX128X_LORA_SF_11 0xB0 // 7 0 11 +#define RADIOLIB_SX128X_LORA_SF_12 0xC0 // 7 0 12 +#define RADIOLIB_SX128X_LORA_BW_1625_00 0x0A // 7 0 LoRa bandwidth: 1625.0 kHz +#define RADIOLIB_SX128X_LORA_BW_812_50 0x18 // 7 0 812.5 kHz +#define RADIOLIB_SX128X_LORA_BW_406_25 0x26 // 7 0 406.25 kHz +#define RADIOLIB_SX128X_LORA_BW_203_125 0x34 // 7 0 203.125 kHz +#define RADIOLIB_SX128X_LORA_CR_4_5 0x01 // 7 0 LoRa coding rate: 4/5 +#define RADIOLIB_SX128X_LORA_CR_4_6 0x02 // 7 0 4/6 +#define RADIOLIB_SX128X_LORA_CR_4_7 0x03 // 7 0 4/7 +#define RADIOLIB_SX128X_LORA_CR_4_8 0x04 // 7 0 4/8 +#define RADIOLIB_SX128X_LORA_CR_4_5_LI 0x05 // 7 0 4/5, long interleaving +#define RADIOLIB_SX128X_LORA_CR_4_6_LI 0x06 // 7 0 4/6, long interleaving +#define RADIOLIB_SX128X_LORA_CR_4_7_LI 0x07 // 7 0 4/7, long interleaving + +//RADIOLIB_SX128X_CMD_SET_PACKET_PARAMS +#define RADIOLIB_SX128X_GFSK_FLRC_SYNC_WORD_OFF 0x00 // 7 0 GFSK/FLRC sync word used: none +#define RADIOLIB_SX128X_GFSK_FLRC_SYNC_WORD_1 0x10 // 7 0 sync word 1 +#define RADIOLIB_SX128X_GFSK_FLRC_SYNC_WORD_2 0x20 // 7 0 sync word 2 +#define RADIOLIB_SX128X_GFSK_FLRC_SYNC_WORD_1_2 0x30 // 7 0 sync words 1 and 2 +#define RADIOLIB_SX128X_GFSK_FLRC_SYNC_WORD_3 0x40 // 7 0 sync word 3 +#define RADIOLIB_SX128X_GFSK_FLRC_SYNC_WORD_1_3 0x50 // 7 0 sync words 1 and 3 +#define RADIOLIB_SX128X_GFSK_FLRC_SYNC_WORD_2_3 0x60 // 7 0 sync words 2 and 3 +#define RADIOLIB_SX128X_GFSK_FLRC_SYNC_WORD_1_2_3 0x70 // 7 0 sync words 1, 2 and 3 +#define RADIOLIB_SX128X_GFSK_FLRC_PACKET_FIXED 0x00 // 7 0 GFSK/FLRC packet length mode: fixed +#define RADIOLIB_SX128X_GFSK_FLRC_PACKET_VARIABLE 0x20 // 7 0 variable +#define RADIOLIB_SX128X_GFSK_FLRC_CRC_OFF 0x00 // 7 0 GFSK/FLRC packet CRC: none +#define RADIOLIB_SX128X_GFSK_FLRC_CRC_1_BYTE 0x10 // 7 0 1 byte +#define RADIOLIB_SX128X_GFSK_FLRC_CRC_2_BYTE 0x20 // 7 0 2 bytes +#define RADIOLIB_SX128X_GFSK_FLRC_CRC_3_BYTE 0x30 // 7 0 3 bytes (FLRC only) +#define RADIOLIB_SX128X_GFSK_BLE_WHITENING_ON 0x00 // 7 0 GFSK/BLE whitening: enabled +#define RADIOLIB_SX128X_GFSK_BLE_WHITENING_OFF 0x08 // 7 0 disabled +#define RADIOLIB_SX128X_BLE_PAYLOAD_LENGTH_MAX_31 0x00 // 7 0 BLE maximum payload length: 31 bytes +#define RADIOLIB_SX128X_BLE_PAYLOAD_LENGTH_MAX_37 0x20 // 7 0 37 bytes +#define RADIOLIB_SX128X_BLE_PAYLOAD_LENGTH_TEST 0x40 // 7 0 63 bytes (test mode) +#define RADIOLIB_SX128X_BLE_PAYLOAD_LENGTH_MAX_255 0x80 // 7 0 255 bytes (Bluetooth 4.2 and above) +#define RADIOLIB_SX128X_BLE_CRC_OFF 0x00 // 7 0 BLE packet CRC: none +#define RADIOLIB_SX128X_BLE_CRC_3_BYTE 0x10 // 7 0 3 byte +#define RADIOLIB_SX128X_BLE_PRBS_9 0x00 // 7 0 BLE test payload contents: PRNG sequence using x^9 + x^5 + x +#define RADIOLIB_SX128X_BLE_EYELONG 0x04 // 7 0 repeated 0xF0 +#define RADIOLIB_SX128X_BLE_EYESHORT 0x08 // 7 0 repeated 0xAA +#define RADIOLIB_SX128X_BLE_PRBS_15 0x0C // 7 0 PRNG sequence using x^15 + x^14 + x^13 + x^12 + x^2 + x + 1 +#define RADIOLIB_SX128X_BLE_ALL_1 0x10 // 7 0 repeated 0xFF +#define RADIOLIB_SX128X_BLE_ALL_0 0x14 // 7 0 repeated 0x00 +#define RADIOLIB_SX128X_BLE_EYELONG_INV 0x18 // 7 0 repeated 0x0F +#define RADIOLIB_SX128X_BLE_EYESHORT_INV 0x1C // 7 0 repeated 0x55 +#define RADIOLIB_SX128X_FLRC_SYNC_WORD_OFF 0x00 // 7 0 FLRC sync word: disabled +#define RADIOLIB_SX128X_FLRC_SYNC_WORD_ON 0x04 // 7 0 enabled +#define RADIOLIB_SX128X_LORA_HEADER_EXPLICIT 0x00 // 7 0 LoRa header mode: explicit +#define RADIOLIB_SX128X_LORA_HEADER_IMPLICIT 0x80 // 7 0 implicit +#define RADIOLIB_SX128X_LORA_CRC_OFF 0x00 // 7 0 LoRa packet CRC: disabled +#define RADIOLIB_SX128X_LORA_CRC_ON 0x20 // 7 0 enabled +#define RADIOLIB_SX128X_LORA_IQ_STANDARD 0x40 // 7 0 LoRa IQ: standard +#define RADIOLIB_SX128X_LORA_IQ_INVERTED 0x00 // 7 0 inverted + +//RADIOLIB_SX128X_CMD_GET_PACKET_STATUS +#define RADIOLIB_SX128X_PACKET_STATUS_SYNC_ERROR 0b01000000 // 6 6 packet status errors byte: sync word error +#define RADIOLIB_SX128X_PACKET_STATUS_LENGTH_ERROR 0b00100000 // 5 5 packet length error +#define RADIOLIB_SX128X_PACKET_STATUS_CRC_ERROR 0b00010000 // 4 4 CRC error +#define RADIOLIB_SX128X_PACKET_STATUS_ABORT_ERROR 0b00001000 // 3 3 packet reception aborted +#define RADIOLIB_SX128X_PACKET_STATUS_HEADER_RECEIVED 0b00000100 // 2 2 header received +#define RADIOLIB_SX128X_PACKET_STATUS_PACKET_RECEIVED 0b00000010 // 1 1 packet received +#define RADIOLIB_SX128X_PACKET_STATUS_PACKET_CTRL_BUSY 0b00000001 // 0 0 packet controller is busy +#define RADIOLIB_SX128X_PACKET_STATUS_RX_PID 0b11000000 // 7 6 packet status status byte: PID field of the received packet +#define RADIOLIB_SX128X_PACKET_STATUS_NO_ACK 0b00100000 // 5 5 NO_ACK field of the received packet +#define RADIOLIB_SX128X_PACKET_STATUS_RX_PID_ERROR 0b00010000 // 4 4 PID field error +#define RADIOLIB_SX128X_PACKET_STATUS_PACKET_SENT 0b00000001 // 0 0 packet sent +#define RADIOLIB_SX128X_PACKET_STATUS_SYNC_DET_ERROR 0b00000000 // 2 0 packet status sync byte: sync word detection error +#define RADIOLIB_SX128X_PACKET_STATUS_SYNC_DET_1 0b00000001 // 2 0 detected sync word 1 +#define RADIOLIB_SX128X_PACKET_STATUS_SYNC_DET_2 0b00000010 // 2 0 detected sync word 2 +#define RADIOLIB_SX128X_PACKET_STATUS_SYNC_DET_3 0b00000100 // 2 0 detected sync word 3 + +//RADIOLIB_SX128X_CMD_SET_DIO_IRQ_PARAMS +#define RADIOLIB_SX128X_IRQ_PREAMBLE_DETECTED 0x8000 // 15 15 interrupt source: preamble detected +#define RADIOLIB_SX128X_IRQ_ADVANCED_RANGING_DONE 0x8000 // 15 15 advanced ranging done +#define RADIOLIB_SX128X_IRQ_RX_TX_TIMEOUT 0x4000 // 14 14 Rx or Tx timeout +#define RADIOLIB_SX128X_IRQ_CAD_DETECTED 0x2000 // 13 13 channel activity detected +#define RADIOLIB_SX128X_IRQ_CAD_DONE 0x1000 // 12 12 CAD finished +#define RADIOLIB_SX128X_IRQ_RANGING_SLAVE_REQ_VALID 0x0800 // 11 11 ranging request valid (slave) +#define RADIOLIB_SX128X_IRQ_RANGING_MASTER_TIMEOUT 0x0400 // 10 10 ranging timeout (master) +#define RADIOLIB_SX128X_IRQ_RANGING_MASTER_RES_VALID 0x0200 // 9 9 ranging result valid (master) +#define RADIOLIB_SX128X_IRQ_RANGING_SLAVE_REQ_DISCARD 0x0100 // 8 8 ranging result valid (master) +#define RADIOLIB_SX128X_IRQ_RANGING_SLAVE_RESP_DONE 0x0080 // 7 7 ranging response complete (slave) +#define RADIOLIB_SX128X_IRQ_CRC_ERROR 0x0040 // 6 6 CRC error +#define RADIOLIB_SX128X_IRQ_HEADER_ERROR 0x0020 // 5 5 header error +#define RADIOLIB_SX128X_IRQ_HEADER_VALID 0x0010 // 4 4 header valid +#define RADIOLIB_SX128X_IRQ_SYNC_WORD_ERROR 0x0008 // 3 3 sync word error +#define RADIOLIB_SX128X_IRQ_SYNC_WORD_VALID 0x0004 // 2 2 sync word valid +#define RADIOLIB_SX128X_IRQ_RX_DONE 0x0002 // 1 1 Rx done +#define RADIOLIB_SX128X_IRQ_TX_DONE 0x0001 // 0 0 Tx done +#define RADIOLIB_SX128X_IRQ_NONE 0x0000 // 15 0 none +#define RADIOLIB_SX128X_IRQ_ALL 0xFFFF // 15 0 all + +//RADIOLIB_SX128X_CMD_SET_REGULATOR_MODE +#define RADIOLIB_SX128X_REGULATOR_LDO 0x00 // 7 0 set regulator mode: LDO (default) +#define RADIOLIB_SX128X_REGULATOR_DC_DC 0x01 // 7 0 DC-DC + +//RADIOLIB_SX128X_CMD_SET_RANGING_ROLE +#define RADIOLIB_SX128X_RANGING_ROLE_MASTER 0x01 // 7 0 ranging role: master +#define RADIOLIB_SX128X_RANGING_ROLE_SLAVE 0x00 // 7 0 slave + +//RADIOLIB_SX128X_REG_LORA_SYNC_WORD_1 - RADIOLIB_SX128X_REG_LORA_SYNC_WORD_2 +#define RADIOLIB_SX128X_SYNC_WORD_PRIVATE 0x12 + +/*! + \class SX128x + \brief Base class for %SX128x series. All derived classes for %SX128x (e.g. SX1280 or SX1281) inherit from this base class. + This class should not be instantiated directly from Arduino sketch, only from its derived classes. +*/ +class SX128x: public PhysicalLayer { + public: + // introduce PhysicalLayer overloads + using PhysicalLayer::transmit; + using PhysicalLayer::receive; + using PhysicalLayer::startTransmit; + using PhysicalLayer::readData; + + /*! + \brief Default constructor. + \param mod Instance of Module that will be used to communicate with the radio. + */ + explicit SX128x(Module* mod); + + // basic methods + + /*! + \brief Initialization method for LoRa modem. + \param freq Carrier frequency in MHz. Defaults to 2400.0 MHz. + \param bw LoRa bandwidth in kHz. Defaults to 812.5 kHz. + \param sf LoRa spreading factor. Defaults to 9. + \param cr LoRa coding rate denominator. Defaults to 7 (coding rate 4/7). + \param syncWord 2-byte LoRa sync word. Defaults to RADIOLIB_SX128X_SYNC_WORD_PRIVATE (0x12). + \param pwr Output power in dBm. Defaults to 10 dBm. + \param preambleLength LoRa preamble length in symbols. Defaults to 12 symbols. + \returns \ref status_codes + */ + int16_t begin(float freq = 2400.0, float bw = 812.5, uint8_t sf = 9, uint8_t cr = 7, uint8_t syncWord = RADIOLIB_SX128X_SYNC_WORD_PRIVATE, int8_t pwr = 10, uint16_t preambleLength = 12); + + /*! + \brief Initialization method for GFSK modem. + \param freq Carrier frequency in MHz. Defaults to 2400.0 MHz. + \param br FSK bit rate in kbps. Defaults to 800 kbps. + \param freqDev Frequency deviation from carrier frequency in kHz. Defaults to 400.0 kHz. + \param pwr Output power in dBm. Defaults to 10 dBm. + \param preambleLength FSK preamble length in bits. Defaults to 16 bits. + \returns \ref status_codes + */ + int16_t beginGFSK(float freq = 2400.0, uint16_t br = 800, float freqDev = 400.0, int8_t pwr = 10, uint16_t preambleLength = 16); + + /*! + \brief Initialization method for BLE modem. + \param freq Carrier frequency in MHz. Defaults to 2400.0 MHz. + \param br BLE bit rate in kbps. Defaults to 800 kbps. + \param freqDev Frequency deviation from carrier frequency in kHz. Defaults to 400.0 kHz. + \param pwr Output power in dBm. Defaults to 10 dBm. + \param dataShaping Time-bandwidth product of the Gaussian filter to be used for shaping. Defaults to 0.5. + \returns \ref status_codes + */ + int16_t beginBLE(float freq = 2400.0, uint16_t br = 800, float freqDev = 400.0, int8_t pwr = 10, uint8_t dataShaping = RADIOLIB_SHAPING_0_5); + + /*! + \brief Initialization method for FLRC modem. + \param freq Carrier frequency in MHz. Defaults to 2400.0 MHz. + \param br FLRC bit rate in kbps. Defaults to 650 kbps. + \param cr FLRC coding rate. Defaults to 3 (coding rate 3/4). + \param pwr Output power in dBm. Defaults to 10 dBm. + \param preambleLength FLRC preamble length in bits. Defaults to 16 bits. + \param dataShaping Time-bandwidth product of the Gaussian filter to be used for shaping. Defaults to 0.5. + \returns \ref status_codes + */ + int16_t beginFLRC(float freq = 2400.0, uint16_t br = 650, uint8_t cr = 3, int8_t pwr = 10, uint16_t preambleLength = 16, uint8_t dataShaping = RADIOLIB_SHAPING_0_5); + + /*! + \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 + by repeatedly issuing setStandby command. Enabled by default. + \returns \ref status_codes + */ + int16_t reset(bool verify = true); + + /*! + \brief Blocking binary transmit method. + Overloads for string-based transmissions are implemented in PhysicalLayer. + \param data Binary data to be sent. + \param len Number of bytes to send. + \param addr Address to send the data to. Unsupported, compatibility only. + \returns \ref status_codes + */ + int16_t transmit(const uint8_t* data, size_t len, uint8_t addr = 0) override; + + /*! + \brief Blocking binary receive method. + Overloads for string-based transmissions are implemented in PhysicalLayer. + \param data Binary data to be sent. + \param len Number of bytes to send. + \returns \ref status_codes + */ + int16_t receive(uint8_t* data, size_t len) override; + + /*! + \brief Starts direct mode transmission. + \param frf Raw RF frequency value. Defaults to 0, required for quick frequency shifts in RTTY. + \returns \ref status_codes + */ + int16_t transmitDirect(uint32_t frf = 0) override; + + /*! + \brief Starts direct mode reception. Only implemented for PhysicalLayer compatibility, + as %SX128x series does not support direct mode reception. Will always return RADIOLIB_ERR_UNKNOWN. + \returns \ref status_codes + */ + int16_t receiveDirect() override; + + /*! + \brief Performs scan for LoRa transmission in the current channel. Detects both preamble and payload. + \returns \ref status_codes + */ + int16_t scanChannel() override; + + /*! + \brief Performs scan for LoRa transmission in the current channel. Detects both preamble and payload. + \param config CAD configuration structure. + \returns \ref status_codes + */ + int16_t scanChannel(const ChannelScanConfig_t &config) override; + + /*! + \brief Sets the module to sleep mode. To wake the device up, call standby(). + Overload for PhysicalLayer compatibility. + \returns \ref status_codes + */ + int16_t sleep() override; + + /*! + \brief Sets the module to sleep mode. To wake the device up, call standby(). + \param retainConfig Set to true to retain configuration and data buffer or to false + to discard current configuration and data buffer. Defaults to true. + \returns \ref status_codes + */ + int16_t sleep(bool retainConfig); + + /*! + \brief Sets the module to standby mode (overload for PhysicalLayer compatibility, uses 13 MHz RC oscillator). + \returns \ref status_codes + */ + int16_t standby() override; + + /*! + \brief Sets the module to standby mode. + \param mode Oscillator to be used in standby mode. Can be set to RADIOLIB_SX128X_STANDBY_RC + (13 MHz RC oscillator) or RADIOLIB_SX128X_STANDBY_XOSC (52 MHz external crystal oscillator). + \param wakeup Whether to force the module to wake up. Setting to true will immediately attempt to wake up the module. + \returns \ref status_codes + */ + int16_t standby(uint8_t mode, bool wakeup = false); + + // interrupt methods + + /*! + \brief Sets interrupt service routine to call when DIO1 activates. + \param func ISR to call. + */ + void setDio1Action(void (*func)(void)); + + /*! + \brief Clears interrupt service routine to call when DIO1 activates. + */ + void clearDio1Action(); + + /*! + \brief Sets interrupt service routine to call when a packet is received. + \param func ISR to call. + */ + void setPacketReceivedAction(void (*func)(void)) override; + + /*! + \brief Clears interrupt service routine to call when a packet is received. + */ + void clearPacketReceivedAction() override; + + /*! + \brief Sets interrupt service routine to call when a packet is sent. + \param func ISR to call. + */ + void setPacketSentAction(void (*func)(void)) override; + + /*! + \brief Clears interrupt service routine to call when a packet is sent. + */ + void clearPacketSentAction() override; + + /*! + \brief Interrupt-driven binary transmit method. + Overloads for string-based transmissions are implemented in PhysicalLayer. + \param data Binary data to be sent. + \param len Number of bytes to send. + \param addr Address to send the data to. Unsupported, compatibility only. + \returns \ref status_codes + */ + int16_t startTransmit(const uint8_t* data, size_t len, uint8_t addr = 0) override; + + /*! + \brief Clean up after transmission is done. + \returns \ref status_codes + */ + int16_t finishTransmit() override; + + /*! + \brief Interrupt-driven receive method with default parameters. + Implemented for compatibility with PhysicalLayer. + + \returns \ref status_codes + */ + int16_t startReceive() override; + + /*! + \brief Interrupt-driven receive method. DIO1 will be activated when full packet is received. + \param timeout Raw timeout value, expressed as multiples of 15.625 us. Defaults to + RADIOLIB_SX128X_RX_TIMEOUT_INF for infinite timeout (Rx continuous mode), + set to RADIOLIB_SX128X_RX_TIMEOUT_NONE for no timeout (Rx single mode). + If timeout other than infinite is set, signal will be generated on DIO1. + + \param irqFlags Sets the IRQ flags, defaults to RX done, RX timeout, CRC error and header error. + \param irqMask Sets the mask of IRQ flags that will trigger DIO1, defaults to RX done. + \param len Only for PhysicalLayer compatibility, not used. + \returns \ref status_codes + */ + int16_t startReceive(uint16_t timeout, RadioLibIrqFlags_t irqFlags = RADIOLIB_IRQ_RX_DEFAULT_FLAGS, RadioLibIrqFlags_t irqMask = RADIOLIB_IRQ_RX_DEFAULT_MASK, size_t len = 0); + + /*! + \brief Reads the current IRQ status. + \returns IRQ status bits + */ + uint16_t getIrqStatus(); + + /*! + \brief Reads data received after calling startReceive method. When the packet length is not known in advance, + getPacketLength method must be called BEFORE calling readData! + \param data Pointer to array to save the received binary data. + \param len Number of bytes that will be read. When set to 0, the packet length will be retrieved automatically. + When more bytes than received are requested, only the number of bytes requested will be returned. + \returns \ref status_codes + */ + int16_t readData(uint8_t* data, size_t len) override; + + /*! + \brief Read currently active IRQ flags. + \returns IRQ flags. + */ + uint32_t getIrqFlags() override; + + /*! + \brief Set interrupt on DIO1 to be sent on a specific IRQ bit (e.g. RxTimeout, CadDone). + \param irq Module-specific IRQ flags. + \returns \ref status_codes + */ + int16_t setIrqFlags(uint32_t irq) override; + + /*! + \brief Clear interrupt on a specific IRQ bit (e.g. RxTimeout, CadDone). + \param irq Module-specific IRQ flags. + \returns \ref status_codes + */ + int16_t clearIrqFlags(uint32_t irq) override; + + /*! + \brief Interrupt-driven channel activity detection method. DIO1 will be activated + when LoRa preamble is detected, or upon timeout. + \returns \ref status_codes + */ + int16_t startChannelScan() override; + + /*! + \brief Interrupt-driven channel activity detection method. DIO1 will be activated + when LoRa preamble is detected, or upon timeout. + \param config CAD configuration structure. + \returns \ref status_codes + */ + int16_t startChannelScan(const ChannelScanConfig_t &config) override; + + /*! + \brief Read the channel scan result + \returns \ref status_codes + */ + int16_t getChannelScanResult() override; + + // configuration methods + + /*! + \brief Sets carrier frequency. Allowed values are in range from 2400.0 to 2500.0 MHz. + \param freq Carrier frequency to be set in MHz. + \returns \ref status_codes + */ + int16_t setFrequency(float freq) override; + + /*! + \brief Sets LoRa bandwidth. Allowed values are 203.125, 406.25, 812.5 and 1625.0 kHz. + \param bw LoRa bandwidth to be set in kHz. + \returns \ref status_codes + */ + int16_t setBandwidth(float bw); + + /*! + \brief Sets LoRa spreading factor. Allowed values range from 5 to 12. + \param sf LoRa spreading factor to be set. + \returns \ref status_codes + */ + int16_t setSpreadingFactor(uint8_t sf); + + /*! + \brief Sets LoRa coding rate denominator. Allowed values range from 5 to 8. + \param cr LoRa coding rate denominator to be set. + \param longInterleaving Whether to enable long interleaving mode. Not available for coding rate 4/7, + defaults to false. + \returns \ref status_codes + */ + int16_t setCodingRate(uint8_t cr, bool longInterleaving = false); + + /*! + \brief Sets output power. Allowed values are in range from -18 to 13 dBm. + \param pwr Output power to be set in dBm. + \returns \ref status_codes + */ + int16_t setOutputPower(int8_t pwr) override; + + /*! + \brief Check if output power is configurable. + \param pwr Output power in dBm. + \param clipped Clipped output power value to what is possible within the module's range. + \returns \ref status_codes + */ + int16_t checkOutputPower(int8_t pwr, int8_t* clipped) override; + + /*! + \brief Set modem for the radio to use. Will perform full reset and reconfigure the radio + using its default parameters. + \param modem Modem type to set - FSK, LoRa or LR-FHSS. + \returns \ref status_codes + */ + int16_t setModem(ModemType_t modem) override; + + /*! + \brief Get modem currently in use by the radio. + \param modem Pointer to a variable to save the retrieved configuration into. + \returns \ref status_codes + */ + int16_t getModem(ModemType_t* modem) override; + + /*! + \brief Sets preamble length for currently active modem. Allowed values range from 1 to 65535. + \param preambleLength Preamble length to be set in symbols (LoRa) or bits (FSK/BLE/FLRC). + \returns \ref status_codes + */ + int16_t setPreambleLength(uint32_t preambleLength); + + /*! + \brief Set data rate. + \param dr Data rate struct. Interpretation depends on currently active modem (FSK or LoRa). + \returns \ref status_codes + */ + int16_t setDataRate(DataRate_t dr) override; + + /*! + \brief Sets FSK or FLRC bit rate. Allowed values are 125, 250, 400, 500, 800, 1000, + 1600 and 2000 kbps (for FSK modem) or 260, 325, 520, 650, 1000 and 1300 (for FLRC modem). + \param br FSK/FLRC bit rate to be set in kbps. + \returns \ref status_codes + */ + int16_t setBitRate(float br) override; + + /*! + \brief Sets FSK frequency deviation. Allowed values range from 0.0 to 3200.0 kHz. + \param freqDev FSK frequency deviation to be set in kHz. + \returns \ref status_codes + */ + int16_t setFrequencyDeviation(float freqDev) override; + + /*! + \brief Sets time-bandwidth product of Gaussian filter applied for shaping. + Allowed values are RADIOLIB_SHAPING_0_5 or RADIOLIB_SHAPING_1_0. Set to RADIOLIB_SHAPING_NONE to disable data shaping. + \param sh Time-bandwidth product of Gaussian filter to be set. + \returns \ref status_codes + */ + int16_t setDataShaping(uint8_t sh) override; + + /*! + \brief Sets FSK/FLRC sync word in the form of array of up to 5 bytes (FSK). For FLRC modem, + the sync word must be exactly 4 bytes long + \param syncWord Sync word to be set. + \param len Sync word length in bytes. + \returns \ref status_codes + */ + int16_t setSyncWord(const uint8_t* syncWord, uint8_t len); + + /*! + \brief Sets LoRa sync word. + \param syncWord LoRa sync word to be set. + \param controlBits Undocumented control bits, required for compatibility purposes. + \returns \ref status_codes + */ + int16_t setSyncWord(uint8_t syncWord, uint8_t controlBits = 0x44); + + /*! + \brief Sets CRC configuration. + \param len CRC length in bytes, Allowed values are 1, 2 or 3, set to 0 to disable CRC. + \param initial Initial CRC value. Defaults to 0x1D0F (CCIT CRC), not available for LoRa modem. + \param polynomial Polynomial for CRC calculation. Defaults to 0x1021 (CCIT CRC), not available for LoRa or BLE modem. + \returns \ref status_codes + */ + int16_t setCRC(uint8_t len, uint32_t initial = 0x1D0F, uint16_t polynomial = 0x1021); + + /*! + \brief Sets whitening parameters, not available for LoRa or FLRC modem. + \param enabled Set to true to enable whitening. + \returns \ref status_codes + */ + int16_t setWhitening(bool enabled); + + /*! + \brief Sets BLE access address. + \param addr BLE access address. + \returns \ref status_codes + */ + int16_t setAccessAddress(uint32_t addr); + + /*! + \brief Enables or disables receiver high sensitivity mode. + \param enable True to enable and false to disable. + \returns \ref status_codes + */ + int16_t setHighSensitivityMode(bool enable); + + /*! + \brief Enables or disables receiver manual gain control. + \param gain Use 0 for automatic gain, 1 for minimum gain and up to 13 for maximum gain. + \returns \ref status_codes + */ + int16_t setGainControl(uint8_t gain = 0); + + /*! + \brief Gets RSSI (Recorded Signal Strength Indicator) of the last received packet. + \returns RSSI of the last received packet in dBm. + */ + float getRSSI() override; + + /*! + \brief Gets RSSI (Recorded Signal Strength Indicator). + \param packet Whether to read last packet RSSI, or the current value. + \returns RSSI value in dBm. + */ + float getRSSI(bool packet); + + /*! + \brief Gets SNR (Signal to Noise Ratio) of the last received packet. Only available for LoRa or ranging modem. + \returns SNR of the last received packet in dB. + */ + float getSNR() override; + + /*! + \brief Gets frequency error of the latest received packet. + \returns Frequency error in Hz. + */ + float getFrequencyError(); + + /*! + \brief Query modem for the packet length of received payload. + \param update Update received packet length. Will return cached value when set to false. + \returns Length of last received packet in bytes. + */ + size_t getPacketLength(bool update = true) override; + + /*! + \brief Query modem for the packet length of received payload and Rx buffer offset. + \param update Update received packet length. Will return cached value when set to false. + \param offset Pointer to variable to store the Rx buffer offset. + \returns Length of last received packet in bytes. + */ + size_t getPacketLength(bool update, uint8_t* offset); + + /*! + \brief Get expected time-on-air for a given size of payload. + \param len Payload length in bytes. + \returns Expected time-on-air in microseconds. + */ + RadioLibTime_t getTimeOnAir(size_t len) override; + + /*! + \brief Set implicit header mode for future reception/transmission. + \returns \ref status_codes + */ + int16_t implicitHeader(size_t len); + + /*! + \brief Set explicit header mode for future reception/transmission. + \param len Payload length in bytes. + \returns \ref status_codes + */ + int16_t explicitHeader(); + + /*! + \brief Sets transmission encoding. Serves only as alias for PhysicalLayer compatibility. + \param encoding Encoding to be used. Set to 0 for NRZ, and 2 for whitening. + \returns \ref status_codes + */ + int16_t setEncoding(uint8_t encoding) override; + + /*! \copydoc Module::setRfSwitchPins */ + void setRfSwitchPins(uint32_t rxEn, uint32_t txEn); + + /*! \copydoc Module::setRfSwitchTable */ + void setRfSwitchTable(const uint32_t (&pins)[Module::RFSWITCH_MAX_PINS], const Module::RfSwitchMode_t table[]); + + /*! + \brief Dummy random method, to ensure PhysicalLayer compatibility. + \returns Always returns 0. + */ + uint8_t randomByte() override; + + /*! + \brief Enable/disable inversion of the I and Q signals + \param enable QI inversion enabled (true) or disabled (false); + \returns \ref status_codes + */ + int16_t invertIQ(bool enable) override; + + #if !RADIOLIB_EXCLUDE_DIRECT_RECEIVE + /*! + \brief Dummy method, to ensure PhysicalLayer compatibility. + \param func Ignored. + */ + void setDirectAction(void (*func)(void)) override; + + /*! + \brief Dummy method, to ensure PhysicalLayer compatibility. + \param pin Ignored. + */ + void readBit(uint32_t pin) override; + #endif + +#if !RADIOLIB_GODMODE && !RADIOLIB_LOW_LEVEL + protected: +#endif + Module* getMod() override; + + // cached LoRa parameters + float bandwidthKhz = 0; + uint8_t bandwidth = 0, spreadingFactor = 0, codingRateLoRa = 0; + uint8_t preambleLengthLoRa = 0, headerType = 0, payloadLen = 0, crcLoRa = 0; + + // SX128x SPI command implementations + uint8_t getStatus(); + int16_t writeRegister(uint16_t addr, uint8_t* data, uint8_t numBytes); + int16_t readRegister(uint16_t addr, uint8_t* data, uint8_t numBytes); + int16_t writeBuffer(uint8_t* data, uint8_t numBytes, uint8_t offset = 0x00); + int16_t readBuffer(uint8_t* data, uint8_t numBytes, uint8_t offset = 0x00); + int16_t setTx(uint16_t periodBaseCount = RADIOLIB_SX128X_TX_TIMEOUT_NONE, uint8_t periodBase = RADIOLIB_SX128X_PERIOD_BASE_15_625_US); + int16_t setRx(uint16_t periodBaseCount, uint8_t periodBase = RADIOLIB_SX128X_PERIOD_BASE_15_625_US); + int16_t setCad(uint8_t symbolNum); + uint8_t getPacketType(); + int16_t setRfFrequency(uint32_t frf); + int16_t setTxParams(uint8_t pwr, uint8_t rampTime = RADIOLIB_SX128X_PA_RAMP_10_US); + int16_t setBufferBaseAddress(uint8_t txBaseAddress = 0x00, uint8_t rxBaseAddress = 0x00); + int16_t setModulationParams(uint8_t modParam1, uint8_t modParam2, uint8_t modParam3); + int16_t setPacketParamsGFSK(uint8_t preambleLen, uint8_t syncLen, uint8_t syncMatch, uint8_t crcLen, uint8_t whiten, uint8_t payLen = 0xFF, uint8_t hdrType = RADIOLIB_SX128X_GFSK_FLRC_PACKET_VARIABLE); + int16_t setPacketParamsBLE(uint8_t connState, uint8_t crcLen, uint8_t bleTest, uint8_t whiten); + int16_t setPacketParamsLoRa(uint8_t preambleLen, uint8_t hdrType, uint8_t payLen, uint8_t crc, uint8_t invIQ = RADIOLIB_SX128X_LORA_IQ_STANDARD); + int16_t setDioIrqParams(uint16_t irqMask, uint16_t dio1Mask, uint16_t dio2Mask = RADIOLIB_SX128X_IRQ_NONE, uint16_t dio3Mask = RADIOLIB_SX128X_IRQ_NONE); + int16_t clearIrqStatus(uint16_t clearIrqParams = RADIOLIB_SX128X_IRQ_ALL); + int16_t setRangingRole(uint8_t role); + int16_t setPacketType(uint8_t type); + +#if !RADIOLIB_GODMODE + private: +#endif + Module* mod; + + // common low-level SPI interface + static int16_t SPIparseStatus(uint8_t in); + + // common parameters + uint8_t power = 0; + + // cached LoRa parameters + uint8_t invertIQEnabled = RADIOLIB_SX128X_LORA_IQ_STANDARD; + + // cached GFSK parameters + float modIndexReal = 0; + uint16_t bitRateKbps = 0; + uint8_t bitRate = 0, modIndex = 0, shaping = 0; + uint8_t preambleLengthGFSK = 0, syncWordLen = 0, syncWordMatch = 0, crcGFSK = 0, whitening = 0; + + // cached FLRC parameters + uint8_t codingRateFLRC = 0; + + // cached BLE parameters + uint8_t connectionState = 0, crcBLE = 0, bleTestPayload = 0; + + int16_t config(uint8_t modem); + int16_t setHeaderType(uint8_t hdrType, size_t len = 0xFF); +}; + +#endif + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/Si443x/Si4430.cpp b/software/firmware/source/libraries/RadioLib/src/modules/Si443x/Si4430.cpp new file mode 100644 index 000000000..7f9ed5fbe --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/Si443x/Si4430.cpp @@ -0,0 +1,39 @@ +#include "Si4430.h" +#if !RADIOLIB_EXCLUDE_SI443X + +Si4430::Si4430(Module* mod) : Si4432(mod) { + +} + +int16_t Si4430::begin(float freq, float br, float freqDev, float rxBw, int8_t power, uint8_t preambleLen) { + // execute common part + int16_t state = Si443x::begin(br, freqDev, rxBw, preambleLen); + RADIOLIB_ASSERT(state); + RADIOLIB_DEBUG_BASIC_PRINTLN("M\tSi4430"); + + // configure publicly accessible settings + state = setFrequency(freq); + RADIOLIB_ASSERT(state); + + state = setOutputPower(power); + RADIOLIB_ASSERT(state); + + return(state); +} + +int16_t Si4430::setFrequency(float freq) { + RADIOLIB_CHECK_RANGE(freq, 900.0, 960.0, RADIOLIB_ERR_INVALID_FREQUENCY); + + // set frequency + return(Si443x::setFrequencyRaw(freq)); +} + +int16_t Si4430::setOutputPower(int8_t power) { + RADIOLIB_CHECK_RANGE(power, -8, 13, RADIOLIB_ERR_INVALID_OUTPUT_POWER); + + // set output power + Module* mod = this->getMod(); + return(mod->SPIsetRegValue(RADIOLIB_SI443X_REG_TX_POWER, (uint8_t)((power + 8) / 3), 2, 0)); +} + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/Si443x/Si4430.h b/software/firmware/source/libraries/RadioLib/src/modules/Si443x/Si4430.h new file mode 100644 index 000000000..2d212e058 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/Si443x/Si4430.h @@ -0,0 +1,67 @@ +#if !defined(_RADIOLIB_SI4430_H) +#define _RADIOLIB_SI4430_H + +#include "../../TypeDef.h" + +#if !RADIOLIB_EXCLUDE_SI443X + +#include "../../Module.h" +#include "Si4432.h" + +/*! + \class Si4430 + \brief Derived class for %Si4430 modules. +*/ +class Si4430: public Si4432 { + public: + + // constructor + + /*! + \brief Default constructor. + \param mod Instance of Module that will be used to communicate with the radio chip. + */ + Si4430(Module* mod); // cppcheck-suppress noExplicitConstructor + + // basic methods + + /*! + \brief Initialization method. Must be called at least once from Arduino sketch to initialize the module. + \param freq Carrier frequency in MHz. Allowed values range from 900.0 MHz to 960.0 MHz. + \param br Bit rate of the FSK transmission in kbps (kilobits per second). Allowed values range from 0.123 to 256.0 kbps. + \param freqDev Frequency deviation of the FSK transmission in kHz. Allowed values range from 0.625 to 320.0 kbps. + \param rxBw Receiver bandwidth in kHz. Allowed values range from 2.6 to 620.7 kHz. + \param power Transmission output power in dBm. Allowed values range from -8 to 13 dBm in 3 dBm steps. + \param preambleLen Preamble Length in bits. Defaults to 16 bits. + \returns \ref status_codes + */ + int16_t begin(float freq = 434.0, float br = 4.8, float freqDev = 5.0, float rxBw = 181.1, int8_t power = 10, uint8_t preambleLen = 16); + + // configuration methods + + /*! + \brief Sets carrier frequency. Allowed values range from 900.0 MHz to 960.0 MHz. + \param freq Carrier frequency to be set in MHz. + \returns \ref status_codes + */ + int16_t setFrequency(float freq) override; + + /*! + \brief Sets output power. Allowed values range from -8 to 13 dBm in 3 dBm steps. + \param power Output power to be set in dBm. + \returns \ref status_codes + */ + int16_t setOutputPower(int8_t power) override; + +#if !RADIOLIB_GODMODE + protected: +#endif + +#if !RADIOLIB_GODMODE + private: +#endif +}; + +#endif + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/Si443x/Si4431.cpp b/software/firmware/source/libraries/RadioLib/src/modules/Si443x/Si4431.cpp new file mode 100644 index 000000000..c603e6f15 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/Si443x/Si4431.cpp @@ -0,0 +1,32 @@ +#include "Si4431.h" +#if !RADIOLIB_EXCLUDE_SI443X + +Si4431::Si4431(Module* mod) : Si4432(mod) { + +} + +int16_t Si4431::begin(float freq, float br, float freqDev, float rxBw, int8_t power, uint8_t preambleLen) { + // execute common part + int16_t state = Si443x::begin(br, freqDev, rxBw, preambleLen); + RADIOLIB_ASSERT(state); + RADIOLIB_DEBUG_BASIC_PRINTLN("M\tSi4431"); + + // configure publicly accessible settings + state = setFrequency(freq); + RADIOLIB_ASSERT(state); + + state = setOutputPower(power); + RADIOLIB_ASSERT(state); + + return(state); +} + +int16_t Si4431::setOutputPower(int8_t power) { + RADIOLIB_CHECK_RANGE(power, -8, 13, RADIOLIB_ERR_INVALID_OUTPUT_POWER); + + // set output power + Module* mod = this->getMod(); + return(mod->SPIsetRegValue(RADIOLIB_SI443X_REG_TX_POWER, (uint8_t)((power + 8) / 3), 2, 0)); +} + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/Si443x/Si4431.h b/software/firmware/source/libraries/RadioLib/src/modules/Si443x/Si4431.h new file mode 100644 index 000000000..a8939ea16 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/Si443x/Si4431.h @@ -0,0 +1,60 @@ +#if !defined(_RADIOLIB_SI4431_H) +#define _RADIOLIB_SI4431_H + +#include "../../TypeDef.h" + +#if !RADIOLIB_EXCLUDE_SI443X + +#include "../../Module.h" +#include "Si4432.h" + +/*! + \class Si4431 + \brief Derived class for %Si4431 modules. +*/ +class Si4431: public Si4432 { + public: + + // constructor + + /*! + \brief Default constructor. + \param mod Instance of Module that will be used to communicate with the radio chip. + */ + Si4431(Module* mod); // cppcheck-suppress noExplicitConstructor + + // basic methods + + /*! + \brief Initialization method. Must be called at least once from Arduino sketch to initialize the module. + \param freq Carrier frequency in MHz. Allowed values range from 240.0 MHz to 930.0 MHz. + \param br Bit rate of the FSK transmission in kbps (kilobits per second). Allowed values range from 0.123 to 256.0 kbps. + \param freqDev Frequency deviation of the FSK transmission in kHz. Allowed values range from 0.625 to 320.0 kbps. + \param rxBw Receiver bandwidth in kHz. Allowed values range from 2.6 to 620.7 kHz. + \param power Transmission output power in dBm. Allowed values range from -8 to 13 dBm in 3 dBm steps. + \param preambleLen Preamble Length in bits. Defaults to 16 bits. + \returns \ref status_codes + */ + int16_t begin(float freq = 434.0, float br = 4.8, float freqDev = 5.0, float rxBw = 181.1, int8_t power = 10, uint8_t preambleLen = 16); + + // configuration methods + + /*! + \brief Sets output power. Allowed values range from -8 to 13 dBm in 3 dBm steps. + \param power Output power to be set in dBm. + \returns \ref status_codes + */ + int16_t setOutputPower(int8_t power) override; + +#if !RADIOLIB_GODMODE + protected: +#endif + +#if !RADIOLIB_GODMODE + private: +#endif +}; + +#endif + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/Si443x/Si4432.cpp b/software/firmware/source/libraries/RadioLib/src/modules/Si443x/Si4432.cpp new file mode 100644 index 000000000..56690862f --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/Si443x/Si4432.cpp @@ -0,0 +1,39 @@ +#include "Si4432.h" +#if !RADIOLIB_EXCLUDE_SI443X + +Si4432::Si4432(Module* mod) : Si443x(mod) { + +} + +int16_t Si4432::begin(float freq, float br, float freqDev, float rxBw, int8_t power, uint8_t preambleLen) { + // execute common part + int16_t state = Si443x::begin(br, freqDev, rxBw, preambleLen); + RADIOLIB_ASSERT(state); + RADIOLIB_DEBUG_BASIC_PRINTLN("M\tSi4432"); + + // configure publicly accessible settings + state = setFrequency(freq); + RADIOLIB_ASSERT(state); + + state = setOutputPower(power); + RADIOLIB_ASSERT(state); + + return(state); +} + +int16_t Si4432::setFrequency(float freq) { + RADIOLIB_CHECK_RANGE(freq, 240.0, 930.0, RADIOLIB_ERR_INVALID_FREQUENCY); + + // set frequency + return(Si443x::setFrequencyRaw(freq)); +} + +int16_t Si4432::setOutputPower(int8_t power) { + RADIOLIB_CHECK_RANGE(power, -1, 20, RADIOLIB_ERR_INVALID_OUTPUT_POWER); + + // set output power + Module* mod = this->getMod(); + return(mod->SPIsetRegValue(RADIOLIB_SI443X_REG_TX_POWER, (uint8_t)((power + 1) / 3), 2, 0)); +} + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/Si443x/Si4432.h b/software/firmware/source/libraries/RadioLib/src/modules/Si443x/Si4432.h new file mode 100644 index 000000000..f3ac06613 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/Si443x/Si4432.h @@ -0,0 +1,67 @@ +#if !defined(_RADIOLIB_SI4432_H) +#define _RADIOLIB_SI4432_H + +#include "../../TypeDef.h" + +#if !RADIOLIB_EXCLUDE_SI443X + +#include "../../Module.h" +#include "Si443x.h" + +/*! + \class Si4432 + \brief Derived class for %Si4432 modules. +*/ +class Si4432: public Si443x { + public: + + // constructor + + /*! + \brief Default constructor. + \param mod Instance of Module that will be used to communicate with the radio chip. + */ + Si4432(Module* mod); // cppcheck-suppress noExplicitConstructor + + // basic methods + + /*! + \brief Initialization method. Must be called at least once from Arduino sketch to initialize the module. + \param freq Carrier frequency in MHz. Allowed values range from 240.0 MHz to 930.0 MHz. + \param br Bit rate of the FSK transmission in kbps (kilobits per second). Allowed values range from 0.123 to 256.0 kbps. + \param freqDev Frequency deviation of the FSK transmission in kHz. Allowed values range from 0.625 to 320.0 kbps. + \param rxBw Receiver bandwidth in kHz. Allowed values range from 2.6 to 620.7 kHz. + \param power Transmission output power in dBm. Allowed values range from -1 to 20 dBm in 3 dBm steps. + \param preambleLen Preamble Length in bits. Defaults to 16 bits. + \returns \ref status_codes + */ + int16_t begin(float freq = 434.0, float br = 4.8, float freqDev = 5.0, float rxBw = 181.1, int8_t power = 10, uint8_t preambleLen = 16); + + // configuration methods + + /*! + \brief Sets carrier frequency. Allowed values range from 240.0 MHz to 930.0 MHz. + \param freq Carrier frequency to be set in MHz. + \returns \ref status_codes + */ + int16_t setFrequency(float freq) override; + + /*! + \brief Sets output power. Allowed values range from -1 to 20 dBm in 3 dBm steps. + \param power Output power to be set in dBm. + \returns \ref status_codes + */ + int16_t setOutputPower(int8_t power) override; + +#if !RADIOLIB_GODMODE + protected: +#endif + +#if !RADIOLIB_GODMODE + private: +#endif +}; + +#endif + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/Si443x/Si443x.cpp b/software/firmware/source/libraries/RadioLib/src/modules/Si443x/Si443x.cpp new file mode 100644 index 000000000..4db2ffca9 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/Si443x/Si443x.cpp @@ -0,0 +1,809 @@ +#include "Si443x.h" +#include +#if !RADIOLIB_EXCLUDE_SI443X + +Si443x::Si443x(Module* mod) : PhysicalLayer(RADIOLIB_SI443X_FREQUENCY_STEP_SIZE, RADIOLIB_SI443X_MAX_PACKET_LENGTH) { + this->mod = mod; +} + +int16_t Si443x::begin(float br, float freqDev, float rxBw, uint8_t preambleLen) { + // set module properties + this->mod->init(); + this->mod->hal->pinMode(this->mod->getIrq(), this->mod->hal->GpioModeInput); + this->mod->hal->pinMode(this->mod->getRst(), this->mod->hal->GpioModeOutput); + this->mod->hal->digitalWrite(this->mod->getRst(), this->mod->hal->GpioLevelLow); + + // try to find the Si443x chip + if(!Si443x::findChip()) { + RADIOLIB_DEBUG_BASIC_PRINTLN("No Si443x found!"); + this->mod->term(); + return(RADIOLIB_ERR_CHIP_NOT_FOUND); + } else { + RADIOLIB_DEBUG_BASIC_PRINTLN("M\tSi443x"); + } + + // reset the device + this->mod->SPIwriteRegister(RADIOLIB_SI443X_REG_OP_FUNC_CONTROL_1, RADIOLIB_SI443X_SOFTWARE_RESET); + + // clear POR interrupt + clearIRQFlags(); + + // configure settings not accessible by API + int16_t state = config(); + RADIOLIB_ASSERT(state); + + // configure publicly accessible settings + state = setBitRate(br); + RADIOLIB_ASSERT(state); + + state = setFrequencyDeviation(freqDev); + RADIOLIB_ASSERT(state); + + state = setRxBandwidth(rxBw); + RADIOLIB_ASSERT(state); + + state = setPreambleLength(preambleLen); + RADIOLIB_ASSERT(state); + + uint8_t syncWord[] = {0x12, 0xAD}; + state = setSyncWord(syncWord, sizeof(syncWord)); + RADIOLIB_ASSERT(state); + + state = packetMode(); + RADIOLIB_ASSERT(state); + + state = setDataShaping(0); + RADIOLIB_ASSERT(state); + + state = setEncoding(0); + RADIOLIB_ASSERT(state); + + state = variablePacketLengthMode(); + + return(state); +} + +void Si443x::reset() { + this->mod->hal->pinMode(this->mod->getRst(), this->mod->hal->GpioModeOutput); + this->mod->hal->digitalWrite(this->mod->getRst(), this->mod->hal->GpioLevelHigh); + this->mod->hal->delay(1); + this->mod->hal->digitalWrite(this->mod->getRst(), this->mod->hal->GpioLevelLow); + this->mod->hal->delay(100); +} + +int16_t Si443x::transmit(const uint8_t* data, size_t len, uint8_t addr) { + // calculate timeout (5ms + 500 % of expected time-on-air) + RadioLibTime_t timeout = 5 + (uint32_t)((((float)(len * 8)) / this->bitRate) * 5); + + // start transmission + int16_t state = startTransmit(data, len, addr); + RADIOLIB_ASSERT(state); + + // wait for transmission end or timeout + RadioLibTime_t start = this->mod->hal->millis(); + while(this->mod->hal->digitalRead(this->mod->getIrq())) { + this->mod->hal->yield(); + if(this->mod->hal->millis() - start > timeout) { + finishTransmit(); + return(RADIOLIB_ERR_TX_TIMEOUT); + } + } + + return(finishTransmit()); +} + +int16_t Si443x::receive(uint8_t* data, size_t len) { + // calculate timeout (500 ms + 400 full 64-byte packets at current bit rate) + RadioLibTime_t timeout = 500 + (1.0/(this->bitRate))*(RADIOLIB_SI443X_MAX_PACKET_LENGTH*400.0); + + // start reception + int16_t state = startReceive(); + RADIOLIB_ASSERT(state); + + // wait for packet reception or timeout + RadioLibTime_t start = this->mod->hal->millis(); + while(this->mod->hal->digitalRead(this->mod->getIrq())) { + if(this->mod->hal->millis() - start > timeout) { + standby(); + clearIRQFlags(); + return(RADIOLIB_ERR_RX_TIMEOUT); + } + } + + // read packet data + return(readData(data, len)); +} + +int16_t Si443x::sleep() { + // set RF switch (if present) + this->mod->setRfSwitchState(Module::MODE_IDLE); + + // disable wakeup timer interrupt + int16_t state = this->mod->SPIsetRegValue(RADIOLIB_SI443X_REG_INTERRUPT_ENABLE_1, 0x00); + RADIOLIB_ASSERT(state); + state = this->mod->SPIsetRegValue(RADIOLIB_SI443X_REG_INTERRUPT_ENABLE_2, 0x00); + RADIOLIB_ASSERT(state); + + // enable wakeup timer to set mode to sleep + this->mod->SPIwriteRegister(RADIOLIB_SI443X_REG_OP_FUNC_CONTROL_1, RADIOLIB_SI443X_ENABLE_WAKEUP_TIMER); + + return(state); +} + +int16_t Si443x::standby() { + return(standby(RADIOLIB_SI443X_XTAL_ON)); +} + +int16_t Si443x::standby(uint8_t mode) { + // set RF switch (if present) + this->mod->setRfSwitchState(Module::MODE_IDLE); + return(this->mod->SPIsetRegValue(RADIOLIB_SI443X_REG_OP_FUNC_CONTROL_1, mode, 7, 0, 10)); +} + +int16_t Si443x::transmitDirect(uint32_t frf) { + // set RF switch (if present) + this->mod->setRfSwitchState(Module::MODE_TX); + + // user requested to start transmitting immediately (required for RTTY) + if(frf != 0) { + // convert the 24-bit frequency to the format accepted by the module + /// \todo integers only + float newFreq = frf / 6400.0; + + // check high/low band + uint8_t bandSelect = RADIOLIB_SI443X_BAND_SELECT_LOW; + uint8_t freqBand = (newFreq / 10) - 24; + if(newFreq >= 480.0) { + bandSelect = RADIOLIB_SI443X_BAND_SELECT_HIGH; + freqBand = (newFreq / 20) - 24; + } + + // calculate register values + uint16_t freqCarrier = ((newFreq / (10 * ((bandSelect >> 5) + 1))) - freqBand - 24) * (uint32_t)64000; + + // update registers + this->mod->SPIwriteRegister(RADIOLIB_SI443X_REG_FREQUENCY_BAND_SELECT, RADIOLIB_SI443X_SIDE_BAND_SELECT_LOW | bandSelect | freqBand); + this->mod->SPIwriteRegister(RADIOLIB_SI443X_REG_NOM_CARRIER_FREQUENCY_1, (uint8_t)((freqCarrier & 0xFF00) >> 8)); + this->mod->SPIwriteRegister(RADIOLIB_SI443X_REG_NOM_CARRIER_FREQUENCY_0, (uint8_t)(freqCarrier & 0xFF)); + + // start direct transmission + directMode(); + this->mod->SPIwriteRegister(RADIOLIB_SI443X_REG_OP_FUNC_CONTROL_1, RADIOLIB_SI443X_TX_ON | RADIOLIB_SI443X_XTAL_ON); + + return(RADIOLIB_ERR_NONE); + } + + // activate direct mode + int16_t state = directMode(); + RADIOLIB_ASSERT(state); + + // start transmitting + this->mod->SPIwriteRegister(RADIOLIB_SI443X_REG_OP_FUNC_CONTROL_1, RADIOLIB_SI443X_TX_ON | RADIOLIB_SI443X_XTAL_ON); + return(state); +} + +int16_t Si443x::receiveDirect() { + // set RF switch (if present) + this->mod->setRfSwitchState(Module::MODE_RX); + + // activate direct mode + int16_t state = directMode(); + RADIOLIB_ASSERT(state); + + // start receiving + this->mod->SPIwriteRegister(RADIOLIB_SI443X_REG_OP_FUNC_CONTROL_1, RADIOLIB_SI443X_RX_ON | RADIOLIB_SI443X_XTAL_ON); + return(state); +} + +int16_t Si443x::packetMode() { + int16_t state = this->mod->SPIsetRegValue(RADIOLIB_SI443X_REG_MODULATION_MODE_CONTROL_2, RADIOLIB_SI443X_MODULATION_FSK, 1, 0); + RADIOLIB_ASSERT(state); + + return(this->mod->SPIsetRegValue(RADIOLIB_SI443X_REG_MODULATION_MODE_CONTROL_2, RADIOLIB_SI443X_TX_DATA_SOURCE_FIFO, 5, 4)); +} + +void Si443x::setIrqAction(void (*func)(void)) { + this->mod->hal->attachInterrupt(this->mod->hal->pinToInterrupt(this->mod->getIrq()), func, this->mod->hal->GpioInterruptFalling); +} + +void Si443x::clearIrqAction() { + this->mod->hal->detachInterrupt(this->mod->hal->pinToInterrupt(this->mod->getIrq())); +} + +void Si443x::setPacketReceivedAction(void (*func)(void)) { + this->setIrqAction(func); +} + +void Si443x::clearPacketReceivedAction() { + this->clearIrqAction(); +} + +void Si443x::setPacketSentAction(void (*func)(void)) { + this->setIrqAction(func); +} + +void Si443x::clearPacketSentAction() { + this->clearIrqAction(); +} + +int16_t Si443x::startTransmit(const uint8_t* data, size_t len, uint8_t addr) { + // check packet length + if(len > RADIOLIB_SI443X_MAX_PACKET_LENGTH) { + return(RADIOLIB_ERR_PACKET_TOO_LONG); + } + + // set mode to standby + int16_t state = standby(); + RADIOLIB_ASSERT(state); + + // clear Tx FIFO + this->mod->SPIsetRegValue(RADIOLIB_SI443X_REG_OP_FUNC_CONTROL_2, RADIOLIB_SI443X_TX_FIFO_RESET, 0, 0); + this->mod->SPIsetRegValue(RADIOLIB_SI443X_REG_OP_FUNC_CONTROL_2, RADIOLIB_SI443X_TX_FIFO_CLEAR, 0, 0); + + // clear interrupt flags + clearIRQFlags(); + + // set packet length + if (this->packetLengthConfig == RADIOLIB_SI443X_FIXED_PACKET_LENGTH_OFF) { + this->mod->SPIwriteRegister(RADIOLIB_SI443X_REG_TRANSMIT_PACKET_LENGTH, len); + } + + /// \todo use header as address field? + (void)addr; + + // write packet to FIFO + this->mod->SPIwriteRegisterBurst(RADIOLIB_SI443X_REG_FIFO_ACCESS, const_cast(data), len); + + // set RF switch (if present) + this->mod->setRfSwitchState(Module::MODE_TX); + + // set interrupt mapping + this->mod->SPIwriteRegister(RADIOLIB_SI443X_REG_INTERRUPT_ENABLE_1, RADIOLIB_SI443X_PACKET_SENT_ENABLED); + this->mod->SPIwriteRegister(RADIOLIB_SI443X_REG_INTERRUPT_ENABLE_2, 0x00); + + // set mode to transmit + this->mod->SPIwriteRegister(RADIOLIB_SI443X_REG_OP_FUNC_CONTROL_1, RADIOLIB_SI443X_TX_ON | RADIOLIB_SI443X_XTAL_ON); + + return(state); +} + +int16_t Si443x::finishTransmit() { + // clear interrupt flags + clearIRQFlags(); + + // set mode to standby to disable transmitter/RF switch + return(standby()); +} + +int16_t Si443x::startReceive() { + // set mode to standby + int16_t state = standby(); + RADIOLIB_ASSERT(state); + + // clear Rx FIFO + this->mod->SPIsetRegValue(RADIOLIB_SI443X_REG_OP_FUNC_CONTROL_2, RADIOLIB_SI443X_RX_FIFO_RESET, 1, 1); + this->mod->SPIsetRegValue(RADIOLIB_SI443X_REG_OP_FUNC_CONTROL_2, RADIOLIB_SI443X_RX_FIFO_CLEAR, 1, 1); + + // clear interrupt flags + clearIRQFlags(); + + // set RF switch (if present) + this->mod->setRfSwitchState(Module::MODE_RX); + + // set interrupt mapping + this->mod->SPIwriteRegister(RADIOLIB_SI443X_REG_INTERRUPT_ENABLE_1, RADIOLIB_SI443X_VALID_PACKET_RECEIVED_ENABLED | RADIOLIB_SI443X_CRC_ERROR_ENABLED); + this->mod->SPIwriteRegister(RADIOLIB_SI443X_REG_INTERRUPT_ENABLE_2, 0x00); + + // set mode to receive + this->mod->SPIwriteRegister(RADIOLIB_SI443X_REG_OP_FUNC_CONTROL_1, RADIOLIB_SI443X_RX_ON | RADIOLIB_SI443X_XTAL_ON); + + return(state); +} + +int16_t Si443x::startReceive(uint32_t timeout, uint32_t irqFlags, uint32_t irqMask, size_t len) { + (void)timeout; + (void)irqFlags; + (void)irqMask; + (void)len; + return(startReceive()); +} + +int16_t Si443x::readData(uint8_t* data, size_t len) { + // clear interrupt flags + clearIRQFlags(); + + // get packet length + size_t length = getPacketLength(); + size_t dumpLen = 0; + if((len != 0) && (len < length)) { + // user requested less data than we got, only return what was requested + dumpLen = length - len; + length = len; + } + + // read packet data + this->mod->SPIreadRegisterBurst(RADIOLIB_SI443X_REG_FIFO_ACCESS, length, data); + + // dump the bytes that weren't requested + if(dumpLen != 0) { + clearFIFO(dumpLen); + } + + // clear internal flag so getPacketLength can return the new packet length + this->packetLengthQueried = false; + + // set mode to standby + int16_t state = standby(); + RADIOLIB_ASSERT(state); + + // clear interrupt flags + clearIRQFlags(); + + return(RADIOLIB_ERR_NONE); +} + +int16_t Si443x::setBitRate(float br) { + RADIOLIB_CHECK_RANGE(br, 0.123, 256.0, RADIOLIB_ERR_INVALID_BIT_RATE); + + // check high data rate + uint8_t dataRateMode = RADIOLIB_SI443X_LOW_DATA_RATE_MODE; + uint8_t exp = 21; + if(br >= 30.0) { + // bit rate above 30 kbps + dataRateMode = RADIOLIB_SI443X_HIGH_DATA_RATE_MODE; + exp = 16; + } + + // calculate raw data rate value + uint16_t txDr = (br * ((uint32_t)1 << exp)) / 1000.0; + + // update registers + int16_t state = this->mod->SPIsetRegValue(RADIOLIB_SI443X_REG_MODULATION_MODE_CONTROL_1, dataRateMode, 5, 5); + this->mod->SPIwriteRegister(RADIOLIB_SI443X_REG_TX_DATA_RATE_1, (uint8_t)((txDr & 0xFF00) >> 8)); + this->mod->SPIwriteRegister(RADIOLIB_SI443X_REG_TX_DATA_RATE_0, (uint8_t)(txDr & 0xFF)); + + if(state == RADIOLIB_ERR_NONE) { + this->bitRate = br; + } + RADIOLIB_ASSERT(state); + + // update clock recovery + state = updateClockRecovery(); + + return(state); +} + +int16_t Si443x::setFrequencyDeviation(float freqDev) { + // set frequency deviation to lowest available setting (required for digimodes) + float newFreqDev = freqDev; + if(freqDev < 0.0) { + newFreqDev = 0.625; + } + + RADIOLIB_CHECK_RANGE(newFreqDev, 0.625, 320.0, RADIOLIB_ERR_INVALID_FREQUENCY_DEVIATION); + + // calculate raw frequency deviation value + uint16_t fdev = (uint16_t)(newFreqDev / 0.625); + + // update registers + int16_t state = this->mod->SPIsetRegValue(RADIOLIB_SI443X_REG_MODULATION_MODE_CONTROL_2, (uint8_t)((fdev & 0x0100) >> 6), 2, 2); + this->mod->SPIwriteRegister(RADIOLIB_SI443X_REG_FREQUENCY_DEVIATION, (uint8_t)(fdev & 0xFF)); + + if(state == RADIOLIB_ERR_NONE) { + this->frequencyDev = newFreqDev; + } + + return(state); +} + +int16_t Si443x::setRxBandwidth(float rxBw) { + RADIOLIB_CHECK_RANGE(rxBw, 2.6, 620.7, RADIOLIB_ERR_INVALID_RX_BANDWIDTH); + + // decide which approximation to use for decimation rate and filter tap calculation + uint8_t bypass = RADIOLIB_SI443X_BYPASS_DEC_BY_3_OFF; + uint8_t decRate = RADIOLIB_SI443X_IF_FILTER_DEC_RATE; + uint8_t filterSet = RADIOLIB_SI443X_IF_FILTER_COEFF_SET; + + // this is the "well-behaved" section - can be linearly approximated + if((rxBw >= 2.6) && (rxBw <= 4.5)) { + decRate = 5; + filterSet = ((rxBw - 2.1429)/0.3250 + 0.5); + } else if((rxBw > 4.5) && (rxBw <= 8.8)) { + decRate = 4; + filterSet = ((rxBw - 3.9857)/0.6643 + 0.5); + } else if((rxBw > 8.8) && (rxBw <= 17.5)) { + decRate = 3; + filterSet = ((rxBw - 7.6714)/1.3536 + 0.5); + } else if((rxBw > 17.5) && (rxBw <= 34.7)) { + decRate = 2; + filterSet = ((rxBw - 15.2000)/2.6893 + 0.5); + } else if((rxBw > 34.7) && (rxBw <= 69.2)) { + decRate = 1; + filterSet = ((rxBw - 30.2430)/5.3679 + 0.5); + } else if((rxBw > 69.2) && (rxBw <= 137.9)) { + decRate = 0; + filterSet = ((rxBw - 60.286)/10.7000 + 0.5); + + // this is the "Lord help thee who tread 'ere" section - no way to approximate this mess + /// \todo float tolerance equality as macro? + } else if(fabsf(rxBw - 142.8) <= 0.001) { + bypass = RADIOLIB_SI443X_BYPASS_DEC_BY_3_ON; + decRate = 1; + filterSet = 4; + } else if(fabsf(rxBw - 167.8) <= 0.001) { + bypass = RADIOLIB_SI443X_BYPASS_DEC_BY_3_ON; + decRate = 1; + filterSet = 5; + } else if(fabsf(rxBw - 181.1) <= 0.001) { + bypass = RADIOLIB_SI443X_BYPASS_DEC_BY_3_ON; + decRate = 1; + filterSet = 6; + } else if(fabsf(rxBw - 191.5) <= 0.001) { + bypass = RADIOLIB_SI443X_BYPASS_DEC_BY_3_ON; + decRate = 0; + filterSet = 15; + } else if(fabsf(rxBw - 225.1) <= 0.001) { + bypass = RADIOLIB_SI443X_BYPASS_DEC_BY_3_ON; + decRate = 0; + filterSet = 1; + } else if(fabsf(rxBw - 248.8) <= 0.001) { + bypass = RADIOLIB_SI443X_BYPASS_DEC_BY_3_ON; + decRate = 0; + filterSet = 2; + } else if(fabsf(rxBw - 269.3) <= 0.001) { + bypass = RADIOLIB_SI443X_BYPASS_DEC_BY_3_ON; + decRate = 0; + filterSet = 3; + } else if(fabsf(rxBw - 284.8) <= 0.001) { + bypass = RADIOLIB_SI443X_BYPASS_DEC_BY_3_ON; + decRate = 0; + filterSet = 4; + } else if(fabsf(rxBw -335.5) <= 0.001) { + bypass = RADIOLIB_SI443X_BYPASS_DEC_BY_3_ON; + decRate = 0; + filterSet = 8; + } else if(fabsf(rxBw - 391.8) <= 0.001) { + bypass = RADIOLIB_SI443X_BYPASS_DEC_BY_3_ON; + decRate = 0; + filterSet = 9; + } else if(fabsf(rxBw - 420.2) <= 0.001) { + bypass = RADIOLIB_SI443X_BYPASS_DEC_BY_3_ON; + decRate = 0; + filterSet = 10; + } else if(fabsf(rxBw - 468.4) <= 0.001) { + bypass = RADIOLIB_SI443X_BYPASS_DEC_BY_3_ON; + decRate = 0; + filterSet = 11; + } else if(fabsf(rxBw - 518.8) <= 0.001) { + bypass = RADIOLIB_SI443X_BYPASS_DEC_BY_3_ON; + decRate = 0; + filterSet = 12; + } else if(fabsf(rxBw - 577.0) <= 0.001) { + bypass = RADIOLIB_SI443X_BYPASS_DEC_BY_3_ON; + decRate = 0; + filterSet = 13; + } else if(fabsf(rxBw - 620.7) <= 0.001) { + bypass = RADIOLIB_SI443X_BYPASS_DEC_BY_3_ON; + decRate = 0; + filterSet = 14; + } else { + return(RADIOLIB_ERR_INVALID_RX_BANDWIDTH); + } + + // shift decimation rate bits + decRate <<= 4; + + // update register + int16_t state = this->mod->SPIsetRegValue(RADIOLIB_SI443X_REG_IF_FILTER_BANDWIDTH, bypass | decRate | filterSet); + RADIOLIB_ASSERT(state); + + // update clock recovery + state = updateClockRecovery(); + + return(state); +} + +int16_t Si443x::setSyncWord(uint8_t* syncWord, size_t len) { + RADIOLIB_CHECK_RANGE(len, 1, 4, RADIOLIB_ERR_INVALID_SYNC_WORD); + + // set mode to standby + int16_t state = standby(); + RADIOLIB_ASSERT(state); + + // set sync word length + state = this->mod->SPIsetRegValue(RADIOLIB_SI443X_REG_HEADER_CONTROL_2, (uint8_t)(len - 1) << 1, 2, 1); + RADIOLIB_ASSERT(state); + + // set sync word bytes + this->mod->SPIwriteRegisterBurst(RADIOLIB_SI443X_REG_SYNC_WORD_3, syncWord, len); + + return(state); +} + +int16_t Si443x::setPreambleLength(uint8_t preambleLen) { + // Si443x configures preamble length in 4-bit nibbles + if(preambleLen % 4 != 0) { + return(RADIOLIB_ERR_INVALID_PREAMBLE_LENGTH); + } + + // set default preamble length + uint8_t preLenNibbles = preambleLen / 4; + int16_t state = this->mod->SPIsetRegValue(RADIOLIB_SI443X_REG_PREAMBLE_LENGTH, preLenNibbles); + RADIOLIB_ASSERT(state); + + // set default preamble detection threshold to 5/8 of preamble length (in units of 4 bits) + uint8_t preThreshold = 5*preLenNibbles / 8; + return(this->mod->SPIsetRegValue(RADIOLIB_SI443X_REG_PREAMBLE_DET_CONTROL, preThreshold << 3, 7, 3)); +} + +size_t Si443x::getPacketLength(bool update) { + if(!this->packetLengthQueried && update) { + if (this->packetLengthConfig == RADIOLIB_SI443X_FIXED_PACKET_LENGTH_ON) { + this->packetLength = this->mod->SPIreadRegister(RADIOLIB_SI443X_REG_TRANSMIT_PACKET_LENGTH); + } else { + this->packetLength = this->mod->SPIreadRegister(RADIOLIB_SI443X_REG_RECEIVED_PACKET_LENGTH); + } + this->packetLengthQueried = true; + } + + return(this->packetLength); +} + +int16_t Si443x::setEncoding(uint8_t encoding) { + // set mode to standby + int16_t state = standby(); + RADIOLIB_ASSERT(state); + + // set encoding + /// \todo - add inverted Manchester? + switch(encoding) { + case RADIOLIB_ENCODING_NRZ: + return(this->mod->SPIsetRegValue(RADIOLIB_SI443X_REG_MODULATION_MODE_CONTROL_1, RADIOLIB_SI443X_MANCHESTER_OFF | RADIOLIB_SI443X_WHITENING_OFF, 2, 0)); + case RADIOLIB_ENCODING_MANCHESTER: + return(this->mod->SPIsetRegValue(RADIOLIB_SI443X_REG_MODULATION_MODE_CONTROL_1, RADIOLIB_SI443X_MANCHESTER_ON | RADIOLIB_SI443X_WHITENING_OFF, 2, 0)); + case RADIOLIB_ENCODING_WHITENING: + return(this->mod->SPIsetRegValue(RADIOLIB_SI443X_REG_MODULATION_MODE_CONTROL_1, RADIOLIB_SI443X_MANCHESTER_OFF | RADIOLIB_SI443X_WHITENING_ON, 2, 0)); + default: + return(RADIOLIB_ERR_INVALID_ENCODING); + } +} + +int16_t Si443x::setDataShaping(uint8_t sh) { + // set mode to standby + int16_t state = standby(); + RADIOLIB_ASSERT(state); + + // set data shaping + switch(sh) { + case RADIOLIB_SHAPING_NONE: + return(this->mod->SPIsetRegValue(RADIOLIB_SI443X_REG_MODULATION_MODE_CONTROL_1, RADIOLIB_SI443X_MANCHESTER_INVERTED_OFF | RADIOLIB_SI443X_MANCHESTER_OFF | RADIOLIB_SI443X_WHITENING_OFF, 2, 0)); + case RADIOLIB_SHAPING_0_5: + return(this->mod->SPIsetRegValue(RADIOLIB_SI443X_REG_MODULATION_MODE_CONTROL_2, RADIOLIB_SI443X_MODULATION_GFSK, 1, 0)); + default: + return(RADIOLIB_ERR_INVALID_ENCODING); + } +} + +void Si443x::setRfSwitchPins(uint32_t rxEn, uint32_t txEn) { + this->mod->setRfSwitchPins(rxEn, txEn); +} + +void Si443x::setRfSwitchTable(const uint32_t (&pins)[Module::RFSWITCH_MAX_PINS], const Module::RfSwitchMode_t table[]) { + this->mod->setRfSwitchTable(pins, table); +} + +uint8_t Si443x::randomByte() { + // set mode to Rx + this->mod->SPIwriteRegister(RADIOLIB_SI443X_REG_OP_FUNC_CONTROL_1, RADIOLIB_SI443X_RX_ON | RADIOLIB_SI443X_XTAL_ON); + + // wait a bit for the RSSI reading to stabilise + this->mod->hal->delay(10); + + // read RSSI value 8 times, always keep just the least significant bit + uint8_t randByte = 0x00; + for(uint8_t i = 0; i < 8; i++) { + randByte |= ((this->mod->SPIreadRegister(RADIOLIB_SI443X_REG_RSSI) & 0x01) << i); + } + + // set mode to standby + standby(); + + return(randByte); +} + +int16_t Si443x::getChipVersion() { + return(this->mod->SPIgetRegValue(RADIOLIB_SI443X_REG_DEVICE_VERSION)); +} + +#if !RADIOLIB_EXCLUDE_DIRECT_RECEIVE +void Si443x::setDirectAction(void (*func)(void)) { + setIrqAction(func); +} + +void Si443x::readBit(uint32_t pin) { + updateDirectBuffer((uint8_t)this->mod->hal->digitalRead(pin)); +} +#endif + +int16_t Si443x::fixedPacketLengthMode(uint8_t len) { + return(Si443x::setPacketMode(RADIOLIB_SI443X_FIXED_PACKET_LENGTH_ON, len)); +} + +int16_t Si443x::variablePacketLengthMode(uint8_t maxLen) { + return(Si443x::setPacketMode(RADIOLIB_SI443X_FIXED_PACKET_LENGTH_OFF, maxLen)); +} + +Module* Si443x::getMod() { + return(this->mod); +} + +int16_t Si443x::setFrequencyRaw(float newFreq) { + // set mode to standby + int16_t state = standby(); + RADIOLIB_ASSERT(state); + + // check high/low band + uint8_t bandSelect = RADIOLIB_SI443X_BAND_SELECT_LOW; + uint8_t freqBand = (newFreq / 10) - 24; + uint8_t afcLimiter = 80; + this->frequency = newFreq; + if(newFreq >= 480.0) { + bandSelect = RADIOLIB_SI443X_BAND_SELECT_HIGH; + freqBand = (newFreq / 20) - 24; + afcLimiter = 40; + } + + // calculate register values + uint16_t freqCarrier = ((newFreq / (10 * ((bandSelect >> 5) + 1))) - freqBand - 24) * (uint32_t)64000; + + // update registers + state = this->mod->SPIsetRegValue(RADIOLIB_SI443X_REG_FREQUENCY_BAND_SELECT, bandSelect | freqBand, 5, 0); + state |= this->mod->SPIsetRegValue(RADIOLIB_SI443X_REG_NOM_CARRIER_FREQUENCY_1, (uint8_t)((freqCarrier & 0xFF00) >> 8)); + state |= this->mod->SPIsetRegValue(RADIOLIB_SI443X_REG_NOM_CARRIER_FREQUENCY_0, (uint8_t)(freqCarrier & 0xFF)); + state |= this->mod->SPIsetRegValue(RADIOLIB_SI443X_REG_AFC_LIMITER, afcLimiter); + + return(state); +} + +int16_t Si443x::setPacketMode(uint8_t mode, uint8_t len) { + // check packet length + if (len > RADIOLIB_SI443X_MAX_PACKET_LENGTH) { + return(RADIOLIB_ERR_PACKET_TOO_LONG); + } + + // set to fixed packet length + int16_t state = this->mod->SPIsetRegValue(RADIOLIB_SI443X_REG_HEADER_CONTROL_2, mode, 3, 3); + RADIOLIB_ASSERT(state); + + // set length to register + state = this->mod->SPIsetRegValue(RADIOLIB_SI443X_REG_TRANSMIT_PACKET_LENGTH, len); + RADIOLIB_ASSERT(state); + + // update cached value + this->packetLengthConfig = mode; + return(state); +} + +bool Si443x::findChip() { + uint8_t i = 0; + bool flagFound = false; + while((i < 10) && !flagFound) { + // reset the module + reset(); + + // check version register + uint8_t version = this->mod->SPIreadRegister(RADIOLIB_SI443X_REG_DEVICE_VERSION); + if(version == RADIOLIB_SI443X_DEVICE_VERSION) { + flagFound = true; + } else { + RADIOLIB_DEBUG_BASIC_PRINTLN("Si443x not found! (%d of 10 tries) RADIOLIB_SI443X_REG_DEVICE_VERSION == 0x%02X, expected 0x0%X", i + 1, version, RADIOLIB_SI443X_DEVICE_VERSION); + this->mod->hal->delay(10); + i++; + } + } + + return(flagFound); +} + +void Si443x::clearIRQFlags() { + uint8_t buff[2]; + this->mod->SPIreadRegisterBurst(RADIOLIB_SI443X_REG_INTERRUPT_STATUS_1, 2, buff); +} + +void Si443x::clearFIFO(size_t count) { + while(count) { + this->mod->SPIreadRegister(RADIOLIB_SI443X_REG_FIFO_ACCESS); + count--; + } +} + +int16_t Si443x::config() { + // set mode to standby + int16_t state = standby(); + RADIOLIB_ASSERT(state); + + // disable POR and chip ready interrupts + this->mod->SPIwriteRegister(RADIOLIB_SI443X_REG_INTERRUPT_ENABLE_2, 0x00); + + // enable AGC + state = this->mod->SPIsetRegValue(RADIOLIB_SI443X_REG_AGC_OVERRIDE_1, RADIOLIB_SI443X_AGC_GAIN_INCREASE_ON | RADIOLIB_SI443X_AGC_ON, 6, 5); + RADIOLIB_ASSERT(state); + + // disable packet header + state = this->mod->SPIsetRegValue(RADIOLIB_SI443X_REG_HEADER_CONTROL_2, RADIOLIB_SI443X_SYNC_WORD_TIMEOUT_OFF | RADIOLIB_SI443X_HEADER_LENGTH_HEADER_NONE, 7, 4); + RADIOLIB_ASSERT(state); + + // set antenna switching + this->mod->SPIsetRegValue(RADIOLIB_SI443X_REG_GPIO0_CONFIG, RADIOLIB_SI443X_GPIOX_TX_STATE_OUT, 4, 0); + this->mod->SPIsetRegValue(RADIOLIB_SI443X_REG_GPIO1_CONFIG, RADIOLIB_SI443X_GPIOX_RX_STATE_OUT, 4, 0); + + // disable packet header checking + state = this->mod->SPIsetRegValue(RADIOLIB_SI443X_REG_HEADER_CONTROL_1, RADIOLIB_SI443X_BROADCAST_ADDR_CHECK_NONE | RADIOLIB_SI443X_RECEIVED_HEADER_CHECK_NONE); + RADIOLIB_ASSERT(state); + + return(state); +} + +int16_t Si443x::updateClockRecovery() { + // get the parameters + uint8_t bypass = this->mod->SPIgetRegValue(RADIOLIB_SI443X_REG_IF_FILTER_BANDWIDTH, 7, 7) >> 7; + uint8_t decRate = this->mod->SPIgetRegValue(RADIOLIB_SI443X_REG_IF_FILTER_BANDWIDTH, 6, 4) >> 4; + uint8_t manch = this->mod->SPIgetRegValue(RADIOLIB_SI443X_REG_MODULATION_MODE_CONTROL_1, 1, 1) >> 1; + + // calculate oversampling ratio, NCO offset and clock recovery gain + int8_t ndecExp = (int8_t)decRate - 3; + float ndec = 0; + if(ndecExp > 0) { + ndec = (uint16_t)1 << ndecExp; + } else { + ndecExp *= -1; + ndec = 1.0/(float)((uint16_t)1 << ndecExp); + } + float rxOsr = ((float)(500 * (1 + 2*bypass))) / (ndec * this->bitRate * ((float)(1 + manch))); + uint32_t ncoOff = (this->bitRate * (1 + manch) * ((uint32_t)(1) << (20 + decRate))) / (500 * (1 + 2*bypass)); + uint16_t crGain = 2 + (((float)(65536.0 * (1 + manch)) * this->bitRate) / (rxOsr * (this->frequencyDev / 0.625))); + uint16_t rxOsr_fixed = (uint16_t)rxOsr; + + // print that whole mess + RADIOLIB_DEBUG_BASIC_PRINTLN("%X\n%X\n%X", bypass, decRate, manch); + RADIOLIB_DEBUG_BASIC_PRINT_FLOAT(rxOsr, 2); + RADIOLIB_DEBUG_BASIC_PRINTLN("\t%d\t%X\n%lu\t%lX\n%d\t%X", rxOsr_fixed, rxOsr_fixed, (long unsigned int)ncoOff, (long unsigned int)ncoOff, crGain, crGain); + + // update oversampling ratio + int16_t state = this->mod->SPIsetRegValue(RADIOLIB_SI443X_REG_CLOCK_REC_OFFSET_2, (uint8_t)((rxOsr_fixed & 0x0700) >> 3), 7, 5); + RADIOLIB_ASSERT(state); + state = this->mod->SPIsetRegValue(RADIOLIB_SI443X_REG_CLOCK_REC_OVERSAMP_RATIO, (uint8_t)(rxOsr_fixed & 0x00FF)); + RADIOLIB_ASSERT(state); + + // update NCO offset + state = this->mod->SPIsetRegValue(RADIOLIB_SI443X_REG_CLOCK_REC_OFFSET_2, (uint8_t)((ncoOff & 0x0F0000) >> 16), 3, 0); + RADIOLIB_ASSERT(state); + state = this->mod->SPIsetRegValue(RADIOLIB_SI443X_REG_CLOCK_REC_OFFSET_1, (uint8_t)((ncoOff & 0x00FF00) >> 8)); + RADIOLIB_ASSERT(state); + state = this->mod->SPIsetRegValue(RADIOLIB_SI443X_REG_CLOCK_REC_OFFSET_0, (uint8_t)(ncoOff & 0x0000FF)); + RADIOLIB_ASSERT(state); + + // update clock recovery loop gain + state = this->mod->SPIsetRegValue(RADIOLIB_SI443X_REG_CLOCK_REC_TIMING_LOOP_GAIN_1, (uint8_t)((crGain & 0x0700) >> 8), 2, 0); + RADIOLIB_ASSERT(state); + state = this->mod->SPIsetRegValue(RADIOLIB_SI443X_REG_CLOCK_REC_TIMING_LOOP_GAIN_0, (uint8_t)(crGain & 0x00FF)); + RADIOLIB_ASSERT(state); + + return(state); +} + +int16_t Si443x::directMode() { + int16_t state = this->mod->SPIsetRegValue(RADIOLIB_SI443X_REG_MODULATION_MODE_CONTROL_2, RADIOLIB_SI443X_TX_DATA_SOURCE_GPIO, 5, 4); + RADIOLIB_ASSERT(state); + + state = this->mod->SPIsetRegValue(RADIOLIB_SI443X_REG_GPIO1_CONFIG, RADIOLIB_SI443X_GPIOX_TX_RX_DATA_CLK_OUT, 4, 0); + RADIOLIB_ASSERT(state); + + state = this->mod->SPIsetRegValue(RADIOLIB_SI443X_REG_GPIO2_CONFIG, RADIOLIB_SI443X_GPIOX_TX_DATA_IN, 4, 0); + RADIOLIB_ASSERT(state); + + state = this->mod->SPIsetRegValue(RADIOLIB_SI443X_REG_MODULATION_MODE_CONTROL_2, RADIOLIB_SI443X_MODULATION_FSK, 1, 0); + return(state); +} + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/Si443x/Si443x.h b/software/firmware/source/libraries/RadioLib/src/modules/Si443x/Si443x.h new file mode 100644 index 000000000..c100b04e2 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/Si443x/Si443x.h @@ -0,0 +1,860 @@ +#if !defined(_RADIOLIB_SI443X_H) +#define _RADIOLIB_SI443X_H + +#include "../../TypeDef.h" + +#if !RADIOLIB_EXCLUDE_SI443X + +#include "../../Module.h" + +#include "../../protocols/PhysicalLayer/PhysicalLayer.h" + +// Si443x physical layer properties +#define RADIOLIB_SI443X_FREQUENCY_STEP_SIZE 156.25 +#define RADIOLIB_SI443X_MAX_PACKET_LENGTH 64 + +// Si443x series common registers +#define RADIOLIB_SI443X_REG_DEVICE_TYPE 0x00 +#define RADIOLIB_SI443X_REG_DEVICE_VERSION 0x01 +#define RADIOLIB_SI443X_REG_DEVICE_STATUS 0x02 +#define RADIOLIB_SI443X_REG_INTERRUPT_STATUS_1 0x03 +#define RADIOLIB_SI443X_REG_INTERRUPT_STATUS_2 0x04 +#define RADIOLIB_SI443X_REG_INTERRUPT_ENABLE_1 0x05 +#define RADIOLIB_SI443X_REG_INTERRUPT_ENABLE_2 0x06 +#define RADIOLIB_SI443X_REG_OP_FUNC_CONTROL_1 0x07 +#define RADIOLIB_SI443X_REG_OP_FUNC_CONTROL_2 0x08 +#define RADIOLIB_SI443X_REG_XOSC_LOAD_CAPACITANCE 0x09 +#define RADIOLIB_SI443X_REG_MCU_OUTPUT_CLOCK 0x0A +#define RADIOLIB_SI443X_REG_GPIO0_CONFIG 0x0B +#define RADIOLIB_SI443X_REG_GPIO1_CONFIG 0x0C +#define RADIOLIB_SI443X_REG_GPIO2_CONFIG 0x0D +#define RADIOLIB_SI443X_REG_IO_PORT_CONFIG 0x0E +#define RADIOLIB_SI443X_REG_ADC_CONFIG 0x0F +#define RADIOLIB_SI443X_REG_ADC_SENSOR_AMP_OFFSET 0x10 +#define RADIOLIB_SI443X_REG_ADC_VALUE 0x11 +#define RADIOLIB_SI443X_REG_TEMP_SENSOR_CONTROL 0x12 +#define RADIOLIB_SI443X_REG_TEMP_VALUE_OFFSET 0x13 +#define RADIOLIB_SI443X_REG_WAKEUP_TIMER_PERIOD_1 0x14 +#define RADIOLIB_SI443X_REG_WAKEUP_TIMER_PERIOD_2 0x15 +#define RADIOLIB_SI443X_REG_WAKEUP_TIMER_PERIOD_3 0x16 +#define RADIOLIB_SI443X_REG_WAKEUP_TIMER_VALUE_1 0x17 +#define RADIOLIB_SI443X_REG_WAKEUP_TIMER_VALUE_2 0x18 +#define RADIOLIB_SI443X_REG_LOW_DC_MODE_DURATION 0x19 +#define RADIOLIB_SI443X_REG_LOW_BATT_DET_THRESHOLD 0x1A +#define RADIOLIB_SI443X_REG_BATT_VOLTAGE_LEVEL 0x1B +#define RADIOLIB_SI443X_REG_IF_FILTER_BANDWIDTH 0x1C +#define RADIOLIB_SI443X_REG_AFC_LOOP_GEARSHIFT_OVERRIDE 0x1D +#define RADIOLIB_SI443X_REG_AFC_TIMING_CONTROL 0x1E +#define RADIOLIB_SI443X_REG_CLOCK_REC_GEARSHIFT_OVERRIDE 0x1F +#define RADIOLIB_SI443X_REG_CLOCK_REC_OVERSAMP_RATIO 0x20 +#define RADIOLIB_SI443X_REG_CLOCK_REC_OFFSET_2 0x21 +#define RADIOLIB_SI443X_REG_CLOCK_REC_OFFSET_1 0x22 +#define RADIOLIB_SI443X_REG_CLOCK_REC_OFFSET_0 0x23 +#define RADIOLIB_SI443X_REG_CLOCK_REC_TIMING_LOOP_GAIN_1 0x24 +#define RADIOLIB_SI443X_REG_CLOCK_REC_TIMING_LOOP_GAIN_0 0x25 +#define RADIOLIB_SI443X_REG_RSSI 0x26 +#define RADIOLIB_SI443X_REG_RSSI_CLEAR_CHANNEL_THRESHOLD 0x27 +#define RADIOLIB_SI443X_REG_ANTENNA_DIVERSITY_1 0x28 +#define RADIOLIB_SI443X_REG_ANTENNA_DIVERSITY_2 0x29 +#define RADIOLIB_SI443X_REG_AFC_LIMITER 0x2A +#define RADIOLIB_SI443X_REG_AFC_CORRECTION 0x2B +#define RADIOLIB_SI443X_REG_OOK_COUNTER_1 0x2C +#define RADIOLIB_SI443X_REG_OOK_COUNTER_2 0x2D +#define RADIOLIB_SI443X_REG_SLICER_PEAK_HOLD 0x2E +#define RADIOLIB_SI443X_REG_DATA_ACCESS_CONTROL 0x30 +#define RADIOLIB_SI443X_REG_EZMAC_STATUS 0x31 +#define RADIOLIB_SI443X_REG_HEADER_CONTROL_1 0x32 +#define RADIOLIB_SI443X_REG_HEADER_CONTROL_2 0x33 +#define RADIOLIB_SI443X_REG_PREAMBLE_LENGTH 0x34 +#define RADIOLIB_SI443X_REG_PREAMBLE_DET_CONTROL 0x35 +#define RADIOLIB_SI443X_REG_SYNC_WORD_3 0x36 +#define RADIOLIB_SI443X_REG_SYNC_WORD_2 0x37 +#define RADIOLIB_SI443X_REG_SYNC_WORD_1 0x38 +#define RADIOLIB_SI443X_REG_SYNC_WORD_0 0x39 +#define RADIOLIB_SI443X_REG_TRANSMIT_HEADER_3 0x3A +#define RADIOLIB_SI443X_REG_TRANSMIT_HEADER_2 0x3B +#define RADIOLIB_SI443X_REG_TRANSMIT_HEADER_1 0x3C +#define RADIOLIB_SI443X_REG_TRANSMIT_HEADER_0 0x3D +#define RADIOLIB_SI443X_REG_TRANSMIT_PACKET_LENGTH 0x3E +#define RADIOLIB_SI443X_REG_CHECK_HEADER_3 0x3F +#define RADIOLIB_SI443X_REG_CHECK_HEADER_2 0x40 +#define RADIOLIB_SI443X_REG_CHECK_HEADER_1 0x41 +#define RADIOLIB_SI443X_REG_CHECK_HEADER_0 0x42 +#define RADIOLIB_SI443X_REG_HEADER_ENABLE_3 0x43 +#define RADIOLIB_SI443X_REG_HEADER_ENABLE_2 0x44 +#define RADIOLIB_SI443X_REG_HEADER_ENABLE_1 0x45 +#define RADIOLIB_SI443X_REG_HEADER_ENABLE_0 0x46 +#define RADIOLIB_SI443X_REG_RECEIVED_HEADER_3 0x47 +#define RADIOLIB_SI443X_REG_RECEIVED_HEADER_2 0x48 +#define RADIOLIB_SI443X_REG_RECEIVED_HEADER_1 0x49 +#define RADIOLIB_SI443X_REG_RECEIVED_HEADER_0 0x4A +#define RADIOLIB_SI443X_REG_RECEIVED_PACKET_LENGTH 0x4B +#define RADIOLIB_SI443X_REG_ADC8_CONTROL 0x4F +#define RADIOLIB_SI443X_REG_CHANNEL_FILTER_COEFF 0x60 +#define RADIOLIB_SI443X_REG_XOSC_CONTROL_TEST 0x62 +#define RADIOLIB_SI443X_REG_AGC_OVERRIDE_1 0x69 +#define RADIOLIB_SI443X_REG_TX_POWER 0x6D +#define RADIOLIB_SI443X_REG_TX_DATA_RATE_1 0x6E +#define RADIOLIB_SI443X_REG_TX_DATA_RATE_0 0x6F +#define RADIOLIB_SI443X_REG_MODULATION_MODE_CONTROL_1 0x70 +#define RADIOLIB_SI443X_REG_MODULATION_MODE_CONTROL_2 0x71 +#define RADIOLIB_SI443X_REG_FREQUENCY_DEVIATION 0x72 +#define RADIOLIB_SI443X_REG_FREQUENCY_OFFSET_1 0x73 +#define RADIOLIB_SI443X_REG_FREQUENCY_OFFSET_2 0x74 +#define RADIOLIB_SI443X_REG_FREQUENCY_BAND_SELECT 0x75 +#define RADIOLIB_SI443X_REG_NOM_CARRIER_FREQUENCY_1 0x76 +#define RADIOLIB_SI443X_REG_NOM_CARRIER_FREQUENCY_0 0x77 +#define RADIOLIB_SI443X_REG_FREQUENCY_HOPPING_CHANNEL_SEL 0x79 +#define RADIOLIB_SI443X_REG_FREQUENCY_HOPPING_STEP_SIZE 0x7A +#define RADIOLIB_SI443X_REG_TX_FIFO_CONTROL_1 0x7C +#define RADIOLIB_SI443X_REG_TX_FIFO_CONTROL_2 0x7D +#define RADIOLIB_SI443X_REG_RX_FIFO_CONTROL 0x7E +#define RADIOLIB_SI443X_REG_FIFO_ACCESS 0x7F + +// RADIOLIB_SI443X_REG_DEVICE_TYPE MSB LSB DESCRIPTION +#define RADIOLIB_SI443X_DEVICE_TYPE 0x08 // 4 0 device identification register + +// RADIOLIB_SI443X_REG_DEVICE_VERSION +#define RADIOLIB_SI443X_DEVICE_VERSION 0x06 // 4 0 chip version register + +// RADIOLIB_SI443X_REG_DEVICE_STATUS +#define RADIOLIB_SI443X_RX_TX_FIFO_OVERFLOW 0b10000000 // 7 7 Rx/Tx FIFO overflow flag +#define RADIOLIB_SI443X_RX_TX_FIFO_UNDERFLOW 0b01000000 // 6 6 Rx/Tx FIFO underflow flag +#define RADIOLIB_SI443X_RX_FIFO_EMPTY 0b00100000 // 5 5 Rx FIFO empty flag +#define RADIOLIB_SI443X_HEADER_ERROR 0b00010000 // 4 4 header error flag +#define RADIOLIB_SI443X_FREQUENCY_ERROR 0b00001000 // 3 3 frequency error flag (frequency outside allowed range) +#define RADIOLIB_SI443X_TX 0b00000010 // 1 0 power state: Tx +#define RADIOLIB_SI443X_RX 0b00000001 // 1 0 Rx +#define RADIOLIB_SI443X_IDLE 0b00000000 // 1 0 idle + +// RADIOLIB_SI443X_REG_INTERRUPT_STATUS_1 +#define RADIOLIB_SI443X_FIFO_LEVEL_ERROR_INTERRUPT 0b10000000 // 7 7 Tx/Rx FIFO overflow or underflow +#define RADIOLIB_SI443X_TX_FIFO_ALMOST_FULL_INTERRUPT 0b01000000 // 6 6 Tx FIFO almost full +#define RADIOLIB_SI443X_TX_FIFO_ALMOST_EMPTY_INTERRUPT 0b00100000 // 5 5 Tx FIFO almost empty +#define RADIOLIB_SI443X_RX_FIFO_ALMOST_FULL_INTERRUPT 0b00010000 // 4 4 Rx FIFO almost full +#define RADIOLIB_SI443X_EXTERNAL_INTERRUPT 0b00001000 // 3 3 external interrupt occurred on GPIOx +#define RADIOLIB_SI443X_PACKET_SENT_INTERRUPT 0b00000100 // 2 2 packet transmission done +#define RADIOLIB_SI443X_VALID_PACKET_RECEIVED_INTERRUPT 0b00000010 // 1 1 valid packet has been received +#define RADIOLIB_SI443X_CRC_ERROR_INTERRUPT 0b00000001 // 0 0 CRC failed + +// RADIOLIB_SI443X_REG_INTERRUPT_STATUS_2 +#define RADIOLIB_SI443X_SYNC_WORD_DETECTED_INTERRUPT 0b10000000 // 7 7 sync word has been detected +#define RADIOLIB_SI443X_VALID_RADIOLIB_PREAMBLE_DETECTED_INTERRUPT 0b01000000 // 6 6 valid preamble has been detected +#define RADIOLIB_SI443X_INVALID_RADIOLIB_PREAMBLE_DETECTED_INTERRUPT 0b00100000 // 5 5 invalid preamble has been detected +#define RADIOLIB_SI443X_RSSI_INTERRUPT 0b00010000 // 4 4 RSSI exceeded programmed threshold +#define RADIOLIB_SI443X_WAKEUP_TIMER_INTERRUPT 0b00001000 // 3 3 wake-up timer expired +#define RADIOLIB_SI443X_LOW_BATTERY_INTERRUPT 0b00000100 // 2 2 low battery detected +#define RADIOLIB_SI443X_CHIP_READY_INTERRUPT 0b00000010 // 1 1 chip ready event detected +#define RADIOLIB_SI443X_POWER_ON_RESET_INTERRUPT 0b00000001 // 0 0 power-on-reset detected + +// RADIOLIB_SI443X_REG_INTERRUPT_ENABLE_1 +#define RADIOLIB_SI443X_FIFO_LEVEL_ERROR_ENABLED 0b10000000 // 7 7 Tx/Rx FIFO overflow or underflow interrupt enabled +#define RADIOLIB_SI443X_TX_FIFO_ALMOST_FULL_ENABLED 0b01000000 // 6 6 Tx FIFO almost full interrupt enabled +#define RADIOLIB_SI443X_TX_FIFO_ALMOST_EMPTY_ENABLED 0b00100000 // 5 5 Tx FIFO almost empty interrupt enabled +#define RADIOLIB_SI443X_RX_FIFO_ALMOST_FULL_ENABLED 0b00010000 // 4 4 Rx FIFO almost full interrupt enabled +#define RADIOLIB_SI443X_EXTERNAL_ENABLED 0b00001000 // 3 3 external interrupt interrupt enabled +#define RADIOLIB_SI443X_PACKET_SENT_ENABLED 0b00000100 // 2 2 packet transmission done interrupt enabled +#define RADIOLIB_SI443X_VALID_PACKET_RECEIVED_ENABLED 0b00000010 // 1 1 valid packet received interrupt enabled +#define RADIOLIB_SI443X_CRC_ERROR_ENABLED 0b00000001 // 0 0 CRC failed interrupt enabled + +// RADIOLIB_SI443X_REG_INTERRUPT_ENABLE_2 +#define RADIOLIB_SI443X_SYNC_WORD_DETECTED_ENABLED 0b10000000 // 7 7 sync word interrupt enabled +#define RADIOLIB_SI443X_VALID_RADIOLIB_PREAMBLE_DETECTED_ENABLED 0b01000000 // 6 6 valid preamble interrupt enabled +#define RADIOLIB_SI443X_INVALID_RADIOLIB_PREAMBLE_DETECTED_ENABLED 0b00100000 // 5 5 invalid preamble interrupt enabled +#define RADIOLIB_SI443X_RSSI_ENABLED 0b00010000 // 4 4 RSSI exceeded programmed threshold interrupt enabled +#define RADIOLIB_SI443X_WAKEUP_TIMER_ENABLED 0b00001000 // 3 3 wake-up timer interrupt enabled +#define RADIOLIB_SI443X_LOW_BATTERY_ENABLED 0b00000100 // 2 2 low battery interrupt enabled +#define RADIOLIB_SI443X_CHIP_READY_ENABLED 0b00000010 // 1 1 chip ready event interrupt enabled +#define RADIOLIB_SI443X_POWER_ON_RESET_ENABLED 0b00000001 // 0 0 power-on-reset interrupt enabled + +// RADIOLIB_SI443X_REG_OP_FUNC_CONTROL_1 +#define RADIOLIB_SI443X_SOFTWARE_RESET 0b10000000 // 7 7 reset all registers to default values +#define RADIOLIB_SI443X_ENABLE_LOW_BATTERY_DETECT 0b01000000 // 6 6 enable low battery detection +#define RADIOLIB_SI443X_ENABLE_WAKEUP_TIMER 0b00100000 // 5 5 enable wakeup timer +#define RADIOLIB_SI443X_32_KHZ_RC 0b00000000 // 4 4 32.768 kHz source: RC oscillator (default) +#define RADIOLIB_SI443X_32_KHZ_XOSC 0b00010000 // 4 4 crystal oscillator +#define RADIOLIB_SI443X_TX_ON 0b00001000 // 3 3 Tx on in manual transmit mode +#define RADIOLIB_SI443X_RX_ON 0b00000100 // 2 2 Rx on in manual receive mode +#define RADIOLIB_SI443X_PLL_ON 0b00000010 // 1 1 PLL on (tune mode) +#define RADIOLIB_SI443X_XTAL_OFF 0b00000000 // 0 0 crystal oscillator: off (standby mode) +#define RADIOLIB_SI443X_XTAL_ON 0b00000001 // 0 0 on (ready mode) + +// RADIOLIB_SI443X_REG_OP_FUNC_CONTROL_2 +#define RADIOLIB_SI443X_ANT_DIV_TR_HL_IDLE_L 0b00000000 // 7 5 GPIO1/2 states: Tx/Rx GPIO1 H, GPIO2 L; idle low (default) +#define RADIOLIB_SI443X_ANT_DIV_TR_LH_IDLE_L 0b00100000 // 7 5 Tx/Rx GPIO1 L, GPIO2 H; idle low +#define RADIOLIB_SI443X_ANT_DIV_TR_HL_IDLE_H 0b01000000 // 7 5 Tx/Rx GPIO1 H, GPIO2 L; idle high +#define RADIOLIB_SI443X_ANT_DIV_TR_LH_IDLE_H 0b01100000 // 7 5 Tx/Rx GPIO1 L, GPIO2 H; idle high +#define RADIOLIB_SI443X_ANT_DIV_TR_ALG_IDLE_L 0b10000000 // 7 5 Tx/Rx diversity algorithm; idle low +#define RADIOLIB_SI443X_ANT_DIV_TR_ALG_IDLE_H 0b10100000 // 7 5 Tx/Rx diversity algorithm; idle high +#define RADIOLIB_SI443X_ANT_DIV_TR_ALG_BEACON_IDLE_L 0b11000000 // 7 5 Tx/Rx diversity algorithm (beacon); idle low +#define RADIOLIB_SI443X_ANT_DIV_TR_ALG_BEACON_IDLE_H 0b11100000 // 7 5 Tx/Rx diversity algorithm (beacon); idle high +#define RADIOLIB_SI443X_RX_MULTIPACKET_OFF 0b00000000 // 4 4 Rx multipacket: disabled (default) +#define RADIOLIB_SI443X_RX_MULTIPACKET_ON 0b00010000 // 4 4 enabled +#define RADIOLIB_SI443X_AUTO_TX_OFF 0b00000000 // 3 3 Tx autotransmit on FIFO almost full: disabled (default) +#define RADIOLIB_SI443X_AUTO_TX_ON 0b00001000 // 3 3 enabled +#define RADIOLIB_SI443X_LOW_DUTY_CYCLE_OFF 0b00000000 // 2 2 low duty cycle mode: disabled (default) +#define RADIOLIB_SI443X_LOW_DUTY_CYCLE_ON 0b00000100 // 2 2 enabled +#define RADIOLIB_SI443X_RX_FIFO_RESET 0b00000010 // 1 1 Rx FIFO reset/clear: reset (call first) +#define RADIOLIB_SI443X_RX_FIFO_CLEAR 0b00000000 // 1 1 clear (call second) +#define RADIOLIB_SI443X_TX_FIFO_RESET 0b00000001 // 0 0 Tx FIFO reset/clear: reset (call first) +#define RADIOLIB_SI443X_TX_FIFO_CLEAR 0b00000000 // 0 0 clear (call second) + +// RADIOLIB_SI443X_REG_XOSC_LOAD_CAPACITANCE +#define RADIOLIB_SI443X_XTAL_SHIFT 0b00000000 // 7 7 crystal capacitance configuration: +#define RADIOLIB_SI443X_XTAL_LOAD_CAPACITANCE 0b01111111 // 6 0 C_int = 1.8 pF + 0.085 pF * RADIOLIB_SI443X_XTAL_LOAD_CAPACITANCE + 3.7 pF * RADIOLIB_SI443X_XTAL_SHIFT + +// RADIOLIB_SI443X_REG_MCU_OUTPUT_CLOCK +#define RADIOLIB_SI443X_CLOCK_TAIL_CYCLES_OFF 0b00000000 // 5 4 additional clock cycles: none (default) +#define RADIOLIB_SI443X_CLOCK_TAIL_CYCLES_128 0b00010000 // 5 4 128 +#define RADIOLIB_SI443X_CLOCK_TAIL_CYCLES_256 0b00100000 // 5 4 256 +#define RADIOLIB_SI443X_CLOCK_TAIL_CYCLES_512 0b00110000 // 5 4 512 +#define RADIOLIB_SI443X_LOW_FREQ_CLOCK_OFF 0b00000000 // 3 3 32.768 kHz clock output: disabled (default) +#define RADIOLIB_SI443X_LOW_FREQ_CLOCK_ON 0b00001000 // 3 3 enabled +#define RADIOLIB_SI443X_MCU_CLOCK_30_MHZ 0b00000000 // 2 0 GPIO clock output: 30 MHz +#define RADIOLIB_SI443X_MCU_CLOCK_15_MHZ 0b00000001 // 2 0 15 MHz +#define RADIOLIB_SI443X_MCU_CLOCK_10_MHZ 0b00000010 // 2 0 10 MHz +#define RADIOLIB_SI443X_MCU_CLOCK_4_MHZ 0b00000011 // 2 0 4 MHz +#define RADIOLIB_SI443X_MCU_CLOCK_3_MHZ 0b00000100 // 2 0 3 MHz +#define RADIOLIB_SI443X_MCU_CLOCK_2_MHZ 0b00000101 // 2 0 2 MHz +#define RADIOLIB_SI443X_MCU_CLOCK_1_MHZ 0b00000110 // 2 0 1 MHz (default) +#define RADIOLIB_SI443X_MCU_CLOCK_32_KHZ 0b00000111 // 2 0 32.768 kHz + +// RADIOLIB_SI443X_REG_GPIO0_CONFIG + RADIOLIB_SI443X_REG_GPIO1_CONFIG + RADIOLIB_SI443X_REG_GPIO2_CONFIG +#define RADIOLIB_SI443X_GPIOX_DRIVE_STRENGTH 0b00000000 // 7 6 GPIOx drive strength (higher number = stronger drive) +#define RADIOLIB_SI443X_GPIOX_PULLUP_OFF 0b00000000 // 5 5 GPIOx internal 200k pullup: disabled (default) +#define RADIOLIB_SI443X_GPIOX_PULLUP_ON 0b00100000 // 5 5 enabled +#define RADIOLIB_SI443X_GPIO0_POWER_ON_RESET_OUT 0b00000000 // 4 0 GPIOx function: power-on-reset output (GPIO0 only, default) +#define RADIOLIB_SI443X_GPIO1_POWER_ON_RESET_INV_OUT 0b00000000 // 4 0 inverted power-on-reset output (GPIO1 only, default) +#define RADIOLIB_SI443X_GPIO2_MCU_CLOCK_OUT 0b00000000 // 4 0 MCU clock output (GPIO2 only, default) +#define RADIOLIB_SI443X_GPIOX_WAKEUP_OUT 0b00000001 // 4 0 wakeup timer expired output +#define RADIOLIB_SI443X_GPIOX_LOW_BATTERY_OUT 0b00000010 // 4 0 low battery detect output +#define RADIOLIB_SI443X_GPIOX_DIGITAL_OUT 0b00000011 // 4 0 direct digital output +#define RADIOLIB_SI443X_GPIOX_EXT_INT_FALLING_IN 0b00000100 // 4 0 external interrupt, falling edge +#define RADIOLIB_SI443X_GPIOX_EXT_INT_RISING_IN 0b00000101 // 4 0 external interrupt, rising edge +#define RADIOLIB_SI443X_GPIOX_EXT_INT_CHANGE_IN 0b00000110 // 4 0 external interrupt, state change +#define RADIOLIB_SI443X_GPIOX_ADC_IN 0b00000111 // 4 0 ADC analog input +#define RADIOLIB_SI443X_GPIOX_ANALOG_TEST_N_IN 0b00001000 // 4 0 analog test N input +#define RADIOLIB_SI443X_GPIOX_ANALOG_TEST_P_IN 0b00001001 // 4 0 analog test P input +#define RADIOLIB_SI443X_GPIOX_DIGITAL_IN 0b00001010 // 4 0 direct digital input +#define RADIOLIB_SI443X_GPIOX_DIGITAL_TEST_OUT 0b00001011 // 4 0 digital test output +#define RADIOLIB_SI443X_GPIOX_ANALOG_TEST_N_OUT 0b00001100 // 4 0 analog test N output +#define RADIOLIB_SI443X_GPIOX_ANALOG_TEST_P_OUT 0b00001101 // 4 0 analog test P output +#define RADIOLIB_SI443X_GPIOX_REFERENCE_VOLTAGE_OUT 0b00001110 // 4 0 reference voltage output +#define RADIOLIB_SI443X_GPIOX_TX_RX_DATA_CLK_OUT 0b00001111 // 4 0 Tx/Rx clock output in direct mode +#define RADIOLIB_SI443X_GPIOX_TX_DATA_IN 0b00010000 // 4 0 Tx data input direct mode +#define RADIOLIB_SI443X_GPIOX_EXT_RETRANSMIT_REQUEST_IN 0b00010001 // 4 0 external retransmission request input +#define RADIOLIB_SI443X_GPIOX_TX_STATE_OUT 0b00010010 // 4 0 Tx state output +#define RADIOLIB_SI443X_GPIOX_TX_FIFO_ALMOST_FULL_OUT 0b00010011 // 4 0 Tx FIFO almost full output +#define RADIOLIB_SI443X_GPIOX_RX_DATA_OUT 0b00010100 // 4 0 Rx data output +#define RADIOLIB_SI443X_GPIOX_RX_STATE_OUT 0b00010101 // 4 0 Rx state output +#define RADIOLIB_SI443X_GPIOX_RX_FIFO_ALMOST_FULL_OUT 0b00010110 // 4 0 Rx FIFO almost full output +#define RADIOLIB_SI443X_GPIOX_ANT_DIV_1_OUT 0b00010111 // 4 0 antenna diversity output 1 +#define RADIOLIB_SI443X_GPIOX_ANT_DIV_2_OUT 0b00011000 // 4 0 antenna diversity output 2 +#define RADIOLIB_SI443X_GPIOX_VALID_PREAMBLE_OUT 0b00011001 // 4 0 valid preamble detected output +#define RADIOLIB_SI443X_GPIOX_INVALID_PREAMBLE_OUT 0b00011010 // 4 0 invalid preamble detected output +#define RADIOLIB_SI443X_GPIOX_SYNC_WORD_DETECTED_OUT 0b00011011 // 4 0 sync word detected output +#define RADIOLIB_SI443X_GPIOX_CLEAR_CHANNEL_OUT 0b00011100 // 4 0 clear channel assessment output +#define RADIOLIB_SI443X_GPIOX_VDD 0b00011101 // 4 0 VDD +#define RADIOLIB_SI443X_GPIOX_GND 0b00011110 // 4 0 GND + +// RADIOLIB_SI443X_REG_IO_PORT_CONFIG +#define RADIOLIB_SI443X_GPIO2_EXT_INT_STATE_MASK 0b01000000 // 6 6 external interrupt state mask for: GPIO2 +#define RADIOLIB_SI443X_GPIO1_EXT_INT_STATE_MASK 0b00100000 // 5 5 GPIO1 +#define RADIOLIB_SI443X_GPIO0_EXT_INT_STATE_MASK 0b00010000 // 4 4 GPIO0 +#define RADIOLIB_SI443X_IRQ_BY_SDO_OFF 0b00000000 // 3 3 output IRQ state on SDO pin: disabled (default) +#define RADIOLIB_SI443X_IRQ_BY_SDO_ON 0b00001000 // 3 3 enabled +#define RADIOLIB_SI443X_GPIO2_DIGITAL_STATE_MASK 0b00000100 // 2 2 digital state mask for: GPIO2 +#define RADIOLIB_SI443X_GPIO1_DIGITAL_STATE_MASK 0b00000010 // 1 1 GPIO1 +#define RADIOLIB_SI443X_GPIO0_DIGITAL_STATE_MASK 0b00000001 // 0 0 GPIO0 + +// RADIOLIB_SI443X_REG_ADC_CONFIG +#define RADIOLIB_SI443X_ADC_START 0b10000000 // 7 7 ADC control: start measurement +#define RADIOLIB_SI443X_ADC_RUNNING 0b00000000 // 7 7 measurement in progress +#define RADIOLIB_SI443X_ADC_DONE 0b10000000 // 7 7 done +#define RADIOLIB_SI443X_ADC_SOURCE_TEMPERATURE 0b00000000 // 6 4 ADC source: internal temperature sensor (default) +#define RADIOLIB_SI443X_ADC_SOURCE_GPIO0_SINGLE 0b00010000 // 6 4 single-ended on GPIO0 +#define RADIOLIB_SI443X_ADC_SOURCE_GPIO1_SINGLE 0b00100000 // 6 4 single-ended on GPIO1 +#define RADIOLIB_SI443X_ADC_SOURCE_GPIO2_SINGLE 0b00110000 // 6 4 single-ended on GPIO2 +#define RADIOLIB_SI443X_ADC_SOURCE_GPIO01_DIFF 0b01000000 // 6 4 differential on GPIO0 (+) and GPIO1 (-) +#define RADIOLIB_SI443X_ADC_SOURCE_GPIO12_DIFF 0b01010000 // 6 4 differential on GPIO1 (+) and GPIO2 (-) +#define RADIOLIB_SI443X_ADC_SOURCE_GPIO02_DIFF 0b01100000 // 6 4 differential on GPIO0 (+) and GPIO2 (-) +#define RADIOLIB_SI443X_ADC_SOURCE_GND 0b01110000 // 6 4 GND +#define RADIOLIB_SI443X_ADC_REFERNCE_BAND_GAP 0b00000000 // 3 2 ADC reference: internal bandgap 1.2 V (default) +#define RADIOLIB_SI443X_ADC_REFERNCE_VDD_3 0b00001000 // 3 2 VDD/3 +#define RADIOLIB_SI443X_ADC_REFERNCE_VDD_2 0b00001100 // 3 2 VDD/2 +#define RADIOLIB_SI443X_ADC_GAIN 0b00000000 // 1 0 ADC amplifier gain + +// RADIOLIB_SI443X_REG_ADC_SENSOR_AMP_OFFSET +#define RADIOLIB_SI443X_ADC_OFFSET 0b00000000 // 3 0 ADC offset + +// RADIOLIB_SI443X_REG_TEMP_SENSOR_CONTROL +#define RADIOLIB_SI443X_TEMP_SENSOR_RANGE_64_TO_64_C 0b00000000 // 7 6 temperature sensor range: -64 to 64 deg. C, 0.5 deg. C resolution (default) +#define RADIOLIB_SI443X_TEMP_SENSOR_RANGE_64_TO_192_C 0b01000000 // 7 6 -64 to 192 deg. C, 1.0 deg. C resolution +#define RADIOLIB_SI443X_TEMP_SENSOR_RANGE_0_TO_128_C 0b11000000 // 7 6 0 to 128 deg. C, 0.5 deg. C resolution +#define RADIOLIB_SI443X_TEMP_SENSOR_RANGE_40_TO_216_F 0b10000000 // 7 6 -40 to 216 deg. F, 1.0 deg. F resolution +#define RADIOLIB_SI443X_TEMP_SENSOR_KELVIN_TO_CELSIUS_OFF 0b00000000 // 5 5 Kelvin to Celsius offset: disabled +#define RADIOLIB_SI443X_TEMP_SENSOR_KELVIN_TO_CELSIUS_ON 0b00100000 // 5 5 enabled (default) +#define RADIOLIB_SI443X_TEMP_SENSOR_TRIM_OFF 0b00000000 // 4 4 temperature sensor trim: disabled (default) +#define RADIOLIB_SI443X_TEMP_SENSOR_TRIM_ON 0b00010000 // 4 4 enabled +#define RADIOLIB_SI443X_TEMP_SENSOR_TRIM_VALUE 0b00000000 // 3 0 temperature sensor trim value + +// RADIOLIB_SI443X_REG_WAKEUP_TIMER_PERIOD_1 +#define RADIOLIB_SI443X_WAKEUP_TIMER_EXPONENT 0b00000011 // 4 0 wakeup timer value exponent + +// RADIOLIB_SI443X_REG_WAKEUP_TIMER_PERIOD_2 + RADIOLIB_SI443X_REG_WAKEUP_TIMER_PERIOD_3 +#define RADIOLIB_SI443X_WAKEUP_TIMER_MANTISSA_MSB 0x00 // 7 0 wakeup timer value: +#define RADIOLIB_SI443X_WAKEUP_TIMER_MANTISSA_LSB 0x01 // 7 0 T = (4 * RADIOLIB_SI443X_WAKEUP_TIMER_MANTISSA * 2 ^ RADIOLIB_SI443X_WAKEUP_TIMER_EXPONENT) / 32.768 ms + +// RADIOLIB_SI443X_REG_LOW_DC_MODE_DURATION +#define RADIOLIB_SI443X_LOW_DC_MODE_DURATION_MANTISSA 0x01 // 7 0 low duty cycle mode duration: T = (4 * RADIOLIB_SI443X_LOW_DC_MODE_DURATION_MANTISSA * 2 ^ RADIOLIB_SI443X_WAKEUP_TIMER_EXPONENT) / 32.768 ms + +// RADIOLIB_SI443X_REG_LOW_BATT_DET_THRESHOLD +#define RADIOLIB_SI443X_LOW_BATT_DET_THRESHOLD 0b00010100 // 4 0 low battery detection threshold: Vth = 1.7 + RADIOLIB_SI443X_LOW_BATT_DET_THRESHOLD * 0.05 V (defaults to 2.7 V) + +// RADIOLIB_SI443X_REG_IF_FILTER_BANDWIDTH +#define RADIOLIB_SI443X_BYPASS_DEC_BY_3_OFF 0b00000000 // 7 7 bypass decimate-by-3 stage: disabled (default) +#define RADIOLIB_SI443X_BYPASS_DEC_BY_3_ON 0b10000000 // 7 7 enabled +#define RADIOLIB_SI443X_IF_FILTER_DEC_RATE 0b00000000 // 6 4 IF filter decimation rate +#define RADIOLIB_SI443X_IF_FILTER_COEFF_SET 0b00000001 // 3 0 IF filter coefficient set selection + +// RADIOLIB_SI443X_REG_AFC_LOOP_GEARSHIFT_OVERRIDE +#define RADIOLIB_SI443X_AFC_WIDEBAND_OFF 0b00000000 // 7 7 AFC wideband: disabled (default) +#define RADIOLIB_SI443X_AFC_WIDEBAND_ON 0b10000000 // 7 7 enabled +#define RADIOLIB_SI443X_AFC_OFF 0b00000000 // 6 6 AFC: disabled +#define RADIOLIB_SI443X_AFC_ON 0b01000000 // 6 6 enabled (default) +#define RADIOLIB_SI443X_AFC_HIGH_GEAR_SETTING 0b00000000 // 5 3 AFC high gear setting +#define RADIOLIB_SI443X_SECOND_PHASE_BIAS_0_DB 0b00000100 // 2 2 second phase antenna selection bias: 0 dB (default) +#define RADIOLIB_SI443X_SECOND_PHASE_BIAS_1_5_DB 0b00000000 // 2 2 1.5 dB +#define RADIOLIB_SI443X_MOVING_AVERAGE_TAP_8 0b00000010 // 1 1 moving average filter tap length: 8*Tb +#define RADIOLIB_SI443X_MOVING_AVERAGE_TAP_4 0b00000000 // 1 1 4*Tb after first preamble (default) +#define RADIOLIB_SI443X_ZERO_PHASE_RESET_5 0b00000000 // 0 0 reset preamble detector after: 5 zero phases (default) +#define RADIOLIB_SI443X_ZERO_PHASE_RESET_2 0b00000001 // 0 0 3 zero phases + +// RADIOLIB_SI443X_REG_AFC_TIMING_CONTROL +#define RADIOLIB_SI443X_SW_ANT_TIMER 0b00000000 // 7 6 number of periods to wait for RSSI to stabilize during antenna switching +#define RADIOLIB_SI443X_SHORT_WAIT 0b00001000 // 5 3 period to wait after AFC correction +#define RADIOLIB_SI443X_ANTENNA_SWITCH_WAIT 0b00000010 // 2 0 antenna switching wait time + +// RADIOLIB_SI443X_REG_CLOCK_REC_GEARSHIFT_OVERRIDE +#define RADIOLIB_SI443X_CLOCK_RECOVER_FAST_GEARSHIFT 0b00000000 // 5 3 clock recovery fast gearshift value +#define RADIOLIB_SI443X_CLOCK_RECOVER_SLOW_GEARSHIFT 0b00000011 // 2 0 clock recovery slow gearshift value + +// RADIOLIB_SI443X_REG_CLOCK_REC_OVERSAMP_RATIO +#define RADIOLIB_SI443X_CLOCK_REC_OVERSAMP_RATIO_LSB 0b01100100 // 7 0 oversampling rate LSB, defaults to 12.5 clock cycles per bit + +// RADIOLIB_SI443X_REG_CLOCK_REC_OFFSET_2 +#define RADIOLIB_SI443X_CLOCK_REC_OVERSAMP_RATIO_MSB 0b00000000 // 7 5 oversampling rate MSB, defaults to 12.5 clock cycles per bit +#define RADIOLIB_SI443X_SECOND_PHASE_SKIP_THRESHOLD 0b00000000 // 4 4 skip seconds phase antenna diversity threshold +#define RADIOLIB_SI443X_NCO_OFFSET_MSB 0b00000001 // 3 0 NCO offset MSB + +// RADIOLIB_SI443X_REG_CLOCK_REC_OFFSET_1 +#define RADIOLIB_SI443X_NCO_OFFSET_MID 0b01000111 // 7 0 NCO offset MID + +// RADIOLIB_SI443X_REG_CLOCK_REC_OFFSET_0 +#define RADIOLIB_SI443X_NCO_OFFSET_LSB 0b10101110 // 7 0 NCO offset LSB + +// RADIOLIB_SI443X_REG_CLOCK_REC_TIMING_LOOP_GAIN_1 +#define RADIOLIB_SI443X_RX_COMPENSATION_OFF 0b00000000 // 4 4 Rx compensation for high data rate: disabled (default) +#define RADIOLIB_SI443X_RX_COMPENSATION_ON 0b00010000 // 4 4 enabled +#define RADIOLIB_SI443X_CLOCK_REC_GAIN_DOUBLE_OFF 0b00000000 // 3 3 clock recovery gain doubling: disabled (default) +#define RADIOLIB_SI443X_CLOCK_REC_GAIN_DOUBLE_ON 0b00001000 // 3 3 enabled +#define RADIOLIB_SI443X_CLOCK_REC_LOOP_GAIN_MSB 0b00000010 // 2 0 clock recovery timing loop gain MSB + +// RADIOLIB_SI443X_REG_CLOCK_REC_TIMING_LOOP_GAIN_0 +#define RADIOLIB_SI443X_CLOCK_REC_LOOP_GAIN_LSB 0b10001111 // 7 0 clock recovery timing loop gain LSB + +// RADIOLIB_SI443X_REG_RSSI_CLEAR_CHANNEL_THRESHOLD +#define RADIOLIB_SI443X_RSSI_CLEAR_CHANNEL_THRESHOLD 0b00011110 // 7 0 RSSI clear channel interrupt threshold + +// RADIOLIB_SI443X_REG_AFC_LIMITER +#define RADIOLIB_SI443X_AFC_LIMITER 0x00 // 7 0 AFC limiter value + +// RADIOLIB_SI443X_REG_OOK_COUNTER_1 +#define RADIOLIB_SI443X_OOK_FREEZE_OFF 0b00000000 // 5 5 OOK moving average detector freeze: disabled (default) +#define RADIOLIB_SI443X_OOK_FREEZE_ON 0b00100000 // 5 5 enabled +#define RADIOLIB_SI443X_PEAK_DETECTOR_OFF 0b00000000 // 4 4 peak detector: disabled +#define RADIOLIB_SI443X_PEAK_DETECTOR_ON 0b00010000 // 4 4 enabled (default) +#define RADIOLIB_SI443X_OOK_MOVING_AVERAGE_OFF 0b00000000 // 3 3 OOK moving average: disabled +#define RADIOLIB_SI443X_OOK_MOVING_AVERAGE_ON 0b00001000 // 3 3 enabled (default) +#define RADIOLIB_SI443X_OOK_COUNTER_MSB 0b00000000 // 2 0 OOK counter MSB + +// RADIOLIB_SI443X_REG_OOK_COUNTER_2 +#define RADIOLIB_SI443X_OOK_COUNTER_LSB 0b10111100 // 7 0 OOK counter LSB + +// RADIOLIB_SI443X_REG_SLICER_PEAK_HOLD +#define RADIOLIB_SI443X_PEAK_DETECTOR_ATTACK 0b00010000 // 6 4 OOK peak detector attach time +#define RADIOLIB_SI443X_PEAK_DETECTOR_DECAY 0b00001100 // 3 0 OOK peak detector decay time + +// RADIOLIB_SI443X_REG_DATA_ACCESS_CONTROL +#define RADIOLIB_SI443X_PACKET_RX_HANDLING_OFF 0b00000000 // 7 7 packet Rx handling: disabled +#define RADIOLIB_SI443X_PACKET_RX_HANDLING_ON 0b10000000 // 7 7 enabled (default) +#define RADIOLIB_SI443X_LSB_FIRST_OFF 0b00000000 // 6 6 LSB first transmission: disabled (default) +#define RADIOLIB_SI443X_LSB_FIRST_ON 0b01000000 // 6 6 enabled +#define RADIOLIB_SI443X_CRC_DATA_ONLY_OFF 0b00000000 // 5 5 CRC calculated only from data fields: disabled (default) +#define RADIOLIB_SI443X_CRC_DATA_ONLY_ON 0b00100000 // 5 5 enabled +#define RADIOLIB_SI443X_SKIP_SECOND_PHASE_PREAMBLE_DET_OFF 0b00000000 // 4 4 skip second phase of preamble detection: disabled (default) +#define RADIOLIB_SI443X_SKIP_SECOND_PHASE_PREAMBLE_DET_ON 0b00010000 // 4 4 enabled +#define RADIOLIB_SI443X_PACKET_TX_HANDLING_OFF 0b00000000 // 3 3 packet Tx handling: disabled +#define RADIOLIB_SI443X_PACKET_TX_HANDLING_ON 0b00001000 // 3 3 enabled (default) +#define RADIOLIB_SI443X_CRC_OFF 0b00000000 // 2 2 CRC: disabled +#define RADIOLIB_SI443X_CRC_ON 0b00000100 // 2 2 enabled (default) +#define RADIOLIB_SI443X_CRC_CCITT 0b00000000 // 1 0 CRC type: CCITT +#define RADIOLIB_SI443X_CRC_IBM_CRC16 0b00000001 // 1 0 IBM CRC-16 (default) +#define RADIOLIB_SI443X_CRC_IEC16 0b00000010 // 1 0 IEC-16 +#define RADIOLIB_SI443X_CRC_BIACHEVA 0b00000011 // 1 0 Biacheva + +// RADIOLIB_SI443X_REG_EZMAC_STATUS +#define RADIOLIB_SI443X_CRC_ALL_ONE 0b01000000 // 6 6 last received CRC was all ones +#define RADIOLIB_SI443X_PACKET_SEARCHING 0b00100000 // 5 5 radio is searching for a valid packet +#define RADIOLIB_SI443X_PACKET_RECEIVING 0b00010000 // 4 4 radio is currently receiving packet +#define RADIOLIB_SI443X_VALID_PACKET_RECEIVED 0b00001000 // 3 3 valid packet was received +#define RADIOLIB_SI443X_CRC_ERROR 0b00000100 // 2 2 CRC check failed +#define RADIOLIB_SI443X_PACKET_TRANSMITTING 0b00000010 // 1 1 radio is currently transmitting packet +#define RADIOLIB_SI443X_PACKET_SENT 0b00000001 // 0 0 packet sent + +// RADIOLIB_SI443X_REG_HEADER_CONTROL_1 +#define RADIOLIB_SI443X_BROADCAST_ADDR_CHECK_NONE 0b00000000 // 7 4 broadcast address check: none (default) +#define RADIOLIB_SI443X_BROADCAST_ADDR_CHECK_BYTE0 0b00010000 // 7 4 on byte 0 +#define RADIOLIB_SI443X_BROADCAST_ADDR_CHECK_BYTE1 0b00100000 // 7 4 on byte 1 +#define RADIOLIB_SI443X_BROADCAST_ADDR_CHECK_BYTE2 0b01000000 // 7 4 on byte 2 +#define RADIOLIB_SI443X_BROADCAST_ADDR_CHECK_BYTE3 0b10000000 // 7 4 on byte 3 +#define RADIOLIB_SI443X_RECEIVED_HEADER_CHECK_NONE 0b00000000 // 3 0 received header check: none +#define RADIOLIB_SI443X_RECEIVED_HEADER_CHECK_BYTE0 0b00000001 // 3 0 on byte 0 +#define RADIOLIB_SI443X_RECEIVED_HEADER_CHECK_BYTE1 0b00000010 // 3 0 on byte 1 +#define RADIOLIB_SI443X_RECEIVED_HEADER_CHECK_BYTE2 0b00000100 // 3 0 on byte 2 (default) +#define RADIOLIB_SI443X_RECEIVED_HEADER_CHECK_BYTE3 0b00001000 // 3 0 on byte 3 (default) + +// RADIOLIB_SI443X_REG_HEADER_CONTROL_2 +#define RADIOLIB_SI443X_SYNC_WORD_TIMEOUT_OFF 0b00000000 // 7 7 ignore timeout period when searching for sync word: disabled (default) +#define RADIOLIB_SI443X_SYNC_WORD_TIMEOUT_ON 0b10000000 // 7 7 enabled +#define RADIOLIB_SI443X_HEADER_LENGTH_HEADER_NONE 0b00000000 // 6 4 header length: none +#define RADIOLIB_SI443X_HEADER_LENGTH_HEADER_3 0b00010000 // 6 4 header 3 +#define RADIOLIB_SI443X_HEADER_LENGTH_HEADER_32 0b00100000 // 6 4 header 3 and 2 +#define RADIOLIB_SI443X_HEADER_LENGTH_HEADER_321 0b00110000 // 6 4 header 3, 2 and 1 (default) +#define RADIOLIB_SI443X_HEADER_LENGTH_HEADER_3210 0b01000000 // 6 4 header 3, 2, 1, and 0 +#define RADIOLIB_SI443X_FIXED_PACKET_LENGTH_OFF 0b00000000 // 3 3 fixed packet length mode: disabled (default) +#define RADIOLIB_SI443X_FIXED_PACKET_LENGTH_ON 0b00001000 // 3 3 enabled +#define RADIOLIB_SI443X_SYNC_LENGTH_SYNC_3 0b00000000 // 2 1 sync word length: sync 3 +#define RADIOLIB_SI443X_SYNC_LENGTH_SYNC_32 0b00000010 // 2 1 sync 3 and 2 (default) +#define RADIOLIB_SI443X_SYNC_LENGTH_SYNC_321 0b00000100 // 2 1 sync 3, 2 and 1 +#define RADIOLIB_SI443X_SYNC_LENGTH_SYNC_3210 0b00000110 // 2 1 sync 3, 2, 1 and 0 +#define RADIOLIB_SI443X_PREAMBLE_LENGTH_MSB 0b00000000 // 0 0 preamble length MSB + +// RADIOLIB_SI443X_REG_PREAMBLE_LENGTH +#define RADIOLIB_SI443X_PREAMBLE_LENGTH_LSB 0b00001000 // 0 0 preamble length LSB, defaults to 32 bits + +// RADIOLIB_SI443X_REG_PREAMBLE_DET_CONTROL +#define RADIOLIB_SI443X_PREAMBLE_DET_THRESHOLD 0b00101000 // 7 3 number of 4-bit nibbles in valid preamble, defaults to 20 bits +#define RADIOLIB_SI443X_RSSI_OFFSET 0b00000010 // 2 0 RSSI calculation offset, defaults to +8 dB + +// RADIOLIB_SI443X_REG_SYNC_WORD_3 - RADIOLIB_SI443X_REG_SYNC_WORD_0 +#define RADIOLIB_SI443X_SYNC_WORD_3 0x2D // 7 0 sync word: 4th byte (MSB) +#define RADIOLIB_SI443X_SYNC_WORD_2 0xD4 // 7 0 3rd byte +#define RADIOLIB_SI443X_SYNC_WORD_1 0x00 // 7 0 2nd byte +#define RADIOLIB_SI443X_SYNC_WORD_0 0x00 // 7 0 1st byte (LSB) + +// RADIOLIB_SI443X_REG_CHANNEL_FILTER_COEFF +#define RADIOLIB_SI443X_INVALID_PREAMBLE_THRESHOLD 0b00000000 // 7 4 invalid preamble threshold in nibbles + +// RADIOLIB_SI443X_REG_XOSC_CONTROL_TEST +#define RADIOLIB_SI443X_STATE_LOW_POWER 0b00000000 // 7 5 chip power state: low power +#define RADIOLIB_SI443X_STATE_READY 0b00100000 // 7 5 ready +#define RADIOLIB_SI443X_STATE_TUNE 0b01100000 // 7 5 tune +#define RADIOLIB_SI443X_STATE_TX 0b01000000 // 7 5 Tx +#define RADIOLIB_SI443X_STATE_RX 0b11100000 // 7 5 Rx + +// RADIOLIB_SI443X_REG_AGC_OVERRIDE_1 +#define RADIOLIB_SI443X_AGC_GAIN_INCREASE_OFF 0b00000000 // 6 6 AGC gain increase override: disabled (default) +#define RADIOLIB_SI443X_AGC_GAIN_INCREASE_ON 0b01000000 // 6 6 enabled +#define RADIOLIB_SI443X_AGC_OFF 0b00000000 // 5 5 AGC loop: disabled +#define RADIOLIB_SI443X_AGC_ON 0b00100000 // 5 5 enabled (default) +#define RADIOLIB_SI443X_LNA_GAIN_MIN 0b00000000 // 4 4 LNA gain select: 5 dB (default) +#define RADIOLIB_SI443X_LNA_GAIN_MAX 0b00010000 // 4 4 25 dB +#define RADIOLIB_SI443X_PGA_GAIN_OVERRIDE 0b00000000 // 3 0 PGA gain override, gain = RADIOLIB_SI443X_PGA_GAIN_OVERRIDE * 3 dB + +// RADIOLIB_SI443X_REG_TX_POWER +#define RADIOLIB_SI443X_LNA_SWITCH_OFF 0b00000000 // 3 3 LNA switch control: disabled +#define RADIOLIB_SI443X_LNA_SWITCH_ON 0b00001000 // 3 3 enabled (default) +#define RADIOLIB_SI443X_OUTPUT_POWER 0b00000000 // 2 0 output power in 3 dB steps, 0 is chip min, 7 is chip max + +// RADIOLIB_SI443X_REG_TX_DATA_RATE_1 + RADIOLIB_SI443X_REG_TX_DATA_RATE_0 +#define RADIOLIB_SI443X_DATA_RATE_MSB 0x0A // 7 0 data rate: DR = 10^6 * (RADIOLIB_SI443X_DATA_RATE / 2^16) in high data rate mode or +#define RADIOLIB_SI443X_DATA_RATE_LSB 0x3D // 7 0 DR = 10^6 * (RADIOLIB_SI443X_DATA_RATE / 2^21) in low data rate mode (defaults to 40 kbps) + +// RADIOLIB_SI443X_REG_MODULATION_MODE_CONTROL_1 +#define RADIOLIB_SI443X_HIGH_DATA_RATE_MODE 0b00000000 // 5 5 data rate: above 30 kbps (default) +#define RADIOLIB_SI443X_LOW_DATA_RATE_MODE 0b00100000 // 5 5 below 30 kbps +#define RADIOLIB_SI443X_PACKET_HANDLER_POWER_DOWN_OFF 0b00000000 // 4 4 power off packet handler in low power mode: disabled (default) +#define RADIOLIB_SI443X_PACKET_HANDLER_POWER_DOWN_ON 0b00010000 // 4 4 enabled +#define RADIOLIB_SI443X_MANCHESTER_PREAMBLE_POL_LOW 0b00000000 // 3 3 preamble polarity in Manchester mode: low +#define RADIOLIB_SI443X_MANCHESTER_PREAMBLE_POL_HIGH 0b00001000 // 3 3 high (default) +#define RADIOLIB_SI443X_MANCHESTER_INVERTED_OFF 0b00000000 // 2 2 inverted Manchester encoding: disabled +#define RADIOLIB_SI443X_MANCHESTER_INVERTED_ON 0b00000100 // 2 2 enabled (default) +#define RADIOLIB_SI443X_MANCHESTER_OFF 0b00000000 // 1 1 Manchester encoding: disabled (default) +#define RADIOLIB_SI443X_MANCHESTER_ON 0b00000010 // 1 1 enabled +#define RADIOLIB_SI443X_WHITENING_OFF 0b00000000 // 0 0 data whitening: disabled (default) +#define RADIOLIB_SI443X_WHITENING_ON 0b00000001 // 0 0 enabled + +// RADIOLIB_SI443X_REG_MODULATION_MODE_CONTROL_2 +#define RADIOLIB_SI443X_TX_DATA_CLOCK_NONE 0b00000000 // 7 6 Tx data clock: disabled (default) +#define RADIOLIB_SI443X_TX_DATA_CLOCK_GPIO 0b01000000 // 7 6 GPIO pin +#define RADIOLIB_SI443X_TX_DATA_CLOCK_SDI 0b10000000 // 7 6 SDI pin +#define RADIOLIB_SI443X_TX_DATA_CLOCK_NIRQ 0b11000000 // 7 6 nIRQ pin +#define RADIOLIB_SI443X_TX_DATA_SOURCE_GPIO 0b00000000 // 5 4 Tx data source in direct mode: GPIO pin (default) +#define RADIOLIB_SI443X_TX_DATA_SOURCE_SDI 0b00010000 // 5 4 SDI pin +#define RADIOLIB_SI443X_TX_DATA_SOURCE_FIFO 0b00100000 // 5 4 FIFO +#define RADIOLIB_SI443X_TX_DATA_SOURCE_PN9 0b00110000 // 5 4 PN9 internal +#define RADIOLIB_SI443X_TX_RX_INVERTED_OFF 0b00000000 // 3 3 Tx/Rx data inverted: disabled (default) +#define RADIOLIB_SI443X_TX_RX_INVERTED_ON 0b00001000 // 3 3 enabled +#define RADIOLIB_SI443X_FREQUENCY_DEVIATION_MSB 0b00000000 // 2 2 frequency deviation MSB +#define RADIOLIB_SI443X_MODULATION_NONE 0b00000000 // 1 0 modulation type: unmodulated carrier (default) +#define RADIOLIB_SI443X_MODULATION_OOK 0b00000001 // 1 0 OOK +#define RADIOLIB_SI443X_MODULATION_FSK 0b00000010 // 1 0 FSK +#define RADIOLIB_SI443X_MODULATION_GFSK 0b00000011 // 1 0 GFSK + +// RADIOLIB_SI443X_REG_FREQUENCY_DEVIATION +#define RADIOLIB_SI443X_FREQUENCY_DEVIATION_LSB 0b00100000 // 7 0 frequency deviation LSB, Fd = 625 Hz * RADIOLIB_SI443X_FREQUENCY_DEVIATION, defaults to 20 kHz + +// RADIOLIB_SI443X_REG_FREQUENCY_OFFSET_1 + RADIOLIB_SI443X_REG_FREQUENCY_OFFSET_2 +#define RADIOLIB_SI443X_FREQUENCY_OFFSET_MSB 0x00 // 7 0 frequency offset: +#define RADIOLIB_SI443X_FREQUENCY_OFFSET_LSB 0x00 // 1 0 Foff = 156.25 Hz * (RADIOLIB_SI443X_BAND_SELECT + 1) * RADIOLIB_SI443X_FREQUENCY_OFFSET, defaults to 156.25 Hz + +// RADIOLIB_SI443X_REG_FREQUENCY_BAND_SELECT +#define RADIOLIB_SI443X_SIDE_BAND_SELECT_LOW 0b00000000 // 6 6 Rx LO tuning: below channel frequency (default) +#define RADIOLIB_SI443X_SIDE_BAND_SELECT_HIGH 0b01000000 // 6 6 above channel frequency +#define RADIOLIB_SI443X_BAND_SELECT_LOW 0b00000000 // 5 5 band select: low, 240 - 479.9 MHz +#define RADIOLIB_SI443X_BAND_SELECT_HIGH 0b00100000 // 5 5 high, 480 - 960 MHz (default) +#define RADIOLIB_SI443X_FREQUENCY_BAND_SELECT 0b00010101 // 4 0 frequency band select + +// RADIOLIB_SI443X_REG_NOM_CARRIER_FREQUENCY_1 + RADIOLIB_SI443X_REG_NOM_CARRIER_FREQUENCY_0 +#define RADIOLIB_SI443X_NOM_CARRIER_FREQUENCY_MSB 0b10111011 // 7 0 nominal carrier frequency: +#define RADIOLIB_SI443X_NOM_CARRIER_FREQUENCY_LSB 0b10000000 // 7 0 Fc = (RADIOLIB_SI443X_BAND_SELECT + 1)*10*(RADIOLIB_SI443X_FREQUENCY_BAND_SELECT + 24) + (RADIOLIB_SI443X_NOM_CARRIER_FREQUENCY - RADIOLIB_SI443X_FREQUENCY_OFFSET)/6400 [MHz] + +// RADIOLIB_SI443X_REG_FREQUENCY_HOPPING_CHANNEL_SEL +#define RADIOLIB_SI443X_FREQUENCY_HOPPING_CHANNEL 0x00 // 7 0 frequency hopping channel number + +// RADIOLIB_SI443X_REG_FREQUENCY_HOPPING_STEP_SIZE +#define RADIOLIB_SI443X_FREQUENCY_HOPPING_STEP_SIZE 0x00 // 7 0 frequency hopping step size + +// RADIOLIB_SI443X_REG_TX_FIFO_CONTROL_1 +#define RADIOLIB_SI443X_TX_FIFO_ALMOST_FULL_THRESHOLD 0x37 // 5 0 Tx FIFO almost full threshold + +// RADIOLIB_SI443X_REG_TX_FIFO_CONTROL_2 +#define RADIOLIB_SI443X_TX_FIFO_ALMOST_EMPTY_THRESHOLD 0x04 // 5 0 Tx FIFO almost full threshold + +// RADIOLIB_SI443X_REG_RX_FIFO_CONTROL +#define RADIOLIB_SI443X_RX_FIFO_ALMOST_FULL_THRESHOLD 0x37 // 5 0 Rx FIFO almost full threshold + +/*! + \class Si443x + \brief Base class for Si443x series. All derived classes for Si443x (e.g. Si4431 or Si4432) inherit from this base class. + This class should not be instantiated directly from Arduino sketch, only from its derived classes. +*/ +class Si443x: public PhysicalLayer { + public: + // introduce PhysicalLayer overloads + using PhysicalLayer::transmit; + using PhysicalLayer::receive; + using PhysicalLayer::startTransmit; + using PhysicalLayer::readData; + + // constructor + + /*! + \brief Default constructor. + \param mod Instance of Module that will be used to communicate with the radio. + */ + explicit Si443x(Module* mod); + + // basic methods + + /*! + \brief Initialization method. + \param br Bit rate of the FSK transmission in kbps (kilobits per second). + \param freqDev Frequency deviation of the FSK transmission in kHz. + \param rxBw Receiver bandwidth in kHz. + \param preambleLen Preamble Length in bits. + \returns \ref status_codes + */ + int16_t begin(float br, float freqDev, float rxBw, uint8_t preambleLen); + + /*! + \brief Reset method. Will reset the chip to the default state using SDN pin. + */ + void reset(); + + /*! + \brief Binary transmit method. Will transmit arbitrary binary data up to 64 bytes long. + For overloads to transmit Arduino String or C-string, see PhysicalLayer::transmit. + \param data Binary data that will be transmitted. + \param len Length of binary data to transmit (in bytes). + \param addr Node address to transmit the packet to. + \returns \ref status_codes + */ + int16_t transmit(const uint8_t* data, size_t len, uint8_t addr = 0) override; + + /*! + \brief Binary receive method. Will attempt to receive arbitrary binary data up to 64 bytes long. + For overloads to receive Arduino String, see PhysicalLayer::receive. + \param data Pointer to array to save the received binary data. + \param len Number of bytes that will be received. Must be known in advance for binary transmissions. + \returns \ref status_codes + */ + int16_t receive(uint8_t* data, size_t len) override; + + /*! + \brief Sets the module to sleep to save power. %Module will not be able to transmit or receive any data while in sleep mode. + %Module will wake up automatically when methods like transmit or receive are called. + \returns \ref status_codes + */ + int16_t sleep() override; + + /*! + \brief Sets the module to standby (with XTAL on). + \returns \ref status_codes + */ + int16_t standby() override; + + /*! + \brief Sets the module to standby. + \param mode Standby mode to be used. + \returns \ref status_codes + */ + int16_t standby(uint8_t mode) override; + + /*! + \brief Enables direct transmission mode. While in direct mode, the module will not be able to transmit or receive packets. + \param frf 24-bit raw frequency value to start transmitting at. Required for quick frequency shifts in RTTY. + \returns \ref status_codes + */ + int16_t transmitDirect(uint32_t frf = 0) override; + + /*! + \brief Enables direct reception mode. While in direct mode, the module will not be able to transmit or receive packets. + \returns \ref status_codes + */ + int16_t receiveDirect() override; + + /*! + \brief Disables direct mode and enables packet mode, allowing the module to receive packets. + \returns \ref status_codes + */ + int16_t packetMode(); + + // interrupt methods + + /*! + \brief Sets interrupt service routine to call when IRQ activates. + \param func ISR to call. + */ + void setIrqAction(void (*func)(void)); + + /*! + \brief Clears interrupt service routine to call when IRQ activates. + */ + void clearIrqAction(); + + /*! + \brief Sets interrupt service routine to call when a packet is received. + \param func ISR to call. + */ + void setPacketReceivedAction(void (*func)(void)) override; + + /*! + \brief Clears interrupt service routine to call when a packet is received. + */ + void clearPacketReceivedAction() override; + + /*! + \brief Sets interrupt service routine to call when a packet is sent. + \param func ISR to call. + */ + void setPacketSentAction(void (*func)(void)) override; + + /*! + \brief Clears interrupt service routine to call when a packet is sent. + */ + void clearPacketSentAction() override; + + /*! + \brief Interrupt-driven binary transmit method. Will start transmitting arbitrary binary data up to 64 bytes long. + \param data Binary data that will be transmitted. + \param len Length of binary data to transmit (in bytes). + \param addr Node address to transmit the packet to. + \returns \ref status_codes + */ + int16_t startTransmit(const uint8_t* data, size_t len, uint8_t addr = 0) override; + + /*! + \brief Clean up after transmission is done. + \returns \ref status_codes + */ + int16_t finishTransmit() override; + + /*! + \brief Interrupt-driven receive method. IRQ will be activated when full valid packet is received. + \returns \ref status_codes + */ + int16_t startReceive() override; + + /*! + \brief Interrupt-driven receive method, implemented for compatibility with PhysicalLayer. + \param timeout Ignored. + \param irqFlags Ignored. + \param irqMask Ignored. + \param len Ignored. + \returns \ref status_codes + */ + int16_t startReceive(uint32_t timeout, uint32_t irqFlags, uint32_t irqMask, size_t len) override; + + /*! + \brief Reads data that was received after calling startReceive method. When the packet length is not known in advance, + getPacketLength method must be called BEFORE calling readData! + \param data Pointer to array to save the received binary data. + \param len Number of bytes that will be read. When set to 0, the packet length will be retrieved automatically. + When more bytes than received are requested, only the number of bytes requested will be returned. + \returns \ref status_codes + */ + int16_t readData(uint8_t* data, size_t len) override; + + // configuration methods + + /*! + \brief Sets FSK bit rate. Allowed values range from 0.123 to 256.0 kbps. + \param br Bit rate to be set (in kbps). + \returns \ref status_codes + */ + int16_t setBitRate(float br) override; + + /*! + \brief Sets FSK frequency deviation from carrier frequency. Allowed values range from 0.625 to 320.0 kHz. + \param freqDev Frequency deviation to be set (in kHz). + \returns \ref status_codes + */ + int16_t setFrequencyDeviation(float freqDev) override; + + /*! + \brief Sets receiver bandwidth. Allowed values range from 2.6 to 620.7 kHz. + \param rxBw Receiver bandwidth to be set in kHz. + \returns \ref status_codes + */ + int16_t setRxBandwidth(float rxBw); + + /*! + \brief Sets sync word. Up to 4 bytes can be set as sync word. + \param syncWord Pointer to the array of sync word bytes. + \param len Sync word length in bytes. + */ + int16_t setSyncWord(uint8_t* syncWord, size_t len) override; + + /*! + \brief Sets preamble length. + \param preambleLen Preamble length to be set (in bits). + \returns \ref status_codes + */ + int16_t setPreambleLength(uint8_t preambleLen); + + /*! + \brief Query modem for the packet length of received payload. + \param update Update received packet length. Will return cached value when set to false. + \returns Length of last received packet in bytes. + */ + size_t getPacketLength(bool update = true) override; + + /*! + \brief Sets transmission encoding. Only available in FSK mode. + Allowed values are RADIOLIB_ENCODING_NRZ, RADIOLIB_ENCODING_MANCHESTER and RADIOLIB_ENCODING_WHITENING. + \param encoding Encoding to be used. + \returns \ref status_codes + */ + int16_t setEncoding(uint8_t encoding) override; + + /*! + \brief Sets Gaussian filter bandwidth-time product that will be used for data shaping. Only available in FSK mode with FSK modulation. + Allowed values are RADIOLIB_SHAPING_0_5 or RADIOLIB_SHAPING_1_0. Set to RADIOLIB_SHAPING_NONE to disable data shaping. + \param sh Gaussian shaping bandwidth-time product that will be used for data shaping + \returns \ref status_codes + */ + int16_t setDataShaping(uint8_t sh) override; + + /*! \copydoc Module::setRfSwitchPins */ + void setRfSwitchPins(uint32_t rxEn, uint32_t txEn); + + /*! \copydoc Module::setRfSwitchTable */ + void setRfSwitchTable(const uint32_t (&pins)[Module::RFSWITCH_MAX_PINS], const Module::RfSwitchMode_t table[]); + + /*! + \brief Get one truly random byte from RSSI noise. + \returns TRNG byte. + */ + uint8_t randomByte() override; + + /*! + \brief Read version SPI register. Should return RADIOLIB_SI443X_DEVICE_VERSION (0x06) if Si443x is connected and working. + \returns Version register contents or \ref status_codes + */ + int16_t getChipVersion(); + + #if !RADIOLIB_EXCLUDE_DIRECT_RECEIVE + /*! + \brief Set interrupt service routine function to call when data bit is received in direct mode. + \param func Pointer to interrupt service routine. + */ + void setDirectAction(void (*func)(void)) override; + + /*! + \brief Function to read and process data bit in direct reception mode. + \param pin Pin on which to read. + */ + void readBit(uint32_t pin) override; + #endif + + /*! + \brief Set modem in fixed packet length mode. + \param len Packet length. + \returns \ref status_codes + */ + int16_t fixedPacketLengthMode(uint8_t len = RADIOLIB_SI443X_MAX_PACKET_LENGTH); + + /*! + \brief Set modem in variable packet length mode. + \param maxLen Maximum packet length. + \returns \ref status_codes + */ + int16_t variablePacketLengthMode(uint8_t maxLen = RADIOLIB_SI443X_MAX_PACKET_LENGTH); + +#if !RADIOLIB_GODMODE && !RADIOLIB_LOW_LEVEL + protected: +#endif + Module* getMod() override; + +#if !RADIOLIB_GODMODE + protected: +#endif + int16_t setFrequencyRaw(float newFreq); + +#if !RADIOLIB_GODMODE + private: +#endif + Module* mod; + + float bitRate = 0; + float frequencyDev = 0; + float frequency = 0; + + size_t packetLength = 0; + bool packetLengthQueried = false; + uint8_t packetLengthConfig = RADIOLIB_SI443X_FIXED_PACKET_LENGTH_ON; + + bool findChip(); + void clearIRQFlags(); + void clearFIFO(size_t count); + int16_t config(); + int16_t updateClockRecovery(); + int16_t directMode(); + int16_t setPacketMode(uint8_t mode, uint8_t len); +}; + +#endif + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/nRF24/nRF24.cpp b/software/firmware/source/libraries/RadioLib/src/modules/nRF24/nRF24.cpp new file mode 100644 index 000000000..deafef27e --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/nRF24/nRF24.cpp @@ -0,0 +1,657 @@ +#include "nRF24.h" +#include +#if !RADIOLIB_EXCLUDE_NRF24 + +nRF24::nRF24(Module* mod) : PhysicalLayer(RADIOLIB_NRF24_FREQUENCY_STEP_SIZE, RADIOLIB_NRF24_MAX_PACKET_LENGTH) { + this->mod = mod; +} + +int16_t nRF24::begin(int16_t freq, int16_t dr, int8_t pwr, uint8_t addrWidth) { + // set module properties + this->mod->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_READ] = RADIOLIB_NRF24_CMD_READ; + this->mod->spiConfig.cmds[RADIOLIB_MODULE_SPI_COMMAND_WRITE] = RADIOLIB_NRF24_CMD_WRITE; + this->mod->init(); + this->mod->hal->pinMode(this->mod->getIrq(), this->mod->hal->GpioModeInput); + + // set pin mode on RST (connected to nRF24 CE pin) + this->mod->hal->pinMode(this->mod->getRst(), this->mod->hal->GpioModeOutput); + this->mod->hal->digitalWrite(this->mod->getRst(), this->mod->hal->GpioLevelLow); + + // wait for minimum power-on reset duration + this->mod->hal->delay(100); + + // check SPI connection + int16_t val = this->mod->SPIgetRegValue(RADIOLIB_NRF24_REG_SETUP_AW); + if(!((val >= 0) && (val <= 3))) { + RADIOLIB_DEBUG_BASIC_PRINTLN("No nRF24 found!"); + this->mod->term(); + return(RADIOLIB_ERR_CHIP_NOT_FOUND); + } + RADIOLIB_DEBUG_BASIC_PRINTLN("M\tnRF24"); + + // configure settings inaccessible by public API + int16_t state = config(); + RADIOLIB_ASSERT(state); + + // set mode to standby + state = standby(); + RADIOLIB_ASSERT(state); + + // set frequency + state = setFrequency(freq); + RADIOLIB_ASSERT(state); + + // set data rate + state = setBitRate(dr); + RADIOLIB_ASSERT(state); + + // set output power + state = setOutputPower(pwr); + RADIOLIB_ASSERT(state); + + // set address width + state = setAddressWidth(addrWidth); + RADIOLIB_ASSERT(state); + + // set CRC + state = setCrcFiltering(true); + RADIOLIB_ASSERT(state); + + // set auto-ACK on all pipes + state = setAutoAck(true); + RADIOLIB_ASSERT(state); + + return(state); +} + +int16_t nRF24::sleep() { + return(this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_CONFIG, RADIOLIB_NRF24_POWER_DOWN, 1, 1)); +} + +int16_t nRF24::standby() { + return(standby(RADIOLIB_NRF24_POWER_UP)); +} + +int16_t nRF24::standby(uint8_t mode) { + // make sure carrier output is disabled + this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_RF_SETUP, RADIOLIB_NRF24_CONT_WAVE_OFF, 7, 7); + this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_RF_SETUP, RADIOLIB_NRF24_PLL_LOCK_OFF, 4, 4); + this->mod->hal->digitalWrite(this->mod->getRst(), this->mod->hal->GpioLevelLow); + + // use standby-1 mode + return(this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_CONFIG, mode, 1, 1)); +} + +int16_t nRF24::transmit(const uint8_t* data, size_t len, uint8_t addr) { + // start transmission + int16_t state = startTransmit(data, len, addr); + RADIOLIB_ASSERT(state); + + // wait until transmission is finished + uint32_t start = this->mod->hal->millis(); + while(this->mod->hal->digitalRead(this->mod->getIrq())) { + this->mod->hal->yield(); + + // check maximum number of retransmits + if(getStatus(RADIOLIB_NRF24_MAX_RT)) { + finishTransmit(); + return(RADIOLIB_ERR_ACK_NOT_RECEIVED); + } + + // check timeout: 15 retries * 4ms (max Tx time as per datasheet) + 10 ms + if(this->mod->hal->millis() - start >= ((15 * 4) + 10)) { + finishTransmit(); + return(RADIOLIB_ERR_TX_TIMEOUT); + } + } + + return(finishTransmit()); +} + +int16_t nRF24::receive(uint8_t* data, size_t len) { + // start reception + int16_t state = startReceive(); + RADIOLIB_ASSERT(state); + + // wait for Rx_DataReady or timeout + uint32_t start = this->mod->hal->millis(); + while(this->mod->hal->digitalRead(this->mod->getIrq())) { + this->mod->hal->yield(); + + // check timeout: 15 retries * 4ms (max Tx time as per datasheet) + 10 ms + if(this->mod->hal->millis() - start >= ((15 * 4) + 10)) { + standby(); + clearIRQ(); + return(RADIOLIB_ERR_RX_TIMEOUT); + } + } + + // read the received data + return(readData(data, len)); +} + +int16_t nRF24::transmitDirect(uint32_t frf) { + // set raw frequency value + if(frf != 0) { + uint8_t freqRaw = frf - 2400; + this->mod->SPIwriteRegister(RADIOLIB_NRF24_REG_RF_CH, freqRaw & 0b01111111); + } + + // output carrier + int16_t state = this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_CONFIG, RADIOLIB_NRF24_PTX, 0, 0); + state |= this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_RF_SETUP, RADIOLIB_NRF24_CONT_WAVE_ON, 7, 7); + state |= this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_RF_SETUP, RADIOLIB_NRF24_PLL_LOCK_ON, 4, 4); + this->mod->hal->digitalWrite(this->mod->getRst(), this->mod->hal->GpioLevelHigh); + return(state); +} + +int16_t nRF24::receiveDirect() { + // nRF24 is unable to directly output demodulated data + // this method is implemented only for PhysicalLayer compatibility + return(RADIOLIB_ERR_NONE); +} + +void nRF24::setIrqAction(void (*func)(void)) { + this->mod->hal->attachInterrupt(this->mod->hal->pinToInterrupt(this->mod->getIrq()), func, this->mod->hal->GpioInterruptFalling); +} + +void nRF24::clearIrqAction() { + this->mod->hal->detachInterrupt(this->mod->hal->pinToInterrupt(this->mod->getIrq())); +} + +void nRF24::setPacketReceivedAction(void (*func)(void)) { + this->setIrqAction(func); +} + +void nRF24::clearPacketReceivedAction() { + this->clearIrqAction(); +} + +void nRF24::setPacketSentAction(void (*func)(void)) { + this->setIrqAction(func); +} + +void nRF24::clearPacketSentAction() { + this->clearIrqAction(); +} + +int16_t nRF24::startTransmit(const uint8_t* data, size_t len, uint8_t addr) { + // suppress unused variable warning + (void)addr; + + // check packet length + if(len > RADIOLIB_NRF24_MAX_PACKET_LENGTH) { + return(RADIOLIB_ERR_PACKET_TOO_LONG); + } + + // set mode to standby + int16_t state = standby(); + RADIOLIB_ASSERT(state); + + // enable primary Tx mode + state = this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_CONFIG, RADIOLIB_NRF24_PTX, 0, 0); + + // clear interrupts + clearIRQ(); + + // enable Tx_DataSent interrupt + state |= this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_CONFIG, RADIOLIB_NRF24_MASK_TX_DS_IRQ_ON, 5, 5); + RADIOLIB_ASSERT(state); + + // flush Tx FIFO + SPItransfer(RADIOLIB_NRF24_CMD_FLUSH_TX); + + // fill Tx FIFO + uint8_t buff[32]; + memset(buff, 0x00, 32); + memcpy(buff, data, len); + SPIwriteTxPayload(const_cast(data), len); + + // CE high to start transmitting + this->mod->hal->digitalWrite(this->mod->getRst(), this->mod->hal->GpioLevelHigh); + this->mod->hal->delay(1); + this->mod->hal->digitalWrite(this->mod->getRst(), this->mod->hal->GpioLevelLow); + + return(state); +} + +int16_t nRF24::finishTransmit() { + // clear interrupt flags + clearIRQ(); + + // set mode to standby to disable transmitter/RF switch + return(standby()); +} + +int16_t nRF24::startReceive() { + // set mode to standby + int16_t state = standby(); + RADIOLIB_ASSERT(state); + + // enable primary Rx mode + state = this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_CONFIG, RADIOLIB_NRF24_PRX, 0, 0); + RADIOLIB_ASSERT(state); + + // enable Rx_DataReady interrupt + clearIRQ(); + state = this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_CONFIG, RADIOLIB_NRF24_MASK_RX_DR_IRQ_ON, 6, 6); + RADIOLIB_ASSERT(state); + + // flush Rx FIFO + SPItransfer(RADIOLIB_NRF24_CMD_FLUSH_RX); + + // CE high to start receiving + this->mod->hal->digitalWrite(this->mod->getRst(), this->mod->hal->GpioLevelHigh); + + // wait to enter Rx state + this->mod->hal->delay(1); + + return(state); +} + +int16_t nRF24::startReceive(uint32_t timeout, uint32_t irqFlags, uint32_t irqMask, size_t len) { + (void)timeout; + (void)irqFlags; + (void)irqMask; + (void)len; + return(startReceive()); +} + +int16_t nRF24::readData(uint8_t* data, size_t len) { + // set mode to standby + int16_t state = standby(); + RADIOLIB_ASSERT(state); + + // get packet length + size_t length = getPacketLength(); + if((len != 0) && (len < length)) { + // user requested less data than we got, only return what was requested + length = len; + } + + // read packet data + SPIreadRxPayload(data, length); + + // clear interrupt + clearIRQ(); + + return(RADIOLIB_ERR_NONE); +} + +int16_t nRF24::setFrequency(float freq) { + RADIOLIB_CHECK_RANGE((uint16_t)freq, 2400, 2525, RADIOLIB_ERR_INVALID_FREQUENCY); + + // set frequency + uint8_t freqRaw = (uint16_t)freq - 2400; + int16_t state = this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_RF_CH, freqRaw, 6, 0); + + if(state == RADIOLIB_ERR_NONE) { + this->frequency = freq; + } + + return(state); +} + +int16_t nRF24::setBitRate(float br) { + // set mode to standby + int16_t state = standby(); + RADIOLIB_ASSERT(state); + + // set data rate + uint16_t bitRate = (uint16_t)br; + if(bitRate == 250) { + state = this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_RF_SETUP, RADIOLIB_NRF24_DR_250_KBPS, 5, 5); + state |= this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_RF_SETUP, RADIOLIB_NRF24_DR_250_KBPS, 3, 3); + } else if(bitRate == 1000) { + state = this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_RF_SETUP, RADIOLIB_NRF24_DR_1_MBPS, 5, 5); + state |= this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_RF_SETUP, RADIOLIB_NRF24_DR_1_MBPS, 3, 3); + } else if(bitRate == 2000) { + state = this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_RF_SETUP, RADIOLIB_NRF24_DR_2_MBPS, 5, 5); + state |= this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_RF_SETUP, RADIOLIB_NRF24_DR_2_MBPS, 3, 3); + } else { + return(RADIOLIB_ERR_INVALID_DATA_RATE); + } + + if(state == RADIOLIB_ERR_NONE) { + this->dataRate = bitRate; + } + + + return(state); +} + +int16_t nRF24::setOutputPower(int8_t pwr) { + // set mode to standby + int16_t state = standby(); + RADIOLIB_ASSERT(state); + + // check allowed values + uint8_t powerRaw = 0; + switch(pwr) { + case -18: + powerRaw = RADIOLIB_NRF24_RF_PWR_18_DBM; + break; + case -12: + powerRaw = RADIOLIB_NRF24_RF_PWR_12_DBM; + break; + case -6: + powerRaw = RADIOLIB_NRF24_RF_PWR_6_DBM; + break; + case 0: + powerRaw = RADIOLIB_NRF24_RF_PWR_0_DBM; + break; + default: + return(RADIOLIB_ERR_INVALID_OUTPUT_POWER); + } + + // write new register value + state = this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_RF_SETUP, powerRaw, 2, 1); + + if(state == RADIOLIB_ERR_NONE) { + this->power = pwr; + } + + return(state); +} + +int16_t nRF24::setAddressWidth(uint8_t addrWidth) { + // set mode to standby + int16_t state = standby(); + RADIOLIB_ASSERT(state); + + // set address width + switch(addrWidth) { + case 2: + // Even if marked as 'Illegal' on the datasheet this will work: + // http://travisgoodspeed.blogspot.com/2011/02/promiscuity-is-nrf24l01s-duty.html + state = this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_SETUP_AW, RADIOLIB_NRF24_ADDRESS_2_BYTES, 1, 0); + break; + case 3: + state = this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_SETUP_AW, RADIOLIB_NRF24_ADDRESS_3_BYTES, 1, 0); + break; + case 4: + state = this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_SETUP_AW, RADIOLIB_NRF24_ADDRESS_4_BYTES, 1, 0); + break; + case 5: + state = this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_SETUP_AW, RADIOLIB_NRF24_ADDRESS_5_BYTES, 1, 0); + break; + default: + return(RADIOLIB_ERR_INVALID_ADDRESS_WIDTH); + } + + // save address width + if(state == RADIOLIB_ERR_NONE) { + this->addressWidth = addrWidth; + } + + return(state); +} + +int16_t nRF24::setTransmitPipe(uint8_t* addr) { + // set mode to standby + int16_t state = standby(); + RADIOLIB_ASSERT(state); + + // set transmit address + this->mod->SPIwriteRegisterBurst(RADIOLIB_NRF24_REG_TX_ADDR, addr, this->addressWidth); + + // set Rx pipe 0 address (for ACK) + this->mod->SPIwriteRegisterBurst(RADIOLIB_NRF24_REG_RX_ADDR_P0, addr, this->addressWidth); + state |= this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_EN_RXADDR, RADIOLIB_NRF24_P0_ON, 0, 0); + + return(state); +} + +int16_t nRF24::setReceivePipe(uint8_t pipeNum, uint8_t* addr) { + // set mode to standby + int16_t state = standby(); + RADIOLIB_ASSERT(state); + + // write full pipe 0 - 1 address and enable the pipe + switch(pipeNum) { + case 0: + this->mod->SPIwriteRegisterBurst(RADIOLIB_NRF24_REG_RX_ADDR_P0, addr, this->addressWidth); + state |= this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_EN_RXADDR, RADIOLIB_NRF24_P0_ON, 0, 0); + break; + case 1: + this->mod->SPIwriteRegisterBurst(RADIOLIB_NRF24_REG_RX_ADDR_P1, addr, this->addressWidth); + state |= this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_EN_RXADDR, RADIOLIB_NRF24_P1_ON, 1, 1); + break; + default: + return(RADIOLIB_ERR_INVALID_PIPE_NUMBER); + } + + return(state); +} + +int16_t nRF24::setReceivePipe(uint8_t pipeNum, uint8_t addrByte) { + // set mode to standby + int16_t state = standby(); + RADIOLIB_ASSERT(state); + + // write unique pipe 2 - 5 address and enable the pipe + switch(pipeNum) { + case 2: + state = this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_RX_ADDR_P2, addrByte); + state |= this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_EN_RXADDR, RADIOLIB_NRF24_P2_ON, 2, 2); + break; + case 3: + state = this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_RX_ADDR_P3, addrByte); + state |= this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_EN_RXADDR, RADIOLIB_NRF24_P3_ON, 3, 3); + break; + case 4: + state = this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_RX_ADDR_P4, addrByte); + state |= this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_EN_RXADDR, RADIOLIB_NRF24_P4_ON, 4, 4); + break; + case 5: + state = this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_RX_ADDR_P5, addrByte); + state |= this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_EN_RXADDR, RADIOLIB_NRF24_P5_ON, 5, 5); + break; + default: + return(RADIOLIB_ERR_INVALID_PIPE_NUMBER); + } + + return(state); +} + +int16_t nRF24::disablePipe(uint8_t pipeNum) { + // set mode to standby + int16_t state = standby(); + RADIOLIB_ASSERT(state); + + switch(pipeNum) { + case 0: + state = this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_EN_RXADDR, RADIOLIB_NRF24_P0_OFF, 0, 0); + break; + case 1: + state = this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_EN_RXADDR, RADIOLIB_NRF24_P1_OFF, 1, 1); + break; + case 2: + state = this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_EN_RXADDR, RADIOLIB_NRF24_P2_OFF, 2, 2); + break; + case 3: + state = this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_EN_RXADDR, RADIOLIB_NRF24_P3_OFF, 3, 3); + break; + case 4: + state = this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_EN_RXADDR, RADIOLIB_NRF24_P4_OFF, 4, 4); + break; + case 5: + state = this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_EN_RXADDR, RADIOLIB_NRF24_P5_OFF, 5, 5); + break; + default: + return(RADIOLIB_ERR_INVALID_PIPE_NUMBER); + } + + return(state); +} + +int16_t nRF24::getStatus(uint8_t mask) { + return(this->mod->SPIgetRegValue(RADIOLIB_NRF24_REG_STATUS) & mask); +} + +bool nRF24::isCarrierDetected() { + return(this->mod->SPIgetRegValue(RADIOLIB_NRF24_REG_RPD, 0, 0) == 1); +} + +int16_t nRF24::setFrequencyDeviation(float freqDev) { + // nRF24 is unable to set frequency deviation + // this method is implemented only for PhysicalLayer compatibility + (void)freqDev; + return(RADIOLIB_ERR_NONE); +} + +size_t nRF24::getPacketLength(bool update) { + (void)update; + uint8_t length = 0; + SPItransfer(RADIOLIB_NRF24_CMD_READ_RX_PAYLOAD_WIDTH, false, NULL, &length, 1); + return((size_t)length); +} + +int16_t nRF24::setCrcFiltering(bool crcOn) { + // Auto Ack needs to be disabled in order to disable CRC. + if (!crcOn) { + int16_t status = setAutoAck(false); + RADIOLIB_ASSERT(status) + } + + // Disable CRC + return this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_CONFIG, (crcOn ? RADIOLIB_NRF24_CRC_ON : RADIOLIB_NRF24_CRC_OFF), 3, 3); +} + +int16_t nRF24::setAutoAck(bool autoAckOn){ + return this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_EN_AA, (autoAckOn ? RADIOLIB_NRF24_AA_ALL_ON : RADIOLIB_NRF24_AA_ALL_OFF), 5, 0); +} + +int16_t nRF24::setAutoAck(uint8_t pipeNum, bool autoAckOn){ + switch(pipeNum) { + case 0: + return this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_EN_AA, (autoAckOn ? RADIOLIB_NRF24_AA_P0_ON : RADIOLIB_NRF24_AA_P0_OFF), 0, 0); + break; + case 1: + return this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_EN_AA, (autoAckOn ? RADIOLIB_NRF24_AA_P1_ON : RADIOLIB_NRF24_AA_P1_OFF), 1, 1); + break; + case 2: + return this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_EN_AA, (autoAckOn ? RADIOLIB_NRF24_AA_P2_ON : RADIOLIB_NRF24_AA_P2_OFF), 2, 2); + break; + case 3: + return this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_EN_AA, (autoAckOn ? RADIOLIB_NRF24_AA_P3_ON : RADIOLIB_NRF24_AA_P3_OFF), 3, 3); + break; + case 4: + return this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_EN_AA, (autoAckOn ? RADIOLIB_NRF24_AA_P4_ON : RADIOLIB_NRF24_AA_P4_OFF), 4, 4); + break; + case 5: + return this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_EN_AA, (autoAckOn ? RADIOLIB_NRF24_AA_P5_ON : RADIOLIB_NRF24_AA_P5_OFF), 5, 5); + break; + default: + return (RADIOLIB_ERR_INVALID_PIPE_NUMBER); + } +} + +int16_t nRF24::setDataShaping(uint8_t sh) { + // nRF24 is unable to set data shaping + // this method is implemented only for PhysicalLayer compatibility + (void)sh; + return(RADIOLIB_ERR_NONE); +} + +int16_t nRF24::setEncoding(uint8_t encoding) { + // nRF24 is unable to set encoding + // this method is implemented only for PhysicalLayer compatibility + (void)encoding; + return(RADIOLIB_ERR_NONE); +} + +void nRF24::clearIRQ() { + // clear status bits + this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_STATUS, RADIOLIB_NRF24_RX_DR | RADIOLIB_NRF24_TX_DS | RADIOLIB_NRF24_MAX_RT, 6, 4); + + // disable interrupts + this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_CONFIG, RADIOLIB_NRF24_MASK_RX_DR_IRQ_OFF | RADIOLIB_NRF24_MASK_TX_DS_IRQ_OFF | RADIOLIB_NRF24_MASK_MAX_RT_IRQ_OFF, 6, 4); +} + +int16_t nRF24::config() { + // enable 16-bit CRC + int16_t state = this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_CONFIG, RADIOLIB_NRF24_CRC_ON | RADIOLIB_NRF24_CRC_16, 3, 2); + RADIOLIB_ASSERT(state); + + // set 15 retries and delay 1500 (5*250) us + this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_SETUP_RETR, (5 << 4) | 5); + + // set features: dynamic payload on, payload with ACK packets off, dynamic ACK off + state = this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_FEATURE, RADIOLIB_NRF24_DPL_ON | RADIOLIB_NRF24_ACK_PAY_OFF | RADIOLIB_NRF24_DYN_ACK_OFF, 2, 0); + RADIOLIB_ASSERT(state); + + // enable dynamic payloads + state = this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_DYNPD, RADIOLIB_NRF24_DPL_ALL_ON, 5, 0); + RADIOLIB_ASSERT(state); + + // reset IRQ + clearIRQ(); + + // clear status + this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_STATUS, RADIOLIB_NRF24_RX_DR | RADIOLIB_NRF24_TX_DS | RADIOLIB_NRF24_MAX_RT, 6, 4); + + // flush FIFOs + SPItransfer(RADIOLIB_NRF24_CMD_FLUSH_TX); + SPItransfer(RADIOLIB_NRF24_CMD_FLUSH_RX); + + // power up + this->mod->SPIsetRegValue(RADIOLIB_NRF24_REG_CONFIG, RADIOLIB_NRF24_POWER_UP, 1, 1); + this->mod->hal->delay(5); + + return(state); +} + +Module* nRF24::getMod() { + return(this->mod); +} + +void nRF24::SPIreadRxPayload(uint8_t* data, uint8_t numBytes) { + SPItransfer(RADIOLIB_NRF24_CMD_READ_RX_PAYLOAD, false, NULL, data, numBytes); +} + +void nRF24::SPIwriteTxPayload(uint8_t* data, uint8_t numBytes) { + SPItransfer(RADIOLIB_NRF24_CMD_WRITE_TX_PAYLOAD, true, data, NULL, numBytes); +} + +void nRF24::SPItransfer(uint8_t cmd, bool write, uint8_t* dataOut, uint8_t* dataIn, uint8_t numBytes) { + // prepare the buffers + size_t buffLen = 1 + numBytes; + #if RADIOLIB_STATIC_ONLY + uint8_t buffOut[RADIOLIB_STATIC_ARRAY_SIZE]; + uint8_t buffIn[RADIOLIB_STATIC_ARRAY_SIZE]; + #else + uint8_t* buffOut = new uint8_t[buffLen]; + uint8_t* buffIn = new uint8_t[buffLen]; + #endif + uint8_t* buffOutPtr = buffOut; + + // copy the command + *(buffOutPtr++) = cmd; + + // copy the data + if(write) { + memcpy(buffOutPtr, dataOut, numBytes); + } else { + memset(buffOutPtr, 0x00, numBytes); + } + + // do the transfer + this->mod->hal->digitalWrite(this->mod->getCs(), this->mod->hal->GpioLevelLow); + this->mod->hal->spiBeginTransaction(); + this->mod->hal->spiTransfer(buffOut, buffLen, buffIn); + this->mod->hal->spiEndTransaction(); + this->mod->hal->digitalWrite(this->mod->getCs(), this->mod->hal->GpioLevelHigh); + + // copy the data + if(!write) { + memcpy(dataIn, &buffIn[1], numBytes); + } + + #if !RADIOLIB_STATIC_ONLY + delete[] buffOut; + delete[] buffIn; + #endif +} + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/modules/nRF24/nRF24.h b/software/firmware/source/libraries/RadioLib/src/modules/nRF24/nRF24.h new file mode 100644 index 000000000..428df70f4 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/modules/nRF24/nRF24.h @@ -0,0 +1,491 @@ +#if !defined(_RADIOLIB_NRF24_H) && !RADIOLIB_EXCLUDE_NRF24 +#define _RADIOLIB_NRF24_H + +#include "../../Module.h" +#include "../../TypeDef.h" + +#include "../../protocols/PhysicalLayer/PhysicalLayer.h" + +// nRF24 physical layer properties +#define RADIOLIB_NRF24_FREQUENCY_STEP_SIZE 1000000.0 +#define RADIOLIB_NRF24_MAX_PACKET_LENGTH 32 + +// nRF24 SPI commands +#define RADIOLIB_NRF24_CMD_READ 0b00000000 +#define RADIOLIB_NRF24_CMD_WRITE 0b00100000 +#define RADIOLIB_NRF24_CMD_READ_RX_PAYLOAD 0b01100001 +#define RADIOLIB_NRF24_CMD_WRITE_TX_PAYLOAD 0b10100000 +#define RADIOLIB_NRF24_CMD_FLUSH_TX 0b11100001 +#define RADIOLIB_NRF24_CMD_FLUSH_RX 0b11100010 +#define RADIOLIB_NRF24_CMD_REUSE_TX_PAXLOAD 0b11100011 +#define RADIOLIB_NRF24_CMD_READ_RX_PAYLOAD_WIDTH 0b01100000 +#define RADIOLIB_NRF24_CMD_WRITE_ACK_PAYLOAD 0b10101000 +#define RADIOLIB_NRF24_CMD_WRITE_TX_PAYLOAD_NOACK 0b10110000 +#define RADIOLIB_NRF24_CMD_NOP 0b11111111 + +// nRF24 register map +#define RADIOLIB_NRF24_REG_CONFIG 0x00 +#define RADIOLIB_NRF24_REG_EN_AA 0x01 +#define RADIOLIB_NRF24_REG_EN_RXADDR 0x02 +#define RADIOLIB_NRF24_REG_SETUP_AW 0x03 +#define RADIOLIB_NRF24_REG_SETUP_RETR 0x04 +#define RADIOLIB_NRF24_REG_RF_CH 0x05 +#define RADIOLIB_NRF24_REG_RF_SETUP 0x06 +#define RADIOLIB_NRF24_REG_STATUS 0x07 +#define RADIOLIB_NRF24_REG_OBSERVE_TX 0x08 +#define RADIOLIB_NRF24_REG_RPD 0x09 +#define RADIOLIB_NRF24_REG_RX_ADDR_P0 0x0A +#define RADIOLIB_NRF24_REG_RX_ADDR_P1 0x0B +#define RADIOLIB_NRF24_REG_RX_ADDR_P2 0x0C +#define RADIOLIB_NRF24_REG_RX_ADDR_P3 0x0D +#define RADIOLIB_NRF24_REG_RX_ADDR_P4 0x0E +#define RADIOLIB_NRF24_REG_RX_ADDR_P5 0x0F +#define RADIOLIB_NRF24_REG_TX_ADDR 0x10 +#define RADIOLIB_NRF24_REG_RX_PW_P0 0x11 +#define RADIOLIB_NRF24_REG_RX_PW_P1 0x12 +#define RADIOLIB_NRF24_REG_RX_PW_P2 0x13 +#define RADIOLIB_NRF24_REG_RX_PW_P3 0x14 +#define RADIOLIB_NRF24_REG_RX_PW_P4 0x15 +#define RADIOLIB_NRF24_REG_RX_PW_P5 0x16 +#define RADIOLIB_NRF24_REG_FIFO_STATUS 0x17 +#define RADIOLIB_NRF24_REG_DYNPD 0x1C +#define RADIOLIB_NRF24_REG_FEATURE 0x1D + +// RADIOLIB_NRF24_REG_CONFIG MSB LSB DESCRIPTION +#define RADIOLIB_NRF24_MASK_RX_DR_IRQ_OFF 0b01000000 // 6 6 RX_DR will not be reflected on IRQ pin +#define RADIOLIB_NRF24_MASK_RX_DR_IRQ_ON 0b00000000 // 6 6 RX_DR will be reflected on IRQ pin as active low (default) +#define RADIOLIB_NRF24_MASK_TX_DS_IRQ_OFF 0b00100000 // 5 5 TX_DS will not be reflected on IRQ pin +#define RADIOLIB_NRF24_MASK_TX_DS_IRQ_ON 0b00000000 // 5 5 TX_DS will be reflected on IRQ pin as active low (default) +#define RADIOLIB_NRF24_MASK_MAX_RT_IRQ_OFF 0b00010000 // 4 4 MAX_RT will not be reflected on IRQ pin +#define RADIOLIB_NRF24_MASK_MAX_RT_IRQ_ON 0b00000000 // 4 4 MAX_RT will be reflected on IRQ pin as active low (default) +#define RADIOLIB_NRF24_CRC_OFF 0b00000000 // 3 3 CRC calculation: disabled +#define RADIOLIB_NRF24_CRC_ON 0b00001000 // 3 3 enabled (default) +#define RADIOLIB_NRF24_CRC_8 0b00000000 // 2 2 CRC scheme: CRC8 (default) +#define RADIOLIB_NRF24_CRC_16 0b00000100 // 2 2 CRC16 +#define RADIOLIB_NRF24_POWER_UP 0b00000010 // 1 1 power up +#define RADIOLIB_NRF24_POWER_DOWN 0b00000000 // 1 1 power down +#define RADIOLIB_NRF24_PTX 0b00000000 // 0 0 enable primary Tx +#define RADIOLIB_NRF24_PRX 0b00000001 // 0 0 enable primary Rx + +// RADIOLIB_NRF24_REG_EN_AA +#define RADIOLIB_NRF24_AA_ALL_OFF 0b00000000 // 5 0 auto-ACK on all pipes: disabled +#define RADIOLIB_NRF24_AA_ALL_ON 0b00111111 // 5 0 enabled (default) +#define RADIOLIB_NRF24_AA_P5_OFF 0b00000000 // 5 5 auto-ACK on pipe 5: disabled +#define RADIOLIB_NRF24_AA_P5_ON 0b00100000 // 5 5 enabled (default) +#define RADIOLIB_NRF24_AA_P4_OFF 0b00000000 // 4 4 auto-ACK on pipe 4: disabled +#define RADIOLIB_NRF24_AA_P4_ON 0b00010000 // 4 4 enabled (default) +#define RADIOLIB_NRF24_AA_P3_OFF 0b00000000 // 3 3 auto-ACK on pipe 3: disabled +#define RADIOLIB_NRF24_AA_P3_ON 0b00001000 // 3 3 enabled (default) +#define RADIOLIB_NRF24_AA_P2_OFF 0b00000000 // 2 2 auto-ACK on pipe 2: disabled +#define RADIOLIB_NRF24_AA_P2_ON 0b00000100 // 2 2 enabled (default) +#define RADIOLIB_NRF24_AA_P1_OFF 0b00000000 // 1 1 auto-ACK on pipe 1: disabled +#define RADIOLIB_NRF24_AA_P1_ON 0b00000010 // 1 1 enabled (default) +#define RADIOLIB_NRF24_AA_P0_OFF 0b00000000 // 0 0 auto-ACK on pipe 0: disabled +#define RADIOLIB_NRF24_AA_P0_ON 0b00000001 // 0 0 enabled (default) + +// RADIOLIB_NRF24_REG_EN_RXADDR +#define RADIOLIB_NRF24_P5_OFF 0b00000000 // 5 5 receive pipe 5: disabled (default) +#define RADIOLIB_NRF24_P5_ON 0b00100000 // 5 5 enabled +#define RADIOLIB_NRF24_P4_OFF 0b00000000 // 4 4 receive pipe 4: disabled (default) +#define RADIOLIB_NRF24_P4_ON 0b00010000 // 4 4 enabled +#define RADIOLIB_NRF24_P3_OFF 0b00000000 // 3 3 receive pipe 3: disabled (default) +#define RADIOLIB_NRF24_P3_ON 0b00001000 // 3 3 enabled +#define RADIOLIB_NRF24_P2_OFF 0b00000000 // 2 2 receive pipe 2: disabled (default) +#define RADIOLIB_NRF24_P2_ON 0b00000100 // 2 2 enabled +#define RADIOLIB_NRF24_P1_OFF 0b00000000 // 1 1 receive pipe 1: disabled +#define RADIOLIB_NRF24_P1_ON 0b00000010 // 1 1 enabled (default) +#define RADIOLIB_NRF24_P0_OFF 0b00000000 // 0 0 receive pipe 0: disabled +#define RADIOLIB_NRF24_P0_ON 0b00000001 // 0 0 enabled (default) + +// RADIOLIB_NRF24_REG_SETUP_AW +#define RADIOLIB_NRF24_ADDRESS_2_BYTES 0b00000000 // 1 0 address width: 2 bytes +#define RADIOLIB_NRF24_ADDRESS_3_BYTES 0b00000001 // 1 0 3 bytes +#define RADIOLIB_NRF24_ADDRESS_4_BYTES 0b00000010 // 1 0 4 bytes +#define RADIOLIB_NRF24_ADDRESS_5_BYTES 0b00000011 // 1 0 5 bytes (default) + +// RADIOLIB_NRF24_REG_SETUP_RETR +#define RADIOLIB_NRF24_ARD 0b00000000 // 7 4 auto retransmit delay: t[us] = (NRF24_ARD + 1) * 250 us +#define RADIOLIB_NRF24_ARC_OFF 0b00000000 // 3 0 auto retransmit count: auto retransmit disabled +#define RADIOLIB_NRF24_ARC 0b00000011 // 3 0 up to 3 retransmits on AA fail (default) + +// RADIOLIB_NRF24_REG_RF_CH +#define RADIOLIB_NRF24_RF_CH 0b00000010 // 6 0 RF channel: f_CH[MHz] = 2400 MHz + NRF24_RF_CH + +// RADIOLIB_NRF24_REG_RF_SETUP +#define RADIOLIB_NRF24_CONT_WAVE_OFF 0b00000000 // 7 7 continuous carrier transmit: disabled (default) +#define RADIOLIB_NRF24_CONT_WAVE_ON 0b10000000 // 7 7 enabled +#define RADIOLIB_NRF24_DR_250_KBPS 0b00100000 // 5 5 data rate: 250 kbps +#define RADIOLIB_NRF24_DR_1_MBPS 0b00000000 // 3 3 1 Mbps (default) +#define RADIOLIB_NRF24_DR_2_MBPS 0b00001000 // 3 3 2 Mbps +#define RADIOLIB_NRF24_PLL_LOCK_ON 0b00010000 // 4 4 force PLL lock: enabled +#define RADIOLIB_NRF24_PLL_LOCK_OFF 0b00000000 // 4 4 disabled (default) +#define RADIOLIB_NRF24_RF_PWR_18_DBM 0b00000000 // 2 1 output power: -18 dBm +#define RADIOLIB_NRF24_RF_PWR_12_DBM 0b00000010 // 2 1 -12 dBm +#define RADIOLIB_NRF24_RF_PWR_6_DBM 0b00000100 // 2 1 -6 dBm +#define RADIOLIB_NRF24_RF_PWR_0_DBM 0b00000110 // 2 1 0 dBm (default) + +// RADIOLIB_NRF24_REG_STATUS +#define RADIOLIB_NRF24_RX_DR 0b01000000 // 6 6 Rx data ready +#define RADIOLIB_NRF24_TX_DS 0b00100000 // 5 5 Tx data sent +#define RADIOLIB_NRF24_MAX_RT 0b00010000 // 4 4 maximum number of retransmits reached (must be cleared to continue) +#define RADIOLIB_NRF24_RX_FIFO_EMPTY 0b00001110 // 3 1 Rx FIFO is empty +#define RADIOLIB_NRF24_RX_P_NO 0b00000000 // 3 1 number of data pipe that received data +#define RADIOLIB_NRF24_TX_FIFO_FULL 0b00000001 // 0 0 Tx FIFO is full + +// RADIOLIB_NRF24_REG_OBSERVE_TX +#define RADIOLIB_NRF24_PLOS_CNT 0b00000000 // 7 4 number of lost packets +#define RADIOLIB_NRF24_ARC_CNT 0b00000000 // 3 0 number of retransmitted packets + +// RADIOLIB_NRF24_REG_RPD +#define RADIOLIB_NRF24_RP_BELOW_64_DBM 0b00000000 // 0 0 received power in the current channel: less than -64 dBm +#define RADIOLIB_NRF24_RP_ABOVE_64_DBM 0b00000001 // 0 0 more than -64 dBm + +// RADIOLIB_NRF24_REG_FIFO_STATUS +#define RADIOLIB_NRF24_TX_REUSE 0b01000000 // 6 6 reusing last transmitted payload +#define RADIOLIB_NRF24_TX_FIFO_FULL_FLAG 0b00100000 // 5 5 Tx FIFO is full +#define RADIOLIB_NRF24_TX_FIFO_EMPTY_FLAG 0b00010000 // 4 4 Tx FIFO is empty +#define RADIOLIB_NRF24_RX_FIFO_FULL_FLAG 0b00000010 // 1 1 Rx FIFO is full +#define RADIOLIB_NRF24_RX_FIFO_EMPTY_FLAG 0b00000001 // 0 0 Rx FIFO is empty + +// RADIOLIB_NRF24_REG_DYNPD +#define RADIOLIB_NRF24_DPL_P5_OFF 0b00000000 // 5 5 dynamic payload length on pipe 5: disabled (default) +#define RADIOLIB_NRF24_DPL_P5_ON 0b00100000 // 5 5 enabled +#define RADIOLIB_NRF24_DPL_P4_OFF 0b00000000 // 4 4 dynamic payload length on pipe 4: disabled (default) +#define RADIOLIB_NRF24_DPL_P4_ON 0b00010000 // 4 4 enabled +#define RADIOLIB_NRF24_DPL_P3_OFF 0b00000000 // 3 3 dynamic payload length on pipe 3: disabled (default) +#define RADIOLIB_NRF24_DPL_P3_ON 0b00001000 // 3 3 enabled +#define RADIOLIB_NRF24_DPL_P2_OFF 0b00000000 // 2 2 dynamic payload length on pipe 2: disabled (default) +#define RADIOLIB_NRF24_DPL_P2_ON 0b00000100 // 2 2 enabled +#define RADIOLIB_NRF24_DPL_P1_OFF 0b00000000 // 1 1 dynamic payload length on pipe 1: disabled (default) +#define RADIOLIB_NRF24_DPL_P1_ON 0b00000010 // 1 1 enabled +#define RADIOLIB_NRF24_DPL_P0_OFF 0b00000000 // 0 0 dynamic payload length on pipe 0: disabled (default) +#define RADIOLIB_NRF24_DPL_P0_ON 0b00000001 // 0 0 enabled +#define RADIOLIB_NRF24_DPL_ALL_OFF 0b00000000 // 5 0 disable all dynamic payloads +#define RADIOLIB_NRF24_DPL_ALL_ON 0b00111111 // 5 0 enable all dynamic payloads + +// RADIOLIB_NRF24_REG_FEATURE +#define RADIOLIB_NRF24_DPL_OFF 0b00000000 // 2 2 dynamic payload length: disabled (default) +#define RADIOLIB_NRF24_DPL_ON 0b00000100 // 2 2 enabled +#define RADIOLIB_NRF24_ACK_PAY_OFF 0b00000000 // 1 1 payload with ACK packets: disabled (default) +#define RADIOLIB_NRF24_ACK_PAY_ON 0b00000010 // 1 1 enabled +#define RADIOLIB_NRF24_DYN_ACK_OFF 0b00000000 // 0 0 payloads without ACK: disabled (default) +#define RADIOLIB_NRF24_DYN_ACK_ON 0b00000001 // 0 0 enabled + +// RadioLib defaults +#define RADIOLIB_NRF24_DEFAULT_FREQ 2400 +#define RADIOLIB_NRF24_DEFAULT_DR 1000 +#define RADIOLIB_NRF24_DEFAULT_POWER -12 +#define RADIOLIB_NRF24_DEFAULT_ADDRWIDTH 5 + +/*! + \class nRF24 + \brief Control class for %nRF24 module. +*/ +class nRF24: public PhysicalLayer { + public: + // introduce PhysicalLayer overloads + using PhysicalLayer::transmit; + using PhysicalLayer::receive; + using PhysicalLayer::startTransmit; + using PhysicalLayer::readData; + + /*! + \brief Default constructor. + \param mod Instance of Module that will be used to communicate with the radio. + */ + nRF24(Module* mod); // cppcheck-suppress noExplicitConstructor + + // basic methods + + /*! + \brief Initialization method. + \param freq Carrier frequency in MHz. Defaults to 2400 MHz. + \param dr Data rate to be used in kbps. Defaults to 1000 kbps. + \param pwr Output power in dBm. Defaults to -12 dBm. + \param addrWidth Address width in bytes. Defaults to 5 bytes. + \returns \ref status_codes + */ + int16_t begin( + int16_t freq = RADIOLIB_NRF24_DEFAULT_FREQ, + int16_t dr = RADIOLIB_NRF24_DEFAULT_DR, + int8_t pwr = RADIOLIB_NRF24_DEFAULT_POWER, + uint8_t addrWidth = RADIOLIB_NRF24_DEFAULT_ADDRWIDTH); + + /*! + \brief Sets the module to sleep mode. + \returns \ref status_codes + */ + int16_t sleep() override; + + /*! + \brief Sets the module to standby mode. + \returns \ref status_codes + */ + int16_t standby() override; + + /*! + \brief Sets the module to standby. + \param mode Standby mode to be used. + \returns \ref status_codes + */ + int16_t standby(uint8_t mode) override; + + /*! + \brief Blocking binary transmit method. + Overloads for string-based transmissions are implemented in PhysicalLayer. + \param data Binary data to be sent. + \param len Number of bytes to send. + \param addr Dummy address parameter, to ensure PhysicalLayer compatibility. + \returns \ref status_codes + */ + int16_t transmit(const uint8_t* data, size_t len, uint8_t addr) override; + + /*! + \brief Blocking binary receive method. + Overloads for string-based transmissions are implemented in PhysicalLayer. + \param data Binary data to be sent. + \param len Number of bytes to send. + \returns \ref status_codes + */ + int16_t receive(uint8_t* data, size_t len) override; + + /*! + \brief Starts direct mode transmission. + \param frf Raw RF frequency value. Defaults to 0, required for quick frequency shifts in RTTY. + \returns \ref status_codes + */ + int16_t transmitDirect(uint32_t frf = 0) override; + + /*! + \brief Dummy direct mode reception method, to ensure PhysicalLayer compatibility. + \returns \ref status_codes + */ + int16_t receiveDirect() override; + + // interrupt methods + + /*! + \brief Sets interrupt service routine to call when IRQ activates. + \param func ISR to call. + */ + void setIrqAction(void (*func)(void)); + + /*! + \brief Clears interrupt service routine . + */ + void clearIrqAction(); + + /*! + \brief Sets interrupt service routine to call when a packet is received. + \param func ISR to call. + */ + void setPacketReceivedAction(void (*func)(void)) override; + + /*! + \brief Clears interrupt service routine to call when a packet is received. + */ + void clearPacketReceivedAction() override; + + /*! + \brief Sets interrupt service routine to call when a packet is sent. + \param func ISR to call. + */ + void setPacketSentAction(void (*func)(void)) override; + + /*! + \brief Clears interrupt service routine to call when a packet is sent. + */ + void clearPacketSentAction() override; + + /*! + \brief Interrupt-driven binary transmit method. IRQ will be activated when full packet is transmitted. + Overloads for string-based transmissions are implemented in PhysicalLayer. + \param data Binary data to be sent. + \param len Number of bytes to send. + \param addr Dummy address parameter, to ensure PhysicalLayer compatibility. + \returns \ref status_codes + */ + int16_t startTransmit(const uint8_t* data, size_t len, uint8_t addr) override; + + /*! + \brief Clean up after transmission is done. + \returns \ref status_codes + */ + int16_t finishTransmit() override; + + /*! + \brief Interrupt-driven receive method. IRQ will be activated when full packet is received. + \returns \ref status_codes + */ + int16_t startReceive() override; + + /*! + \brief Interrupt-driven receive method, implemented for compatibility with PhysicalLayer. + \param timeout Ignored. + \param irqFlags Ignored. + \param irqMask Ignored. + \param len Ignored. + \returns \ref status_codes + */ + int16_t startReceive(uint32_t timeout, uint32_t irqFlags, uint32_t irqMask, size_t len) override; + + /*! + \brief Reads data received after calling startReceive method. When the packet length is not known in advance, + getPacketLength method must be called BEFORE calling readData! + \param data Pointer to array to save the received binary data. + \param len Number of bytes that will be received. Must be known in advance for binary transmissions. + \returns \ref status_codes + */ + int16_t readData(uint8_t* data, size_t len) override; + + // configuration methods + + /*! + \brief Sets carrier frequency. Allowed values range from 2400 MHz to 2525 MHz. + \param freq Carrier frequency to be set in MHz. + \returns \ref status_codes + */ + int16_t setFrequency(float freq) override; + + /*! + \brief Sets bit rate. Allowed values are 2000, 1000 or 250 kbps. + \param br Bit rate to be set in kbps. + \returns \ref status_codes + */ + int16_t setBitRate(float br) override; + + /*! + \brief Sets output power. Allowed values are -18, -12, -6 or 0 dBm. + \param pwr Output power to be set in dBm. + \returns \ref status_codes + */ + int16_t setOutputPower(int8_t pwr) override; + + /*! + \brief Sets address width of transmit and receive pipes in bytes. Allowed values are 3, 4 or 5 bytes. + \param addrWidth Address width to be set in bytes. + \returns \ref status_codes + */ + int16_t setAddressWidth(uint8_t addrWidth); + + /*! + \brief Sets address of transmit pipe. The address width must be the same as the same + as the configured in setAddressWidth. + \param addr Address to which the next packet shall be transmitted. + \returns \ref status_codes + */ + int16_t setTransmitPipe(uint8_t* addr); + + /*! + \brief Sets address of receive pipes 0 or 1. The address width must be the same as the same + as the configured in setAddressWidth. + \param pipeNum Number of pipe to which the address shall be set. Either 0 or 1, + other pipes are handled using overloaded method. + \param addr Address from which %nRF24 shall receive new packets on the specified pipe. + \returns \ref status_codes + */ + int16_t setReceivePipe(uint8_t pipeNum, uint8_t* addr); + + /*! + \brief Sets address of receive pipes 2 - 5. The first 2 - 4 address bytes for these pipes + are the same as for address pipe 1, only the last byte can be set. + \param pipeNum Number of pipe to which the address shall be set. Allowed values range from 2 to 5. + \param addrByte LSB of address from which %nRF24 shall receive new packets on the specified pipe. + \returns \ref status_codes + */ + int16_t setReceivePipe(uint8_t pipeNum, uint8_t addrByte); + + /*! + \brief Disables specified receive pipe. + \param pipeNum Receive pipe to be disabled. + \returns \ref status_codes + */ + int16_t disablePipe(uint8_t pipeNum); + + /*! + \brief Gets nRF24 status register. + \param mask Bit mask to be used on the returned register value. + \returns Status register value or \ref status_codes + */ + int16_t getStatus(uint8_t mask = 0xFF); + + /*! + \brief Checks if carrier was detected during last RX + \returns Whatever the carrier was above threshold. + */ + bool isCarrierDetected(); + + /*! + \brief Dummy configuration method, to ensure PhysicalLayer compatibility. + \param freqDev Dummy frequency deviation parameter, no configuration will be changed. + \returns \ref status_codes + */ + int16_t setFrequencyDeviation(float freqDev) override; + + /*! + \brief Query modem for the packet length of received payload. + \param update Update received packet length. Will return cached value when set to false. + \returns Length of last received packet in bytes. + */ + size_t getPacketLength(bool update = true) override; + + /*! + \brief Enable CRC filtering and generation. + \param crcOn Set or unset CRC check. + \returns \ref status_codes + */ + int16_t setCrcFiltering(bool crcOn = true); + + /*! + \brief Enable or disable auto-acknowledge packets on all pipes. + \param autoAckOn Enable (true) or disable (false) auto-acks. + \returns \ref status_codes + */ + int16_t setAutoAck(bool autoAckOn = true); + + /*! + \brief Enable or disable auto-acknowledge packets on given pipe. + \param pipeNum Number of pipe to which enable / disable auto-acks. + \param autoAckOn Enable (true) or disable (false) auto-acks. + \returns \ref status_codes + */ + int16_t setAutoAck(uint8_t pipeNum, bool autoAckOn); + + /*! + \brief Dummy data shaping configuration method, to ensure PhysicalLayer compatibility. + \param sh Ignored. + \returns \ref status_codes + */ + int16_t setDataShaping(uint8_t sh) override; + + /*! + \brief Dummy encoding configuration method, to ensure PhysicalLayer compatibility. + \param encoding Ignored. + \returns \ref status_codes + */ + int16_t setEncoding(uint8_t encoding) override; + +#if !RADIOLIB_GODMODE && !RADIOLIB_LOW_LEVEL + protected: +#endif + Module* getMod() override; + + void SPIreadRxPayload(uint8_t* data, uint8_t numBytes); + void SPIwriteTxPayload(uint8_t* data, uint8_t numBytes); + void SPItransfer(uint8_t cmd, bool write = false, uint8_t* dataOut = NULL, uint8_t* dataIn = NULL, uint8_t numBytes = 0); + +#if !RADIOLIB_GODMODE + private: +#endif + Module* mod; + + int16_t frequency = RADIOLIB_NRF24_DEFAULT_FREQ; + int16_t dataRate = RADIOLIB_NRF24_DEFAULT_DR; + int8_t power = RADIOLIB_NRF24_DEFAULT_POWER; + uint8_t addressWidth = RADIOLIB_NRF24_DEFAULT_ADDRWIDTH; + + int16_t config(); + void clearIRQ(); +}; + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/protocols/AFSK/AFSK.cpp b/software/firmware/source/libraries/RadioLib/src/protocols/AFSK/AFSK.cpp new file mode 100644 index 000000000..bf0ddc4c2 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/protocols/AFSK/AFSK.cpp @@ -0,0 +1,42 @@ +#include "AFSK.h" +#if !RADIOLIB_EXCLUDE_AFSK + +AFSKClient::AFSKClient(PhysicalLayer* phy, uint32_t pin): outPin(pin) { + phyLayer = phy; +} + +AFSKClient::AFSKClient(AFSKClient* aud) { + phyLayer = aud->phyLayer; + outPin = aud->outPin; +} + +int16_t AFSKClient::begin() { + return(phyLayer->startDirect()); +} + +int16_t AFSKClient::tone(uint16_t freq, bool autoStart) { + if(freq == 0) { + return(RADIOLIB_ERR_INVALID_FREQUENCY); + } + + if(autoStart) { + int16_t state = phyLayer->transmitDirect(); + RADIOLIB_ASSERT(state); + } + + Module* mod = phyLayer->getMod(); + mod->hal->tone(outPin, freq); + return(RADIOLIB_ERR_NONE); +} + +int16_t AFSKClient::noTone(bool keepOn) { + Module* mod = phyLayer->getMod(); + mod->hal->noTone(outPin); + if(keepOn) { + return(0); + } + + return(phyLayer->standby()); +} + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/protocols/AFSK/AFSK.h b/software/firmware/source/libraries/RadioLib/src/protocols/AFSK/AFSK.h new file mode 100644 index 000000000..e38b5b769 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/protocols/AFSK/AFSK.h @@ -0,0 +1,70 @@ +#if !defined(_RADIOLIB_AFSK_H) +#define _RADIOLIB_AFSK_H + +#include "../../TypeDef.h" + +#if !RADIOLIB_EXCLUDE_AFSK + +#include "../../Module.h" + +#include "../PhysicalLayer/PhysicalLayer.h" + +/*! + \class AFSKClient + \brief Client for audio-based transmissions. Requires Arduino tone() function, and a module capable of direct mode transmission using DIO pins. +*/ +class AFSKClient { + public: + /*! + \brief Default contructor. + \param phy Pointer to the wireless module providing PhysicalLayer communication. + \param pin The pin that will be used for audio output. + */ + AFSKClient(PhysicalLayer* phy, uint32_t pin); + + /*! + \brief Copy contructor. + \param aud Pointer to the AFSKClient instance to copy. + */ + explicit AFSKClient(AFSKClient* aud); + + /*! + \brief Initialization method. + \returns \ref status_codes + */ + int16_t begin(); + + /*! + \brief Start transmitting audio tone. + \param freq Frequency of the tone in Hz. + \param autoStart Whether to automatically enter transmission mode. Defaults to true. + \returns \ref status_codes + */ + int16_t tone(uint16_t freq, bool autoStart = true); + + /*! + \brief Stops transmitting audio tone. + \param keepOn Keep transmitter on - this may limit noise when switching transmitter on or off. + \returns \ref status_codes + */ + int16_t noTone(bool keepOn = false); + +#if !RADIOLIB_GODMODE + private: +#endif + PhysicalLayer* phyLayer; + uint32_t outPin; + + // allow specific classes access the private PhysicalLayer pointer + friend class RTTYClient; + friend class MorseClient; + friend class HellClient; + friend class SSTVClient; + friend class AX25Client; + friend class FSK4Client; + friend class BellClient; +}; + +#endif + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/protocols/APRS/APRS.cpp b/software/firmware/source/libraries/RadioLib/src/protocols/APRS/APRS.cpp new file mode 100644 index 000000000..c2fb72f3e --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/protocols/APRS/APRS.cpp @@ -0,0 +1,297 @@ +#include "APRS.h" +#include +#include +#include +#if !RADIOLIB_EXCLUDE_APRS + +APRSClient::APRSClient(AX25Client* ax) { + axClient = ax; + phyLayer = nullptr; +} + +APRSClient::APRSClient(PhysicalLayer* phy) { + axClient = nullptr; + phyLayer = phy; +} + +int16_t APRSClient::begin(char sym, char* callsign, uint8_t ssid, bool alt) { + RADIOLIB_CHECK_RANGE(sym, ' ', '}', RADIOLIB_ERR_INVALID_SYMBOL); + symbol = sym; + + if(alt) { + table = '\\'; + } else { + table = '/'; + } + + // callsign is only processed for LoRa APRS + if(!callsign) { + return(RADIOLIB_ERR_NONE); + } + + if(strlen(callsign) > RADIOLIB_AX25_MAX_CALLSIGN_LEN) { + return(RADIOLIB_ERR_INVALID_CALLSIGN); + } + + memcpy(this->src, callsign, strlen(callsign)); + this->id = ssid; + + return(RADIOLIB_ERR_NONE); +} + +int16_t APRSClient::sendPosition(char* destCallsign, uint8_t destSSID, char* lat, char* lon, char* msg, char* time) { + size_t len = 1 + strlen(lat) + 1 + strlen(lon); + if(msg != NULL) { + len += 1 + strlen(msg); + } + if(time != NULL) { + len += strlen(time); + } + #if !RADIOLIB_STATIC_ONLY + char* info = new char[len + 2]; + #else + char info[RADIOLIB_STATIC_ARRAY_SIZE]; + #endif + + // build the info field + if(msg != NULL) { + if(time != NULL) { + // timestamp and message + sprintf(info, RADIOLIB_APRS_DATA_TYPE_POSITION_TIME_MSG "%s%s%c%s%c%s", time, lat, table, lon, symbol, msg); + } else { + // message, no timestamp + sprintf(info, RADIOLIB_APRS_DATA_TYPE_POSITION_NO_TIME_MSG "%s%c%s%c%s", lat, table, lon, symbol, msg); + } + + } else { + if(time != NULL) { + // timestamp, no message + sprintf(info, RADIOLIB_APRS_DATA_TYPE_POSITION_TIME_NO_MSG "%s%s%c%s%c", time, lat, table, lon, symbol); + } else { + // no message, no timestamp + sprintf(info, RADIOLIB_APRS_DATA_TYPE_POSITION_NO_TIME_NO_MSG "%s%c%s%c", lat, table, lon, symbol); + } + + } + + // send the frame + info[len] = '\0'; + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("APRS Info: %s, length = %d", info, (int)len); + int16_t state = sendFrame(destCallsign, destSSID, info); + #if !RADIOLIB_STATIC_ONLY + delete[] info; + #endif + return(state); +} + +int16_t APRSClient::sendMicE(float lat, float lon, uint16_t heading, uint16_t speed, uint8_t type, uint8_t* telem, size_t telemLen, char* grid, char* status, int32_t alt) { + // sanity checks first + if(((telemLen == 0) && (telem != NULL)) || ((telemLen != 0) && (telem == NULL))) { + return(RADIOLIB_ERR_INVALID_MIC_E_TELEMETRY); + } + + if((telemLen != 0) && (telemLen != 2) && (telemLen != 5)) { + return(RADIOLIB_ERR_INVALID_MIC_E_TELEMETRY_LENGTH); + } + + if((telemLen > 0) && ((grid != NULL) || (status != NULL) || (alt != RADIOLIB_APRS_MIC_E_ALTITUDE_UNUSED))) { + // can't have both telemetry and status + return(RADIOLIB_ERR_MIC_E_TELEMETRY_STATUS); + } + + // prepare buffers + char destCallsign[7]; + #if !RADIOLIB_STATIC_ONLY + size_t infoLen = 10; + if(telemLen > 0) { + infoLen += 1 + telemLen; + } else { + if(grid != NULL) { + infoLen += strlen(grid) + 2; + } + if(status != NULL) { + infoLen += strlen(status); + } + if(alt > RADIOLIB_APRS_MIC_E_ALTITUDE_UNUSED) { + infoLen += 4; + } + } + char* info = new char[infoLen]; + #else + char info[RADIOLIB_STATIC_ARRAY_SIZE]; + #endif + size_t infoPos = 0; + + // the following is based on APRS Mic-E implementation by https://github.com/omegat + // as discussed in https://github.com/jgromes/RadioLib/issues/430 + + // latitude first, because that is in the destination field + float lat_abs = RADIOLIB_ABS(lat); + int lat_deg = (int)lat_abs; + int lat_min = (lat_abs - (float)lat_deg) * 60.0f; + int lat_hun = (((lat_abs - (float)lat_deg) * 60.0f) - lat_min) * 100.0f; + destCallsign[0] = lat_deg/10; + destCallsign[1] = lat_deg%10; + destCallsign[2] = lat_min/10; + destCallsign[3] = lat_min%10; + destCallsign[4] = lat_hun/10; + destCallsign[5] = lat_hun%10; + + // next, add the extra bits + if(type & 0x04) { destCallsign[0] += RADIOLIB_APRS_MIC_E_DEST_BIT_OFFSET; } + if(type & 0x02) { destCallsign[1] += RADIOLIB_APRS_MIC_E_DEST_BIT_OFFSET; } + if(type & 0x01) { destCallsign[2] += RADIOLIB_APRS_MIC_E_DEST_BIT_OFFSET; } + if(lat >= 0) { destCallsign[3] += RADIOLIB_APRS_MIC_E_DEST_BIT_OFFSET; } + if(lon >= 100 || lon <= -100) { destCallsign[4] += RADIOLIB_APRS_MIC_E_DEST_BIT_OFFSET; } + if(lon < 0) { destCallsign[5] += RADIOLIB_APRS_MIC_E_DEST_BIT_OFFSET; } + destCallsign[6] = '\0'; + + // now convert to Mic-E characters to get the "callsign" + for(uint8_t i = 0; i < 6; i++) { + if(destCallsign[i] <= 9) { + destCallsign[i] += '0'; + } else { + destCallsign[i] += ('A' - 10); + } + } + + // setup the information field + info[infoPos++] = RADIOLIB_APRS_MIC_E_GPS_DATA_CURRENT; + + // encode the longtitude + float lon_abs = RADIOLIB_ABS(lon); + int32_t lon_deg = (int32_t)lon_abs; + int32_t lon_min = (lon_abs - (float)lon_deg) * 60.0f; + int32_t lon_hun = (((lon_abs - (float)lon_deg) * 60.0f) - lon_min) * 100.0f; + + if(lon_deg <= 9) { + info[infoPos++] = lon_deg + 118; + } else if(lon_deg <= 99) { + info[infoPos++] = lon_deg + 28; + } else if(lon_deg <= 109) { + info[infoPos++] = lon_deg + 8; + } else { + info[infoPos++] = lon_deg - 72; + } + + if(lon_min <= 9){ + info[infoPos++] = lon_min + 88; + } else { + info[infoPos++] = lon_min + 28; + } + + info[infoPos++] = lon_hun + 28; + + // now the speed and heading - this gets really weird + int32_t speed_hun_ten = speed/10; + int32_t speed_uni = speed%10; + int32_t head_hun = heading/100; + int32_t head_ten_uni = heading%100; + + if(speed <= 199) { + info[infoPos++] = speed_hun_ten + 'l'; + } else { + info[infoPos++] = speed_hun_ten + '0'; + } + + info[infoPos++] = speed_uni*10 + head_hun + 32; + info[infoPos++] = head_ten_uni + 28; + info[infoPos++] = symbol; + info[infoPos++] = table; + + // onto the optional stuff - check telemetry first + if(telemLen > 0) { + if(telemLen == 2) { + info[infoPos++] = RADIOLIB_APRS_MIC_E_TELEMETRY_LEN_2; + } else { + info[infoPos++] = RADIOLIB_APRS_MIC_E_TELEMETRY_LEN_5; + } + for(uint8_t i = 0; i < telemLen; i++) { + sprintf(&(info[infoPos]), "%02X", telem[i]); + infoPos += 2; + } + + } else { + if(grid != NULL) { + memcpy(&(info[infoPos]), grid, strlen(grid)); + infoPos += strlen(grid); + info[infoPos++] = '/'; + info[infoPos++] = 'G'; + } + if(status != NULL) { + info[infoPos++] = ' '; + memcpy(&(info[infoPos]), status, strlen(status)); + infoPos += strlen(status); + } + if(alt > RADIOLIB_APRS_MIC_E_ALTITUDE_UNUSED) { + // altitude is offset by -10 km + int32_t alt_val = alt + 10000; + + // ... and encoded in base 91 for some reason + info[infoPos++] = (alt_val / 8281) + 33; + info[infoPos++] = ((alt_val % 8281) / 91) + 33; + info[infoPos++] = ((alt_val % 8281) % 91) + 33; + info[infoPos++] = '}'; + } + } + info[infoPos++] = '\0'; + + // send the frame + uint8_t destSSID = 0; + if(this->phyLayer != nullptr) { + // TODO make SSID configurable? + destSSID = 1; + } + int16_t state = sendFrame(destCallsign, destSSID, info); + #if !RADIOLIB_STATIC_ONLY + delete[] info; + #endif + return(state); +} + +int16_t APRSClient::sendFrame(char* destCallsign, uint8_t destSSID, char* info) { + // encoding depends on whether AX.25 should be used or not + if(this->axClient != nullptr) { + // AX.25/classical mode, get AX.25 callsign + char srcCallsign[RADIOLIB_AX25_MAX_CALLSIGN_LEN + 1]; + axClient->getCallsign(srcCallsign); + + AX25Frame frameUI(destCallsign, destSSID, srcCallsign, axClient->getSSID(), + RADIOLIB_AX25_CONTROL_UNNUMBERED_FRAME, + RADIOLIB_AX25_PID_NO_LAYER_3, const_cast(info)); + + // optionally set repeaters + if(this->repCalls && this->repSSIDs && this->numReps) { + int16_t state = frameUI.setRepeaters(this->repCalls, this->repSSIDs, this->numReps); + RADIOLIB_ASSERT(state); + } + + return(axClient->sendFrame(&frameUI)); + + } else if(this->phyLayer != nullptr) { + // non-AX.25/LoRa mode + size_t len = RADIOLIB_APRS_LORA_HEADER_LEN + strlen(this->src) + 4 + strlen(destCallsign) + 11 + strlen(info); + char* buff = new char[len]; + snprintf(buff, len, RADIOLIB_APRS_LORA_HEADER "%s-%d>%s,WIDE%d-%d:%s", this->src, this->id, destCallsign, destSSID, destSSID, info); + + int16_t res = this->phyLayer->transmit(reinterpret_cast(buff), strlen(buff)); + delete[] buff; + return(res); + } + + return(RADIOLIB_ERR_WRONG_MODEM); +} + +void APRSClient::useRepeaters(char** repeaterCallsigns, uint8_t* repeaterSSIDs, uint8_t numRepeaters) { + this->repCalls = repeaterCallsigns; + this->repSSIDs = repeaterSSIDs; + this->numReps = numRepeaters; +} + +void APRSClient::dropRepeaters() { + this->repCalls = NULL; + this->repSSIDs = NULL; + this->numReps = 0; +} + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/protocols/APRS/APRS.h b/software/firmware/source/libraries/RadioLib/src/protocols/APRS/APRS.h new file mode 100644 index 000000000..44031fdea --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/protocols/APRS/APRS.h @@ -0,0 +1,184 @@ +#if !defined(_RADIOLIB_APRS_H) +#define _RADIOLIB_APRS_H + +#include "../../TypeDef.h" + +#if !RADIOLIB_EXCLUDE_APRS + +#include "../PhysicalLayer/PhysicalLayer.h" +#include "../AX25/AX25.h" + +// APRS data type identifiers +#define RADIOLIB_APRS_DATA_TYPE_POSITION_NO_TIME_NO_MSG "!" +#define RADIOLIB_APRS_DATA_TYPE_GPS_RAW "$" +#define RADIOLIB_APRS_DATA_TYPE_ITEM ")" +#define RADIOLIB_APRS_DATA_TYPE_TEST "," +#define RADIOLIB_APRS_DATA_TYPE_POSITION_TIME_NO_MSG "/" +#define RADIOLIB_APRS_DATA_TYPE_MSG ":" +#define RADIOLIB_APRS_DATA_TYPE_OBJECT ";" +#define RADIOLIB_APRS_DATA_TYPE_STATION_CAPABILITES "<" +#define RADIOLIB_APRS_DATA_TYPE_POSITION_NO_TIME_MSG "=" +#define RADIOLIB_APRS_DATA_TYPE_STATUS ">" +#define RADIOLIB_APRS_DATA_TYPE_QUERY "?" +#define RADIOLIB_APRS_DATA_TYPE_POSITION_TIME_MSG "@" +#define RADIOLIB_APRS_DATA_TYPE_TELEMETRY "T" +#define RADIOLIB_APRS_DATA_TYPE_MAIDENHEAD_BEACON "[" +#define RADIOLIB_APRS_DATA_TYPE_WEATHER_REPORT "_" +#define RADIOLIB_APRS_DATA_TYPE_USER_DEFINED "{" +#define RADIOLIB_APRS_DATA_TYPE_THIRD_PARTY "}" + +/*! + \defgroup mic_e_message_types Mic-E message types. + \{ +*/ + +/*! \brief Mic-E "Off duty" message. */ +#define RADIOLIB_APRS_MIC_E_TYPE_OFF_DUTY 0b00000111 + +/*! \brief Mic-E "En route" message. */ +#define RADIOLIB_APRS_MIC_E_TYPE_EN_ROUTE 0b00000110 + +/*! \brief Mic-E "In service" message. */ +#define RADIOLIB_APRS_MIC_E_TYPE_IN_SERVICE 0b00000101 + +/*! \brief Mic-E "Returning" message. */ +#define RADIOLIB_APRS_MIC_E_TYPE_RETURNING 0b00000100 + +/*! \brief Mic-E "Commited" message. */ +#define RADIOLIB_APRS_MIC_E_TYPE_COMMITTED 0b00000011 + +/*! \brief Mic-E special message. */ +#define RADIOLIB_APRS_MIC_E_TYPE_SPECIAL 0b00000010 + +/*! \brief Mic-E priority message. */ +#define RADIOLIB_APRS_MIC_E_TYPE_PRIORITY 0b00000001 + +/*! \brief Mic-E emergency message. */ +#define RADIOLIB_APRS_MIC_E_TYPE_EMERGENCY 0b00000000 + +/*! + \} +*/ + +// magic offset applied to encode extra bits in the Mic-E destination field +#define RADIOLIB_APRS_MIC_E_DEST_BIT_OFFSET 25 + +// Mic-E data types +#define RADIOLIB_APRS_MIC_E_GPS_DATA_CURRENT '`' +#define RADIOLIB_APRS_MIC_E_GPS_DATA_OLD '\'' + +// Mic-E telemetry flags +#define RADIOLIB_APRS_MIC_E_TELEMETRY_LEN_2 '`' +#define RADIOLIB_APRS_MIC_E_TELEMETRY_LEN_5 '\'' + +// alias for unused altitude in Mic-E +#define RADIOLIB_APRS_MIC_E_ALTITUDE_UNUSED -1000000 + +// special header applied for APRS over LoRa +#define RADIOLIB_APRS_LORA_HEADER "<\xff\x01" +#define RADIOLIB_APRS_LORA_HEADER_LEN (3) + +/*! + \class APRSClient + \brief Client for APRS communication. +*/ +class APRSClient { + public: + /*! + \brief Constructor for "classic" mode using AX.25/AFSK. + \param ax Pointer to the instance of AX25Client to be used for APRS. + */ + explicit APRSClient(AX25Client* ax); + + /*! + \brief Constructor for LoRa mode. + \param phy Pointer to the wireless module providing PhysicalLayer communication. + */ + explicit APRSClient(PhysicalLayer* phy); + + // basic methods + + /*! + \brief Initialization method. + \param sym APRS symbol to be displayed. + \param callsign Source callsign. Required and only used for APRS over LoRa, ignored in classic mode. + \param ssid Source SSID. Only used for APRS over LoRa, ignored in classic mode, defaults to 0. + \param alt Whether to use the primary (false) or alternate (true) symbol table. Defaults to primary table. + \returns \ref status_codes + */ + int16_t begin(char sym, char* callsign = NULL, uint8_t ssid = 0, bool alt = false); + + /*! + \brief Transmit position. + \param destCallsign Destination station callsign. + \param destSSID Destination station SSID. + \param lat Latitude as a null-terminated string. + \param lon Longitude as a null-terminated string. + \param msg Message to be transmitted. Defaults to NULL (no message). + \param time Position timestamp. Defaults to NULL (no timestamp). + \returns \ref status_codes + */ + int16_t sendPosition(char* destCallsign, uint8_t destSSID, char* lat, char* lon, char* msg = NULL, char* time = NULL); + + /*! + \brief Transmit position using Mic-E encoding. + \param lat Geographical latitude, positive for north, negative for south. + \param lon Geographical longitude, positive for east, negative for west. + \param heading Heading in degrees. + \param speed Speed in knots. + \param type Mic-E message type - see \ref mic_e_message_types. + \param telem Pointer to telemetry array (either 2 or 5 bytes long). NULL when telemetry is not used. + \param telemLen Telemetry length, 2 or 5. 0 when telemetry is not used. + \param grid Maidenhead grid locator. NULL when not used. + \param status Status message to send. NULL when not used. + \param alt Altitude to send. RADIOLIB_APRS_MIC_E_ALTITUDE_UNUSED when not used. + */ + int16_t sendMicE(float lat, float lon, uint16_t heading, uint16_t speed, uint8_t type, uint8_t* telem = NULL, size_t telemLen = 0, char* grid = NULL, char* status = NULL, int32_t alt = RADIOLIB_APRS_MIC_E_ALTITUDE_UNUSED); + + /*! + \brief Transmit generic APRS frame. + \param destCallsign Destination station callsign. + \param destSSID Destination station SSID. + \param info AX.25 info field contents. + \returns \ref status_codes + */ + int16_t sendFrame(char* destCallsign, uint8_t destSSID, char* info); + + /*! + \brief Set the repeater callsigns and SSIDs to be used by the frames sent by sendPosition, sendMicE or sendFrame. + \param repeaterCallsigns Array of repeater callsigns in the form of null-terminated C-strings. + \param repeaterSSIDs Array of repeater SSIDs. + \param numRepeaters Number of repeaters, maximum is 8. + \returns \ref status_codes + */ + void useRepeaters(char** repeaterCallsigns, uint8_t* repeaterSSIDs, uint8_t numRepeaters); + + /*! + \brief Stop using repeaters. + \returns \ref status_codes + */ + void dropRepeaters(); + +#if !RADIOLIB_GODMODE + private: +#endif + AX25Client* axClient; + PhysicalLayer* phyLayer; + + // default APRS symbol (car) + char symbol = '>'; + char table = '/'; + + // repeaters + char** repCalls = NULL; + uint8_t* repSSIDs = NULL; + uint8_t numReps = 0; + + // source callsign when using APRS over LoRa + char src[RADIOLIB_AX25_MAX_CALLSIGN_LEN + 1] = { 0 }; + uint8_t id = 0; +}; + +#endif + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/protocols/AX25/AX25.cpp b/software/firmware/source/libraries/RadioLib/src/protocols/AX25/AX25.cpp new file mode 100644 index 000000000..22e1220a2 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/protocols/AX25/AX25.cpp @@ -0,0 +1,513 @@ +#include "AX25.h" + +#include + +#if !RADIOLIB_EXCLUDE_AX25 + +AX25Frame::AX25Frame(const char* destCallsign, uint8_t destSSID, const char* srcCallsign, uint8_t srcSSID, uint8_t control) +: AX25Frame(destCallsign, destSSID, srcCallsign, srcSSID, control, 0, NULL, 0) { + +} + +AX25Frame::AX25Frame(const char* destCallsign, uint8_t destSSID, const char* srcCallsign, uint8_t srcSSID, uint8_t control, uint8_t protocolID, const char* info) + : AX25Frame(destCallsign, destSSID, srcCallsign, srcSSID, control, protocolID, reinterpret_cast(const_cast(info)), strlen(info)) { + +} + +AX25Frame::AX25Frame(const char* destCallsign, uint8_t destSSID, const char* srcCallsign, uint8_t srcSSID, uint8_t control, uint8_t protocolID, uint8_t* info, uint16_t infoLen) { + // destination callsign/SSID + memcpy(this->destCallsign, destCallsign, strlen(destCallsign)); + this->destCallsign[strlen(destCallsign)] = '\0'; + this->destSSID = destSSID; + + // source callsign/SSID + memcpy(this->srcCallsign, srcCallsign, strlen(srcCallsign)); + this->srcCallsign[strlen(srcCallsign)] = '\0'; + this->srcSSID = srcSSID; + + // set repeaters + this->numRepeaters = 0; + #if !RADIOLIB_STATIC_ONLY + this->repeaterCallsigns = NULL; + this->repeaterSSIDs = NULL; + #endif + + // control field + this->control = control; + + // sequence numbers + this->rcvSeqNumber = 0; + this->sendSeqNumber = 0; + + // PID field + this->protocolID = protocolID; + + // info field + this->infoLen = infoLen; + if(infoLen > 0) { + #if !RADIOLIB_STATIC_ONLY + this->info = new uint8_t[infoLen]; + #endif + memcpy(this->info, info, infoLen); + } +} + +AX25Frame::AX25Frame(const AX25Frame& frame) + : destSSID(frame.destSSID), + srcSSID(frame.srcSSID), + numRepeaters(frame.numRepeaters), + control(frame.control), + protocolID(frame.protocolID), + infoLen(frame.infoLen), + rcvSeqNumber(frame.rcvSeqNumber), + sendSeqNumber(frame.sendSeqNumber) { + strncpy(this->destCallsign, frame.destCallsign, RADIOLIB_AX25_MAX_CALLSIGN_LEN + 1); + strncpy(this->srcCallsign, frame.srcCallsign, RADIOLIB_AX25_MAX_CALLSIGN_LEN + 1); + + if(frame.infoLen) { + #if !RADIOLIB_STATIC_ONLY + this->info = new uint8_t[frame.infoLen]; + #endif + memcpy(this->info, frame.info, frame.infoLen); + } + + if(frame.numRepeaters) { + #if !RADIOLIB_STATIC_ONLY + this->repeaterCallsigns = new char*[frame.numRepeaters]; + for(uint8_t i = 0; i < frame.numRepeaters; i++) { + this->repeaterCallsigns[i] = new char[strlen(frame.repeaterCallsigns[i]) + 1]; + } + this->repeaterSSIDs = new uint8_t[frame.numRepeaters]; + #endif + + this->numRepeaters = frame.numRepeaters; + for(uint8_t i = 0; i < frame.numRepeaters; i++) { + memcpy(this->repeaterCallsigns[i], frame.repeaterCallsigns[i], strlen(frame.repeaterCallsigns[i])); + this->repeaterCallsigns[i][strlen(frame.repeaterCallsigns[i])] = '\0'; + } + memcpy(this->repeaterSSIDs, frame.repeaterSSIDs, frame.numRepeaters); + } +} + +AX25Frame::~AX25Frame() { + #if !RADIOLIB_STATIC_ONLY + // deallocate info field + if(infoLen > 0) { + delete[] this->info; + } + + // deallocate repeaters + if(this->numRepeaters > 0) { + for(uint8_t i = 0; i < this->numRepeaters; i++) { + delete[] this->repeaterCallsigns[i]; + } + delete[] this->repeaterCallsigns; + delete[] this->repeaterSSIDs; + } + #endif +} + +AX25Frame& AX25Frame::operator=(const AX25Frame& frame) { + // destination callsign/SSID + memcpy(this->destCallsign, frame.destCallsign, strlen(frame.destCallsign)); + this->destCallsign[strlen(frame.destCallsign)] = '\0'; + this->destSSID = frame.destSSID; + + // source callsign/SSID + memcpy(this->srcCallsign, frame.srcCallsign, strlen(frame.srcCallsign)); + this->srcCallsign[strlen(frame.srcCallsign)] = '\0'; + this->srcSSID = frame.srcSSID; + + // repeaters + this->numRepeaters = frame.numRepeaters; + for(uint8_t i = 0; i < this->numRepeaters; i++) { + memcpy(this->repeaterCallsigns[i], frame.repeaterCallsigns[i], strlen(frame.repeaterCallsigns[i])); + } + memcpy(this->repeaterSSIDs, frame.repeaterSSIDs, this->numRepeaters); + + // control field + this->control = frame.control; + + // sequence numbers + this->rcvSeqNumber = frame.rcvSeqNumber; + this->sendSeqNumber = frame.sendSeqNumber; + + // PID field + this->protocolID = frame.protocolID; + + // info field + this->infoLen = frame.infoLen; + memcpy(this->info, frame.info, this->infoLen); + + return(*this); +} + +int16_t AX25Frame::setRepeaters(char** repeaterCallsigns, uint8_t* repeaterSSIDs, uint8_t numRepeaters) { + // check number of repeaters + if((numRepeaters < 1) || (numRepeaters > 8)) { + return(RADIOLIB_ERR_INVALID_NUM_REPEATERS); + } + + // check repeater configuration + if((repeaterCallsigns == NULL) || (repeaterSSIDs == NULL)) { + return(RADIOLIB_ERR_INVALID_NUM_REPEATERS); + } + for(uint16_t i = 0; i < numRepeaters; i++) { + if(strlen(repeaterCallsigns[i]) > RADIOLIB_AX25_MAX_CALLSIGN_LEN) { + return(RADIOLIB_ERR_INVALID_REPEATER_CALLSIGN); + } + } + + // create buffers + #if !RADIOLIB_STATIC_ONLY + this->repeaterCallsigns = new char*[numRepeaters]; + for(uint8_t i = 0; i < numRepeaters; i++) { + this->repeaterCallsigns[i] = new char[strlen(repeaterCallsigns[i]) + 1]; + } + this->repeaterSSIDs = new uint8_t[numRepeaters]; + #endif + + // copy data + this->numRepeaters = numRepeaters; + for(uint8_t i = 0; i < numRepeaters; i++) { + memcpy(this->repeaterCallsigns[i], repeaterCallsigns[i], strlen(repeaterCallsigns[i])); + this->repeaterCallsigns[i][strlen(repeaterCallsigns[i])] = '\0'; + } + memcpy(this->repeaterSSIDs, repeaterSSIDs, numRepeaters); + + return(RADIOLIB_ERR_NONE); +} + +void AX25Frame::setRecvSequence(uint8_t seqNumber) { + this->rcvSeqNumber = seqNumber; +} + +void AX25Frame::setSendSequence(uint8_t seqNumber) { + this->sendSeqNumber = seqNumber; +} + +AX25Client::AX25Client(PhysicalLayer* phy) { + phyLayer = phy; + #if !RADIOLIB_EXCLUDE_AFSK + audio = nullptr; + bellModem = nullptr; + #endif +} + +#if !RADIOLIB_EXCLUDE_AFSK +AX25Client::AX25Client(AFSKClient* aud) + : audio(aud) { + phyLayer = this->audio->phyLayer; + bellModem = new BellClient(this->audio); + bellModem->setModem(Bell202); +} + +AX25Client::AX25Client(const AX25Client& ax25) + : phyLayer(ax25.phyLayer), + sourceSSID(ax25.sourceSSID), + preambleLen(ax25.preambleLen) { + strncpy(sourceCallsign, ax25.sourceCallsign, RADIOLIB_AX25_MAX_CALLSIGN_LEN + 1); + #if !RADIOLIB_EXCLUDE_AFSK + if(ax25.bellModem) { + this->audio = ax25.audio; + this->bellModem = new BellClient(ax25.audio); + } + #endif +} + +AX25Client& AX25Client::operator=(const AX25Client& ax25) { + if(&ax25 != this) { + this->phyLayer = ax25.phyLayer; + this->sourceSSID = ax25.sourceSSID; + this->preambleLen = ax25.preambleLen; + strncpy(sourceCallsign, ax25.sourceCallsign, RADIOLIB_AX25_MAX_CALLSIGN_LEN + 1); + #if !RADIOLIB_EXCLUDE_AFSK + if(ax25.bellModem) { + this->audio = ax25.audio; + this->bellModem = new BellClient(ax25.audio); + } + #endif + } + return(*this); +} + +int16_t AX25Client::setCorrection(int16_t mark, int16_t space, float length) { + BellModem_t modem; + modem.freqMark = Bell202.freqMark + mark; + modem.freqSpace = Bell202.freqSpace + space; + modem.freqMarkReply = modem.freqMark; + modem.freqSpaceReply = modem.freqSpace; + modem.baudRate = length*(float)Bell202.baudRate; + bellModem->setModem(modem); + return(RADIOLIB_ERR_NONE); +} +#endif + +int16_t AX25Client::begin(const char* srcCallsign, uint8_t srcSSID, uint8_t preLen) { + // set source SSID + sourceSSID = srcSSID; + + // check source callsign length (6 characters max) + if(strlen(srcCallsign) > RADIOLIB_AX25_MAX_CALLSIGN_LEN) { + return(RADIOLIB_ERR_INVALID_CALLSIGN); + } + + // copy callsign + memcpy(sourceCallsign, srcCallsign, strlen(srcCallsign)); + sourceCallsign[strlen(srcCallsign)] = '\0'; + + // save preamble length + preambleLen = preLen; + + // configure for direct mode + return(phyLayer->startDirect()); +} + +#if defined(RADIOLIB_BUILD_ARDUINO) +int16_t AX25Client::transmit(String& str, const char* destCallsign, uint8_t destSSID) { + return(transmit(str.c_str(), destCallsign, destSSID)); +} +#endif + +int16_t AX25Client::transmit(const char* str, const char* destCallsign, uint8_t destSSID) { + // create control field + uint8_t controlField = RADIOLIB_AX25_CONTROL_UNNUMBERED_FRAME; + + // build the frame + AX25Frame frame(destCallsign, destSSID, sourceCallsign, sourceSSID, controlField, + RADIOLIB_AX25_PID_NO_LAYER_3, + reinterpret_cast(const_cast(str)), strlen(str)); + + // send Unnumbered Information frame + return(sendFrame(&frame)); +} + +int16_t AX25Client::sendFrame(AX25Frame* frame) { + // check destination callsign length (6 characters max) + if(strlen(frame->destCallsign) > RADIOLIB_AX25_MAX_CALLSIGN_LEN) { + return(RADIOLIB_ERR_INVALID_CALLSIGN); + } + + // check repeater configuration + #if !RADIOLIB_STATIC_ONLY + if(!(((frame->repeaterCallsigns == NULL) && (frame->repeaterSSIDs == NULL) && (frame->numRepeaters == 0)) || + ((frame->repeaterCallsigns != NULL) && (frame->repeaterSSIDs != NULL) && (frame->numRepeaters != 0)))) { + return(RADIOLIB_ERR_INVALID_NUM_REPEATERS); + } + for(uint16_t i = 0; i < frame->numRepeaters; i++) { + if(strlen(frame->repeaterCallsigns[i]) > RADIOLIB_AX25_MAX_CALLSIGN_LEN) { + return(RADIOLIB_ERR_INVALID_REPEATER_CALLSIGN); + } + } + #endif + + // calculate frame length without FCS (destination address, source address, repeater addresses, control, PID, info) + size_t frameBuffLen = ((2 + frame->numRepeaters)*(RADIOLIB_AX25_MAX_CALLSIGN_LEN + 1)) + 1 + 1 + frame->infoLen; + // create frame buffer without preamble, start or stop flags + #if !RADIOLIB_STATIC_ONLY + uint8_t* frameBuff = new uint8_t[frameBuffLen + 2]; + #else + uint8_t frameBuff[RADIOLIB_STATIC_ARRAY_SIZE]; + #endif + uint8_t* frameBuffPtr = frameBuff; + + // set destination callsign - all address field bytes are shifted by one bit to make room for HDLC address extension bit + memset(frameBuffPtr, ' ' << 1, RADIOLIB_AX25_MAX_CALLSIGN_LEN); + for(size_t i = 0; i < strlen(frame->destCallsign); i++) { + *(frameBuffPtr + i) = frame->destCallsign[i] << 1; + } + frameBuffPtr += RADIOLIB_AX25_MAX_CALLSIGN_LEN; + + // set destination SSID + *(frameBuffPtr++) = RADIOLIB_AX25_SSID_RESPONSE_DEST | RADIOLIB_AX25_SSID_RESERVED_BITS | (frame->destSSID & 0x0F) << 1 | RADIOLIB_AX25_SSID_HDLC_EXTENSION_CONTINUE; + + // set source callsign - all address field bytes are shifted by one bit to make room for HDLC address extension bit + memset(frameBuffPtr, ' ' << 1, RADIOLIB_AX25_MAX_CALLSIGN_LEN); + for(size_t i = 0; i < strlen(frame->srcCallsign); i++) { + *(frameBuffPtr + i) = frame->srcCallsign[i] << 1; + } + frameBuffPtr += RADIOLIB_AX25_MAX_CALLSIGN_LEN; + + // set source SSID + *(frameBuffPtr++) = RADIOLIB_AX25_SSID_COMMAND_SOURCE | RADIOLIB_AX25_SSID_RESERVED_BITS | (frame->srcSSID & 0x0F) << 1 | RADIOLIB_AX25_SSID_HDLC_EXTENSION_CONTINUE; + + // set repeater callsigns + for(uint16_t i = 0; i < frame->numRepeaters; i++) { + memset(frameBuffPtr, ' ' << 1, RADIOLIB_AX25_MAX_CALLSIGN_LEN); + for(size_t j = 0; j < strlen(frame->repeaterCallsigns[i]); j++) { + *(frameBuffPtr + j) = frame->repeaterCallsigns[i][j] << 1; + } + frameBuffPtr += RADIOLIB_AX25_MAX_CALLSIGN_LEN; + *(frameBuffPtr++) = RADIOLIB_AX25_SSID_HAS_NOT_BEEN_REPEATED | RADIOLIB_AX25_SSID_RESERVED_BITS | (frame->repeaterSSIDs[i] & 0x0F) << 1 | RADIOLIB_AX25_SSID_HDLC_EXTENSION_CONTINUE; + } + + // set HDLC extension end bit + *(frameBuffPtr - 1) |= RADIOLIB_AX25_SSID_HDLC_EXTENSION_END; + + // set sequence numbers of the frames that have it + uint8_t controlField = frame->control; + if((frame->control & 0x01) == 0) { + // information frame, set both sequence numbers + controlField |= frame->rcvSeqNumber << 5; + controlField |= frame->sendSeqNumber << 1; + } else if((frame->control & 0x02) == 0) { + // supervisory frame, set only receive sequence number + controlField |= frame->rcvSeqNumber << 5; + } + + // set control field + *(frameBuffPtr++) = controlField; + + // set PID field of the frames that have it + if(frame->protocolID != 0x00) { + *(frameBuffPtr++) = frame->protocolID; + } + + // set info field of the frames that have it + if(frame->infoLen > 0) { + memcpy(frameBuffPtr, frame->info, frame->infoLen); + frameBuffPtr += frame->infoLen; + } + + // flip bit order + for(size_t i = 0; i < frameBuffLen; i++) { + frameBuff[i] = rlb_reflect(frameBuff[i], 8); + } + + // calculate + RadioLibCRCInstance.size = 16; + RadioLibCRCInstance.poly = RADIOLIB_CRC_CCITT_POLY; + RadioLibCRCInstance.init = RADIOLIB_CRC_CCITT_INIT; + RadioLibCRCInstance.out = RADIOLIB_CRC_CCITT_OUT; + RadioLibCRCInstance.refIn = false; + RadioLibCRCInstance.refOut = false; + uint16_t fcs = RadioLibCRCInstance.checksum(frameBuff, frameBuffLen); + *(frameBuffPtr++) = (uint8_t)((fcs >> 8) & 0xFF); + *(frameBuffPtr++) = (uint8_t)(fcs & 0xFF); + + // prepare buffer for the final frame (stuffed, with added preamble + flags and NRZI-encoded) + #if !RADIOLIB_STATIC_ONLY + // worst-case scenario: sequence of 1s, will have 120% of the original length, stuffed frame also includes both flags + uint8_t* stuffedFrameBuff = new uint8_t[preambleLen + 1 + (6*frameBuffLen)/5 + 2]; + #else + uint8_t stuffedFrameBuff[RADIOLIB_STATIC_ARRAY_SIZE]; + #endif + + // initialize buffer to all zeros + memset(stuffedFrameBuff, 0x00, preambleLen + 1 + (6*frameBuffLen)/5 + 2); + + // stuff bits (skip preamble and both flags) + uint16_t stuffedFrameBuffLenBits = 8*(preambleLen + 1); + uint8_t count = 0; + for(size_t i = 0; i < frameBuffLen + 2; i++) { + for(int8_t shift = 7; shift >= 0; shift--) { + uint16_t stuffedFrameBuffPos = stuffedFrameBuffLenBits + 7 - 2*(stuffedFrameBuffLenBits%8); + if((frameBuff[i] >> shift) & 0x01) { + // copy 1 and increment counter + SET_BIT_IN_ARRAY_MSB(stuffedFrameBuff, stuffedFrameBuffPos); + stuffedFrameBuffLenBits++; + count++; + + // check 5 consecutive 1s + if(count == 5) { + // get the new position in stuffed frame + stuffedFrameBuffPos = stuffedFrameBuffLenBits + 7 - 2*(stuffedFrameBuffLenBits%8); + + // insert 0 and reset counter + CLEAR_BIT_IN_ARRAY_MSB(stuffedFrameBuff, stuffedFrameBuffPos); + stuffedFrameBuffLenBits++; + count = 0; + } + + } else { + // copy 0 and reset counter + CLEAR_BIT_IN_ARRAY_MSB(stuffedFrameBuff, stuffedFrameBuffPos); + stuffedFrameBuffLenBits++; + count = 0; + } + + } + } + + // deallocate memory + #if !RADIOLIB_STATIC_ONLY + delete[] frameBuff; + #endif + + // set preamble bytes and start flag field + for(uint16_t i = 0; i < preambleLen + 1; i++) { + stuffedFrameBuff[i] = RADIOLIB_AX25_FLAG; + } + + // get stuffed frame length in bytes + size_t stuffedFrameBuffLen = stuffedFrameBuffLenBits/8 + 1; + uint8_t trailingLen = stuffedFrameBuffLenBits % 8; + + // set end flag field (may be split into two bytes due to misalignment caused by extra stuffing bits) + if(trailingLen != 0) { + stuffedFrameBuffLen++; + stuffedFrameBuff[stuffedFrameBuffLen - 2] |= RADIOLIB_AX25_FLAG >> trailingLen; + stuffedFrameBuff[stuffedFrameBuffLen - 1] = RADIOLIB_AX25_FLAG << (8 - trailingLen); + } else { + stuffedFrameBuff[stuffedFrameBuffLen - 1] = RADIOLIB_AX25_FLAG; + } + + // convert to NRZI + for(size_t i = preambleLen + 1; i < stuffedFrameBuffLen*8; i++) { + size_t currBitPos = i + 7 - 2*(i%8); + size_t prevBitPos = (i - 1) + 7 - 2*((i - 1)%8); + if(TEST_BIT_IN_ARRAY_MSB(stuffedFrameBuff, currBitPos)) { + // bit is 1, no change, copy previous bit + if(TEST_BIT_IN_ARRAY_MSB(stuffedFrameBuff, prevBitPos)) { + SET_BIT_IN_ARRAY_MSB(stuffedFrameBuff, currBitPos); + } else { + CLEAR_BIT_IN_ARRAY_MSB(stuffedFrameBuff, currBitPos); + } + + } else { + // bit is 0, transition, copy inversion of the previous bit + if(TEST_BIT_IN_ARRAY_MSB(stuffedFrameBuff, prevBitPos)) { + CLEAR_BIT_IN_ARRAY_MSB(stuffedFrameBuff, currBitPos); + } else { + SET_BIT_IN_ARRAY_MSB(stuffedFrameBuff, currBitPos); + } + } + } + + // transmit + int16_t state = RADIOLIB_ERR_NONE; + #if !RADIOLIB_EXCLUDE_AFSK + if(bellModem != nullptr) { + bellModem->idle(); + + // iterate over all bytes in the buffer + for(uint32_t i = 0; i < stuffedFrameBuffLen; i++) { + bellModem->write(stuffedFrameBuff[i]); + } + + bellModem->standby(); + + } else { + #endif + state = phyLayer->transmit(stuffedFrameBuff, stuffedFrameBuffLen); + #if !RADIOLIB_EXCLUDE_AFSK + } + #endif + + // deallocate memory + #if !RADIOLIB_STATIC_ONLY + delete[] stuffedFrameBuff; + #endif + + return(state); +} + +void AX25Client::getCallsign(char* buff) { + strncpy(buff, sourceCallsign, RADIOLIB_AX25_MAX_CALLSIGN_LEN + 1); +} + +uint8_t AX25Client::getSSID() { + return(sourceSSID); +} + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/protocols/AX25/AX25.h b/software/firmware/source/libraries/RadioLib/src/protocols/AX25/AX25.h new file mode 100644 index 000000000..c7678a3a2 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/protocols/AX25/AX25.h @@ -0,0 +1,334 @@ +#if !defined(_RADIOLIB_AX25_H) +#define _RADIOLIB_AX25_H + +#include "../../TypeDef.h" + +#if !RADIOLIB_EXCLUDE_AX25 + +#include "../PhysicalLayer/PhysicalLayer.h" +#include "../AFSK/AFSK.h" +#include "../BellModem/BellModem.h" +#include "../../utils/CRC.h" +#include "../../utils/FEC.h" + +// maximum callsign length in bytes +#define RADIOLIB_AX25_MAX_CALLSIGN_LEN 6 + +// flag field MSB LSB DESCRIPTION +#define RADIOLIB_AX25_FLAG 0b01111110 // 7 0 AX.25 frame start/end flag + +// address field +#define RADIOLIB_AX25_SSID_COMMAND_DEST 0b10000000 // 7 7 frame type: command (set in destination SSID) +#define RADIOLIB_AX25_SSID_COMMAND_SOURCE 0b00000000 // 7 7 command (set in source SSID) +#define RADIOLIB_AX25_SSID_RESPONSE_DEST 0b00000000 // 7 7 response (set in destination SSID) +#define RADIOLIB_AX25_SSID_RESPONSE_SOURCE 0b10000000 // 7 7 response (set in source SSID) +#define RADIOLIB_AX25_SSID_HAS_NOT_BEEN_REPEATED 0b00000000 // 7 7 not repeated yet (set in repeater SSID) +#define RADIOLIB_AX25_SSID_HAS_BEEN_REPEATED 0b10000000 // 7 7 repeated (set in repeater SSID) +#define RADIOLIB_AX25_SSID_RESERVED_BITS 0b01100000 // 6 5 reserved bits in SSID +#define RADIOLIB_AX25_SSID_HDLC_EXTENSION_CONTINUE 0b00000000 // 0 0 HDLC extension bit: next octet contains more address information +#define RADIOLIB_AX25_SSID_HDLC_EXTENSION_END 0b00000001 // 0 0 address field end + +// control field +#define RADIOLIB_AX25_CONTROL_U_SET_ASYNC_BAL_MODE 0b01101100 // 7 2 U frame type: set asynchronous balanced mode (connect request) +#define RADIOLIB_AX25_CONTROL_U_SET_ASYNC_BAL_MODE_EXT 0b00101100 // 7 2 set asynchronous balanced mode extended (connect request with module 128) +#define RADIOLIB_AX25_CONTROL_U_DISCONNECT 0b01000000 // 7 2 disconnect request +#define RADIOLIB_AX25_CONTROL_U_DISCONNECT_MODE 0b00001100 // 7 2 disconnect mode (system busy or disconnected) +#define RADIOLIB_AX25_CONTROL_U_UNNUMBERED_ACK 0b01100000 // 7 2 unnumbered acknowledge +#define RADIOLIB_AX25_CONTROL_U_FRAME_REJECT 0b10000100 // 7 2 frame reject +#define RADIOLIB_AX25_CONTROL_U_UNNUMBERED_INFORMATION 0b00000000 // 7 2 unnumbered information +#define RADIOLIB_AX25_CONTROL_U_EXHANGE_IDENTIFICATION 0b10101100 // 7 2 exchange ID +#define RADIOLIB_AX25_CONTROL_U_TEST 0b11100000 // 7 2 test +#define RADIOLIB_AX25_CONTROL_POLL_FINAL_ENABLED 0b00010000 // 4 4 control field poll/final bit: enabled +#define RADIOLIB_AX25_CONTROL_POLL_FINAL_DISABLED 0b00000000 // 4 4 disabled +#define RADIOLIB_AX25_CONTROL_S_RECEIVE_READY 0b00000000 // 3 2 S frame type: receive ready (system ready to receive) +#define RADIOLIB_AX25_CONTROL_S_RECEIVE_NOT_READY 0b00000100 // 3 2 receive not ready (TNC buffer full) +#define RADIOLIB_AX25_CONTROL_S_REJECT 0b00001000 // 3 2 reject (out of sequence or duplicate) +#define RADIOLIB_AX25_CONTROL_S_SELECTIVE_REJECT 0b00001100 // 3 2 selective reject (single frame repeat request) +#define RADIOLIB_AX25_CONTROL_INFORMATION_FRAME 0b00000000 // 0 0 frame type: information (I frame) +#define RADIOLIB_AX25_CONTROL_SUPERVISORY_FRAME 0b00000001 // 1 0 supervisory (S frame) +#define RADIOLIB_AX25_CONTROL_UNNUMBERED_FRAME 0b00000011 // 1 0 unnumbered (U frame) + +// protocol identifier field +#define RADIOLIB_AX25_PID_ISO_8208 0x01 +#define RADIOLIB_AX25_PID_TCP_IP_COMPRESSED 0x06 +#define RADIOLIB_AX25_PID_TCP_IP_UNCOMPRESSED 0x07 +#define RADIOLIB_AX25_PID_SEGMENTATION_FRAGMENT 0x08 +#define RADIOLIB_AX25_PID_TEXNET_DATAGRAM_PROTOCOL 0xC3 +#define RADIOLIB_AX25_PID_LINK_QUALITY_PROTOCOL 0xC4 +#define RADIOLIB_AX25_PID_APPLETALK 0xCA +#define RADIOLIB_AX25_PID_APPLETALK_ARP 0xCB +#define RADIOLIB_AX25_PID_ARPA_INTERNET_PROTOCOL 0xCC +#define RADIOLIB_AX25_PID_ARPA_ADDRESS_RESOLUTION 0xCD +#define RADIOLIB_AX25_PID_FLEXNET 0xCE +#define RADIOLIB_AX25_PID_NET_ROM 0xCF +#define RADIOLIB_AX25_PID_NO_LAYER_3 0xF0 +#define RADIOLIB_AX25_PID_ESCAPE_CHARACTER 0xFF + +/*! + \class AX25Frame + \brief Abstraction of AX.25 frame format. +*/ +class AX25Frame { + public: + /*! + \brief Callsign of the destination station. + */ + char destCallsign[RADIOLIB_AX25_MAX_CALLSIGN_LEN + 1]; + + /*! + \brief SSID of the destination station. + */ + uint8_t destSSID; + + /*! + \brief Callsign of the source station. + */ + char srcCallsign[RADIOLIB_AX25_MAX_CALLSIGN_LEN + 1]; + + /*! + \brief SSID of the source station. + */ + uint8_t srcSSID; + + /*! + \brief Number of repeaters to be used. + */ + uint8_t numRepeaters; + + /*! + \brief The control field. + */ + uint8_t control; + + /*! + \brief The protocol identifier (PID) field. + */ + uint8_t protocolID; + + /*! + \brief Number of bytes in the information field. + */ + uint16_t infoLen; + + /*! + \brief Receive sequence number. + */ + uint8_t rcvSeqNumber; + + /*! + \brief Send sequence number. + */ + uint16_t sendSeqNumber; + + #if !RADIOLIB_STATIC_ONLY + /*! + \brief The info field. + */ + uint8_t* info; + + /*! + \brief Array of repeater callsigns. + */ + char** repeaterCallsigns; + + /*! + \brief Array of repeater SSIDs. + */ + uint8_t* repeaterSSIDs; + #else + /*! + \brief The info field. + */ + uint8_t info[RADIOLIB_STATIC_ARRAY_SIZE]; + + /*! + \brief Array of repeater callsigns. + */ + char repeaterCallsigns[8][RADIOLIB_AX25_MAX_CALLSIGN_LEN + 1]; + + /*! + \brief Array of repeater SSIDs. + */ + uint8_t repeaterSSIDs[8]; + #endif + + /*! + \brief Overloaded constructor, for frames without info field. + \param destCallsign Callsign of the destination station. + \param destSSID SSID of the destination station. + \param srcCallsign Callsign of the source station. + \param srcSSID SSID of the source station. + \param control The control field. + */ + AX25Frame(const char* destCallsign, uint8_t destSSID, const char* srcCallsign, uint8_t srcSSID, uint8_t control); + + /*! + \brief Overloaded constructor, for frames with C-string info field. + \param destCallsign Callsign of the destination station. + \param destSSID SSID of the destination station. + \param srcCallsign Callsign of the source station. + \param srcSSID SSID of the source station. + \param control The control field. + \param protocolID The protocol identifier (PID) field. Set to zero if the frame doesn't have this field. + \param info Information field, in the form of null-terminated C-string. + */ + AX25Frame(const char* destCallsign, uint8_t destSSID, const char* srcCallsign, uint8_t srcSSID, uint8_t control, uint8_t protocolID, const char* info); + + /*! + \brief Default constructor. + \param destCallsign Callsign of the destination station. + \param destSSID SSID of the destination station. + \param srcCallsign Callsign of the source station. + \param srcSSID SSID of the source station. + \param control The control field. + \param protocolID The protocol identifier (PID) field. Set to zero if the frame doesn't have this field. + \param info Information field, in the form of arbitrary binary buffer. + \param infoLen Number of bytes in the information field. + */ + AX25Frame(const char* destCallsign, uint8_t destSSID, const char* srcCallsign, uint8_t srcSSID, uint8_t control, uint8_t protocolID, uint8_t* info, uint16_t infoLen); + + /*! + \brief Copy constructor. + \param frame AX25Frame instance to copy. + */ + AX25Frame(const AX25Frame& frame); + + /*! + \brief Default destructor. + */ + ~AX25Frame(); + + /*! + \brief Overload for assignment operator. + \param frame rvalue AX25Frame. + */ + AX25Frame& operator=(const AX25Frame& frame); + + /*! + \brief Method to set the repeater callsigns and SSIDs. + \param repeaterCallsigns Array of repeater callsigns in the form of null-terminated C-strings. + \param repeaterSSIDs Array of repeater SSIDs. + \param numRepeaters Number of repeaters, maximum is 8. + \returns \ref status_codes + */ + int16_t setRepeaters(char** repeaterCallsigns, uint8_t* repeaterSSIDs, uint8_t numRepeaters); + + /*! + \brief Method to set receive sequence number. + \param seqNumber Sequence number to set, 0 to 7. + */ + void setRecvSequence(uint8_t seqNumber); + + /*! + \brief Method to set send sequence number. + \param seqNumber Sequence number to set, 0 to 7. + */ + void setSendSequence(uint8_t seqNumber); +}; + +/*! + \class AX25Client + \brief Client for AX25 communication. +*/ +class AX25Client { + public: + /*! + \brief Constructor for 2-FSK mode. + \param phy Pointer to the wireless module providing PhysicalLayer communication. + */ + explicit AX25Client(PhysicalLayer* phy); + + #if !RADIOLIB_EXCLUDE_AFSK + /*! + \brief Constructor for AFSK mode. + \param aud Pointer to the AFSK instance providing audio. + */ + explicit AX25Client(AFSKClient* aud); + + /*! + \brief Copy constructor. + \param ax25 AX25Client instance to copy. + */ + AX25Client(const AX25Client& ax25); + + /*! + \brief Overload for assignment operator. + \param ax25 rvalue AX25Client. + */ + AX25Client& operator=(const AX25Client& ax25); + + /*! + \brief Set AFSK tone correction offset. On some platforms, this is required to get the audio produced + by the setup to match the expected 1200/2200 Hz tones. + \param mark Positive or negative correction offset for mark audio frequency in Hz. + \param space Positive or negative correction offset for space audio frequency in Hz. + \param length Audio tone length modifier, defaults to 1.0. + \returns \ref status_codes + */ + int16_t setCorrection(int16_t mark, int16_t space, float length = 1.0f); + #endif + + // basic methods + + /*! + \brief Initialization method. + \param srcCallsign Callsign of the source station. + \param srcSSID 4-bit SSID of the source station (in case there are more stations with the same callsign). + Defaults to 0. + \param preLen Number of "preamble" bytes (RADIOLIB_AX25_FLAG) sent ahead of the actual AX.25 frame. + Does not include the first RADIOLIB_AX25_FLAG byte, which is considered part of the frame. Defaults to 8. + \returns \ref status_codes + */ + int16_t begin(const char* srcCallsign, uint8_t srcSSID = 0x00, uint8_t preLen = 8); + + #if defined(RADIOLIB_BUILD_ARDUINO) + /*! + \brief Transmit unnumbered information (UI) frame. + \param str Data to be sent as Arduino String. + \param destCallsign Callsign of the destination station. + \param destSSID 4-bit SSID of the destination station (in case there are more stations with the same callsign). + Defaults to 0. + \returns \ref status_codes + */ + int16_t transmit(String& str, const char* destCallsign, uint8_t destSSID = 0x00); + #endif + + /*! + \brief Transmit unnumbered information (UI) frame. + \param str Data to be sent. + \param destCallsign Callsign of the destination station. + \param destSSID 4-bit SSID of the destination station (in case there are more stations with the same callsign). + Defaults to 0. + \returns \ref status_codes + */ + int16_t transmit(const char* str, const char* destCallsign, uint8_t destSSID = 0x00); + + /*! + \brief Transmit arbitrary AX.25 frame. + \param frame Frame to be sent. + \returns \ref status_codes + */ + int16_t sendFrame(AX25Frame* frame); + +#if !RADIOLIB_GODMODE + private: +#endif + friend class APRSClient; + + PhysicalLayer* phyLayer; + #if !RADIOLIB_EXCLUDE_AFSK + AFSKClient* audio; + BellClient* bellModem; + #endif + + char sourceCallsign[RADIOLIB_AX25_MAX_CALLSIGN_LEN + 1] = { 0 }; + uint8_t sourceSSID = 0; + uint16_t preambleLen = 0; + + void getCallsign(char* buff); + uint8_t getSSID(); +}; + +#endif + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/protocols/BellModem/BellModem.cpp b/software/firmware/source/libraries/RadioLib/src/protocols/BellModem/BellModem.cpp new file mode 100644 index 000000000..30da93726 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/protocols/BellModem/BellModem.cpp @@ -0,0 +1,98 @@ +#include "BellModem.h" +#if !RADIOLIB_EXCLUDE_BELL + +const BellModem_t Bell101 = { + .freqMark = 1270, + .freqSpace = 1070, + .baudRate = 110, + .freqMarkReply = 2225, + .freqSpaceReply = 2025, +}; + +const BellModem_t Bell103 = { + .freqMark = 1270, + .freqSpace = 1070, + .baudRate = 300, + .freqMarkReply = 2225, + .freqSpaceReply = 2025, +}; + +const BellModem_t Bell202 = { + .freqMark = 1200, + .freqSpace = 2200, + .baudRate = 1200, + .freqMarkReply = 1200, + .freqSpaceReply = 2200, +}; + +BellClient::BellClient(PhysicalLayer* phy, uint32_t pin) : AFSKClient(phy, pin) { + this->reply = false; +} + +BellClient::BellClient(AFSKClient* aud) : AFSKClient(aud) { + this->reply = false; +} + +int16_t BellClient::begin(const BellModem_t& modem) { + int16_t state = setModem(modem); + RADIOLIB_ASSERT(state); + + state = phyLayer->startDirect(); + return(state); +} + +int16_t BellClient::setModem(const BellModem_t& modem) { + this->modemType = modem; + this->toneLen = (1000000.0/(float)this->modemType.baudRate)*this->correction; + return(RADIOLIB_ERR_NONE); +} + +int16_t BellClient::setCorrection(float corr) { + this->correction = corr; + return(RADIOLIB_ERR_NONE); +} + +size_t BellClient::write(uint8_t b) { + // first get the frequencies + uint16_t toneMark = this->modemType.freqMark; + uint16_t toneSpace = this->modemType.freqSpace; + if(this->reply) { + toneMark = this->modemType.freqMarkReply; + toneSpace = this->modemType.freqSpaceReply; + } + + // get the Module pointer to access HAL + Module* mod = this->phyLayer->getMod(); + + if(this->autoStart) { + phyLayer->transmitDirect(); + } + + // iterate over the bits and set correct frequencies + for(uint16_t mask = 0x80; mask >= 0x01; mask >>= 1) { + RadioLibTime_t start = mod->hal->micros(); + if(b & mask) { + this->tone(toneMark, false); + } else { + this->tone(toneSpace, false); + } + mod->waitForMicroseconds(start, this->toneLen); + } + + if(this->autoStart) { + phyLayer->standby(); + } + return(1); +} + +int16_t BellClient::idle() { + this->autoStart = false; + return(phyLayer->transmitDirect()); +} + +int16_t BellClient::standby() { + this->autoStart = true; + return(phyLayer->standby()); +} + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/protocols/BellModem/BellModem.h b/software/firmware/source/libraries/RadioLib/src/protocols/BellModem/BellModem.h new file mode 100644 index 000000000..e65f41179 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/protocols/BellModem/BellModem.h @@ -0,0 +1,128 @@ +#if !defined(_RADIOLIB_BELL_MODEM_H) +#define _RADIOLIB_BELL_MODEM_H + +#include "../../TypeDef.h" +#include "../../Module.h" + +#if !RADIOLIB_EXCLUDE_BELL + +#include "../PhysicalLayer/PhysicalLayer.h" +#include "../AFSK/AFSK.h" +#include "../Print/Print.h" +#include "../Print/ITA2String.h" + +/*! + \struct BellModem_t + \brief Definition of the Bell-compatible modem. +*/ +struct BellModem_t { + /*! + \brief Frequency of the mark tone. + */ + int16_t freqMark; + + /*! + \brief Frequency of the space tone. + */ + int16_t freqSpace; + + /*! + \brief Baud rate. + */ + int16_t baudRate; + + /*! + \brief Frequency of the mark tone when replying. + */ + int16_t freqMarkReply; + + /*! + \brief Frequency of the space tone when replying. + */ + int16_t freqSpaceReply; +}; + +// currently implemented Bell modems +extern const struct BellModem_t Bell101; +extern const struct BellModem_t Bell103; +extern const struct BellModem_t Bell202; + +/*! + \class BellClient + \brief Client for Bell modem communication. The public interface is the same as Arduino Serial. +*/ +class BellClient: public AFSKClient, public RadioLibPrint { + + public: + + /*! + \brief Whether the modem is replying. + On some modems, the replying station has different tone frequencies. + */ + bool reply; + + /*! + \brief Default constructor. + \param phy Pointer to the wireless module providing PhysicalLayer communication. + \param pin The GPIO pin at which the tones will be generated. + */ + explicit BellClient(PhysicalLayer* phy, uint32_t pin); + + /*! + \brief Audio-client constructor. Can be used when AFSKClient instance already exists. + \param aud Audio client to use. + */ + explicit BellClient(AFSKClient* aud); + + /*! + \brief Initialization method. + \param modem Definition of the Bell modem to use for communication. + \returns \ref status_codes + */ + int16_t begin(const BellModem_t& modem); + + /*! + \brief Set Bell modem. + \param modem Definition of the Bell modem to use for communication. + \returns \ref status_codes + */ + int16_t setModem(const BellModem_t& modem); + + /*! + \brief Set correction coefficient for tone length. + \param corr Timing correction factor, used to adjust the length of tones. + Less than 1.0 leads to shorter tones, defaults to 1.0 (no correction). + \returns \ref status_codes + */ + int16_t setCorrection(float corr); + + /*! + \brief Write one byte. Implementation of interface of the RadioLibPrint/Print class. + \param b Byte to write. + \returns 1 if the byte was written, 0 otherwise. + */ + size_t write(uint8_t b) override; + + /*! + \brief Set the modem to idle (ready to transmit). + */ + int16_t idle(); + + /*! + \brief Set the modem to standby (transmitter off). + */ + int16_t standby(); + +#if !RADIOLIB_GODMODE + private: +#endif + BellModem_t modemType = Bell101; + float correction = 1.0; + uint16_t toneLen = 0; + bool autoStart = true; + +}; + +#endif + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/protocols/ExternalRadio/ExternalRadio.cpp b/software/firmware/source/libraries/RadioLib/src/protocols/ExternalRadio/ExternalRadio.cpp new file mode 100644 index 000000000..212f8a155 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/protocols/ExternalRadio/ExternalRadio.cpp @@ -0,0 +1,70 @@ +#include "ExternalRadio.h" + +#if defined(RADIOLIB_BUILD_ARDUINO) +ExternalRadio::ExternalRadio(uint32_t pin) : PhysicalLayer(1, 0) { + mod = new Module(RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC, pin); + mod->hal->pinMode(pin, mod->hal->GpioModeOutput); + this->prevFrf = 0; +} +#endif + +ExternalRadio::ExternalRadio(RadioLibHal *hal, uint32_t pin) : PhysicalLayer(1, 0) { + mod = new Module(hal, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC, pin); + mod->hal->pinMode(pin, mod->hal->GpioModeOutput); + this->prevFrf = 0; +} + +ExternalRadio::ExternalRadio(const ExternalRadio& ext) : PhysicalLayer(1, 0) { + this->prevFrf = ext.prevFrf; + if(ext.mod) { + this->mod = new Module(ext.mod->hal, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC, ext.mod->getGpio()); + } +} + +ExternalRadio& ExternalRadio::operator=(const ExternalRadio& ext) { + if(&ext != this) { + this->prevFrf = ext.prevFrf; + if(ext.mod) { + this->mod = new Module(ext.mod->hal, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC, ext.mod->getGpio()); + } + } + return(*this); +} + +ExternalRadio::~ExternalRadio() { + if(this->mod) { + delete this->mod; + } +} + +Module* ExternalRadio::getMod() { + return(mod); +} + +int16_t ExternalRadio::setFrequencyDeviation(float freqDev) { + (void)freqDev; + return(RADIOLIB_ERR_NONE); +} + +int16_t ExternalRadio::setDataShaping(uint8_t sh) { + (void)sh; + return(RADIOLIB_ERR_NONE); +} + +int16_t ExternalRadio::setEncoding(uint8_t encoding) { + (void)encoding; + return(RADIOLIB_ERR_NONE); +} + +int16_t ExternalRadio::transmitDirect(uint32_t frf) { + if(frf != this->prevFrf) { + uint32_t val = this->mod->hal->GpioLevelLow; + if(frf > this->prevFrf) { + val = this->mod->hal->GpioLevelHigh; + } + this->prevFrf = frf; + this->mod->hal->digitalWrite(this->mod->getGpio(), val); + } + + return(RADIOLIB_ERR_NONE); +} \ No newline at end of file diff --git a/software/firmware/source/libraries/RadioLib/src/protocols/ExternalRadio/ExternalRadio.h b/software/firmware/source/libraries/RadioLib/src/protocols/ExternalRadio/ExternalRadio.h new file mode 100644 index 000000000..86befabcb --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/protocols/ExternalRadio/ExternalRadio.h @@ -0,0 +1,87 @@ +#if !defined(_RADIOLIB_EXTERNAL_RADIO_H) +#define _RADIOLIB_EXTERNAL_RADIO_H + +#include "../../TypeDef.h" +#include "../../Module.h" + +#include "../PhysicalLayer/PhysicalLayer.h" + +/*! + \class ExternalRadio + \brief Class to interface with external radio hardware. +*/ +class ExternalRadio: public PhysicalLayer { + public: + #if defined(RADIOLIB_BUILD_ARDUINO) + /*! + \brief Default constructor. + \param pin Output pin when using direct transmission, defaults to unused pin. + */ + ExternalRadio(uint32_t pin = RADIOLIB_NC); // cppcheck-suppress noExplicitConstructor + #endif + + /*! + \brief Default constructor. + \param hal Pointer to the hardware abstraction layer to use. + \param pin Output pin when using direct transmission, defaults to unused pin. + */ + ExternalRadio(RadioLibHal *hal, uint32_t pin = RADIOLIB_NC); // cppcheck-suppress noExplicitConstructor + + /*! + \brief Copy constructor. + \param ext ExternalRadio instance to copy. + */ + ExternalRadio(const ExternalRadio& ext); + + /*! + \brief Overload for assignment operator. + \param ext rvalue ExternalRadio. + */ + ExternalRadio& operator=(const ExternalRadio& ext); + + /*! + \brief Default destructor. + */ + ~ExternalRadio(); + + /*! + \brief Method to retrieve pointer to the underlying Module instance. + \returns Pointer to the Module instance. + */ + Module* getMod() override; + + /*! + \brief Dummy implementation overriding PhysicalLayer. + \param freqDev Ignored. + \returns \ref status_codes + */ + int16_t setFrequencyDeviation(float freqDev) override; + + /*! + \brief Dummy implementation overriding PhysicalLayer. + \param sh Ignored. + \returns \ref status_codes + */ + int16_t setDataShaping(uint8_t sh) override; + + /*! + \brief Dummy implementation overriding PhysicalLayer. + \param encoding Ignored. + \returns \ref status_codes + */ + int16_t setEncoding(uint8_t encoding) override; + + /*! + \brief Direct transmission to drive external radio. + \param frf "Frequency" to control the output pin. If the frequency is higher than the one sent previously, + the output pin will be set to logic high. Otherwise it will be set to logic low. + \returns \ref status_codes + */ + int16_t transmitDirect(uint32_t frf = 0) override; + + private: + Module* mod; + uint32_t prevFrf; +}; + +#endif \ No newline at end of file diff --git a/software/firmware/source/libraries/RadioLib/src/protocols/FSK4/FSK4.cpp b/software/firmware/source/libraries/RadioLib/src/protocols/FSK4/FSK4.cpp new file mode 100644 index 000000000..7e2d737e8 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/protocols/FSK4/FSK4.cpp @@ -0,0 +1,129 @@ +#include "FSK4.h" +#include +#if !RADIOLIB_EXCLUDE_FSK4 + +FSK4Client::FSK4Client(PhysicalLayer* phy) { + phyLayer = phy; + #if !RADIOLIB_EXCLUDE_AFSK + audioClient = nullptr; + #endif +} + +#if !RADIOLIB_EXCLUDE_AFSK + FSK4Client::FSK4Client(AFSKClient* audio) { + phyLayer = audio->phyLayer; + audioClient = audio; + } +#endif + +int16_t FSK4Client::begin(float base, uint32_t shift, uint16_t rate) { + // save configuration + baseFreqHz = base; + shiftFreqHz = shift; + + // calculate duration of 1 bit + bitDuration = (RadioLibTime_t)1000000/rate; + + // calculate carrier shift + shiftFreq = getRawShift(shift); + + // Write resultant tones into arrays for quick lookup when modulating. + for(uint8_t i = 0; i < 4; i++) { + tones[i] = shiftFreq*i; + tonesHz[i] = shiftFreqHz*i; + } + + // calculate 24-bit frequency + baseFreq = (base * 1000000.0) / phyLayer->getFreqStep(); + + // configure for direct mode + return(phyLayer->startDirect()); +} + +void FSK4Client::idle() { + // Idle at Tone 0. + tone(0); +} + +int16_t FSK4Client::setCorrection(int16_t offsets[], float length) { + for(uint8_t i = 0; i < 4; i++) { + tones[i] += getRawShift(offsets[i]); + tonesHz[i] += offsets[i]; + } + bitDuration *= length; + return(RADIOLIB_ERR_NONE); +} + +size_t FSK4Client::write(uint8_t* buff, size_t len) { + size_t n = 0; + for(size_t i = 0; i < len; i++) { + n += FSK4Client::write(buff[i]); + } + FSK4Client::standby(); + return(n); +} + +size_t FSK4Client::write(uint8_t b) { + // send symbols MSB first + for(uint8_t i = 0; i < 4; i++) { + // Extract 4FSK symbol (2 bits) + uint8_t symbol = (b & 0xC0) >> 6; + + // Modulate + FSK4Client::tone(symbol); + + // Shift to next symbol + b = b << 2; + } + + return(1); +} + +void FSK4Client::tone(uint8_t i) { + Module* mod = phyLayer->getMod(); + RadioLibTime_t start = mod->hal->micros(); + transmitDirect(baseFreq + tones[i], baseFreqHz + tonesHz[i]); + mod->waitForMicroseconds(start, bitDuration); +} + +int16_t FSK4Client::transmitDirect(uint32_t freq, uint32_t freqHz) { + #if !RADIOLIB_EXCLUDE_AFSK + if(audioClient != nullptr) { + return(audioClient->tone(freqHz)); + } + #endif + return(phyLayer->transmitDirect(freq)); +} + +int16_t FSK4Client::standby() { + // ensure everything is stopped in interrupt timing mode + Module* mod = phyLayer->getMod(); + mod->waitForMicroseconds(0, 0); + #if !RADIOLIB_EXCLUDE_AFSK + if(audioClient != nullptr) { + return(audioClient->noTone()); + } + #endif + return(phyLayer->standby()); +} + +int32_t FSK4Client::getRawShift(int32_t shift) { + // calculate module carrier frequency resolution + int32_t step = round(phyLayer->getFreqStep()); + + // check minimum shift value + if(RADIOLIB_ABS(shift) < step / 2) { + return(0); + } + + // round shift to multiples of frequency step size + if(RADIOLIB_ABS(shift) % step < (step / 2)) { + return(shift / step); + } + if(shift < 0) { + return((shift / step) - 1); + } + return((shift / step) + 1); +} + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/protocols/FSK4/FSK4.h b/software/firmware/source/libraries/RadioLib/src/protocols/FSK4/FSK4.h new file mode 100644 index 000000000..31df4101c --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/protocols/FSK4/FSK4.h @@ -0,0 +1,99 @@ +#if !defined(_RADIOLIB_FSK4_H) +#define _RADIOLIB_FSK4_H + +#include "../../TypeDef.h" + +#if !RADIOLIB_EXCLUDE_FSK4 + +#include "../PhysicalLayer/PhysicalLayer.h" +#include "../AFSK/AFSK.h" + +/*! + \class FSK4Client + \brief Client for FSK-4 communication. The public interface is the same as Arduino Serial. +*/ +class FSK4Client { + public: + /*! + \brief Constructor for FSK-4 mode. + \param phy Pointer to the wireless module providing PhysicalLayer communication. + */ + explicit FSK4Client(PhysicalLayer* phy); + + #if !RADIOLIB_EXCLUDE_AFSK + /*! + \brief Constructor for AFSK mode. + \param audio Pointer to the AFSK instance providing audio. + */ + explicit FSK4Client(AFSKClient* audio); + #endif + + // basic methods + + /*! + \brief Initialization method. + \param base Base (space) frequency to be used in MHz (in FSK-4 mode), + or the space tone frequency in Hz (in AFSK mode) + \param shift Frequency shift between each tone in Hz. + \param rate Baud rate to be used during transmission. + \returns \ref status_codes + */ + int16_t begin(float base, uint32_t shift, uint16_t rate); + + /*! + \brief Send out idle condition (RF tone at mark frequency). + */ + void idle(); + + /*! + \brief Set correction coefficients for frequencies and tone length. + \param offsets Four positive or negative correction offsets for audio frequencies in Hz. + \param length Tone length modifier, defaults to 1.0. + \returns \ref status_codes + */ + int16_t setCorrection(int16_t offsets[4], float length = 1.0f); + + /*! + \brief Transmit binary data. + \param buff Buffer to transmit. + \param len Number of bytes to transmit. + \returns Number of transmitted bytes. + */ + size_t write(uint8_t* buff, size_t len); + + /*! + \brief Transmit a single byte. + \param b Byte to transmit. + \returns Number of transmitted bytes. + */ + size_t write(uint8_t b); + + /*! + \brief Stop transmitting. + \returns \ref status_codes + */ + int16_t standby(); + +#if !RADIOLIB_GODMODE + private: +#endif + PhysicalLayer* phyLayer; + #if !RADIOLIB_EXCLUDE_AFSK + AFSKClient* audioClient; + #endif + + uint32_t baseFreq = 0, baseFreqHz = 0; + uint32_t shiftFreq = 0, shiftFreqHz = 0; + RadioLibTime_t bitDuration = 0; + uint32_t tones[4] = { 0 }; + uint32_t tonesHz[4] = { 0 }; + + void tone(uint8_t i); + + int16_t transmitDirect(uint32_t freq = 0, uint32_t freqHz = 0); + int32_t getRawShift(int32_t shift); +}; + +#endif + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/protocols/Hellschreiber/Hellschreiber.cpp b/software/firmware/source/libraries/RadioLib/src/protocols/Hellschreiber/Hellschreiber.cpp new file mode 100644 index 000000000..b79e7a441 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/protocols/Hellschreiber/Hellschreiber.cpp @@ -0,0 +1,102 @@ +#include "Hellschreiber.h" + +#if !RADIOLIB_EXCLUDE_HELLSCHREIBER + +HellClient::HellClient(PhysicalLayer* phy) { + phyLayer = phy; + lineFeed = " "; + #if !RADIOLIB_EXCLUDE_AFSK + audioClient = nullptr; + #endif +} + +#if !RADIOLIB_EXCLUDE_AFSK +HellClient::HellClient(AFSKClient* audio) { + phyLayer = audio->phyLayer; + lineFeed = " "; + audioClient = audio; +} +#endif + +int16_t HellClient::begin(float base, float rate) { + // calculate 24-bit frequency + baseFreqHz = base; + baseFreq = (base * 1000000.0) / phyLayer->getFreqStep(); + + // calculate "pixel" duration + pixelDuration = 1000000.0/rate; + + // configure for direct mode + return(phyLayer->startDirect()); +} + +size_t HellClient::printGlyph(const uint8_t* buff) { + // print the character + Module* mod = phyLayer->getMod(); + bool transmitting = false; + for(uint8_t mask = 0x40; mask >= 0x01; mask >>= 1) { + for(int8_t i = RADIOLIB_HELL_FONT_HEIGHT - 1; i >= 0; i--) { + RadioLibTime_t start = mod->hal->micros(); + if((buff[i] & mask) && (!transmitting)) { + transmitting = true; + transmitDirect(baseFreq, baseFreqHz); + } else if((!(buff[i] & mask)) && (transmitting)) { + transmitting = false; + standby(); + } + mod->waitForMicroseconds(start, pixelDuration); + } + } + + // make sure transmitter is off + standby(); + + return(1); +} + +void HellClient::setInversion(bool inv) { + invert = inv; +} + +size_t HellClient::write(uint8_t b) { + // convert to position in font buffer + uint8_t pos = b; + if((pos >= ' ') && (pos <= '_')) { + pos -= ' '; + } else if((pos >= 'a') && (pos <= 'z')) { + pos -= (2*' '); + } else { + return(0); + } + + // fetch character from flash + uint8_t buff[RADIOLIB_HELL_FONT_WIDTH]; + buff[0] = 0x00; + for(uint8_t i = 0; i < RADIOLIB_HELL_FONT_WIDTH - 2; i++) { + buff[i + 1] = RADIOLIB_NONVOLATILE_READ_BYTE(&HellFont[pos][i]); + } + buff[RADIOLIB_HELL_FONT_WIDTH - 1] = 0x00; + + // print the character + return(printGlyph(buff)); +} + +int16_t HellClient::transmitDirect(uint32_t freq, uint32_t freqHz) { + #if !RADIOLIB_EXCLUDE_AFSK + if(audioClient != nullptr) { + return(audioClient->tone(freqHz)); + } + #endif + return(phyLayer->transmitDirect(freq)); +} + +int16_t HellClient::standby() { + #if !RADIOLIB_EXCLUDE_AFSK + if(audioClient != nullptr) { + return(audioClient->noTone(invert)); + } + #endif + return(phyLayer->standby(RADIOLIB_STANDBY_WARM)); +} + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/protocols/Hellschreiber/Hellschreiber.h b/software/firmware/source/libraries/RadioLib/src/protocols/Hellschreiber/Hellschreiber.h new file mode 100644 index 000000000..0c266979d --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/protocols/Hellschreiber/Hellschreiber.h @@ -0,0 +1,156 @@ +#if !defined(_RADIOLIB_HELLSCHREIBER_H) +#define _RADIOLIB_HELLSCHREIBER_H + +#include "../../TypeDef.h" + +#if !RADIOLIB_EXCLUDE_HELLSCHREIBER + +#include "../PhysicalLayer/PhysicalLayer.h" +#include "../AFSK/AFSK.h" +#include "../Print/Print.h" + +#define RADIOLIB_HELL_FONT_WIDTH 7 +#define RADIOLIB_HELL_FONT_HEIGHT 7 + +// font definition: characters are stored in rows, +// least significant byte of each character is the first row +// Hellschreiber use 7x7 characters, but this simplified font uses only 5x5 +// the extra bytes aren't stored +static const uint8_t HellFont[64][RADIOLIB_HELL_FONT_WIDTH - 2] RADIOLIB_NONVOLATILE = { + { 0b0000000, 0b0000000, 0b0000000, 0b0000000, 0b0000000 }, // space + { 0b0001000, 0b0001000, 0b0001000, 0b0000000, 0b0001000 }, // ! + { 0b0010100, 0b0010100, 0b0000000, 0b0000000, 0b0000000 }, // " + { 0b0010100, 0b0111110, 0b0010100, 0b0111110, 0b0010100 }, // # + { 0b0111110, 0b0101000, 0b0111110, 0b0001010, 0b0111110 }, // $ + { 0b0110010, 0b0110100, 0b0001000, 0b0010110, 0b0100110 }, // % + { 0b0010000, 0b0101000, 0b0010000, 0b0101000, 0b0110100 }, // & + { 0b0001000, 0b0001000, 0b0000000, 0b0000000, 0b0000000 }, // ' + { 0b0000100, 0b0001000, 0b0001000, 0b0001000, 0b0000100 }, // ( + { 0b0010000, 0b0001000, 0b0001000, 0b0001000, 0b0010000 }, // ) + { 0b0010100, 0b0001000, 0b0010100, 0b0000000, 0b0000000 }, // * + { 0b0001000, 0b0001000, 0b0111110, 0b0001000, 0b0001000 }, // + + { 0b0000000, 0b0000000, 0b0000000, 0b0001000, 0b0010000 }, // , + { 0b0000000, 0b0000000, 0b0111110, 0b0000000, 0b0000000 }, // - + { 0b0000000, 0b0000000, 0b0000000, 0b0000000, 0b0001000 }, // . + { 0b0000010, 0b0000100, 0b0001000, 0b0010000, 0b0100000 }, // / + { 0b0011100, 0b0100110, 0b0101010, 0b0110010, 0b0011100 }, // 0 + { 0b0011000, 0b0001000, 0b0001000, 0b0001000, 0b0001000 }, // 1 + { 0b0011000, 0b0100100, 0b0001000, 0b0010000, 0b0111100 }, // 2 + { 0b0111100, 0b0000100, 0b0011100, 0b0000100, 0b0111100 }, // 3 + { 0b0100100, 0b0100100, 0b0111100, 0b0000100, 0b0000100 }, // 4 + { 0b0011100, 0b0100000, 0b0111100, 0b0000100, 0b0111100 }, // 5 + { 0b0111100, 0b0100000, 0b0111100, 0b0100100, 0b0111100 }, // 6 + { 0b0111100, 0b0000100, 0b0001000, 0b0010000, 0b0100000 }, // 7 + { 0b0111100, 0b0100100, 0b0011000, 0b0100100, 0b0111100 }, // 8 + { 0b0111100, 0b0100100, 0b0111100, 0b0000100, 0b0111100 }, // 9 + { 0b0000000, 0b0001000, 0b0000000, 0b0000000, 0b0001000 }, // : + { 0b0000000, 0b0001000, 0b0000000, 0b0001000, 0b0001000 }, // ; + { 0b0000100, 0b0001000, 0b0010000, 0b0001000, 0b0000100 }, // < + { 0b0000000, 0b0111110, 0b0000000, 0b0111110, 0b0000000 }, // = + { 0b0010000, 0b0001000, 0b0000100, 0b0001000, 0b0010000 }, // > + { 0b0011100, 0b0000100, 0b0001000, 0b0000000, 0b0001000 }, // ? + { 0b0011100, 0b0100010, 0b0101110, 0b0101010, 0b0001100 }, // @ + { 0b0111110, 0b0100010, 0b0111110, 0b0100010, 0b0100010 }, // A + { 0b0111100, 0b0010010, 0b0011110, 0b0010010, 0b0111100 }, // B + { 0b0011110, 0b0110000, 0b0100000, 0b0110000, 0b0011110 }, // C + { 0b0111100, 0b0100010, 0b0100010, 0b0100010, 0b0111100 }, // D + { 0b0111110, 0b0100000, 0b0111100, 0b0100000, 0b0111110 }, // E + { 0b0111110, 0b0100000, 0b0111100, 0b0100000, 0b0100000 }, // F + { 0b0111110, 0b0100000, 0b0101110, 0b0100010, 0b0111110 }, // G + { 0b0100010, 0b0100010, 0b0111110, 0b0100010, 0b0100010 }, // H + { 0b0011100, 0b0001000, 0b0001000, 0b0001000, 0b0011100 }, // I + { 0b0111100, 0b0001000, 0b0001000, 0b0101000, 0b0111000 }, // J + { 0b0100100, 0b0101000, 0b0110000, 0b0101000, 0b0100100 }, // K + { 0b0100000, 0b0100000, 0b0100000, 0b0100000, 0b0111100 }, // L + { 0b0100010, 0b0110110, 0b0101010, 0b0100010, 0b0100010 }, // M + { 0b0100010, 0b0110010, 0b0101010, 0b0100110, 0b0100010 }, // N + { 0b0011100, 0b0100010, 0b0100010, 0b0100010, 0b0011100 }, // O + { 0b0111110, 0b0100010, 0b0111110, 0b0100000, 0b0100000 }, // P + { 0b0111110, 0b0100010, 0b0100010, 0b0100110, 0b0111110 }, // Q + { 0b0111110, 0b0100010, 0b0111110, 0b0100100, 0b0100010 }, // R + { 0b0111110, 0b0100000, 0b0111110, 0b0000010, 0b0111110 }, // S + { 0b0111110, 0b0001000, 0b0001000, 0b0001000, 0b0001000 }, // T + { 0b0100010, 0b0100010, 0b0100010, 0b0100010, 0b0111110 }, // U + { 0b0100010, 0b0100010, 0b0010100, 0b0010100, 0b0001000 }, // V + { 0b0100010, 0b0100010, 0b0101010, 0b0110110, 0b0100010 }, // W + { 0b0100010, 0b0010100, 0b0001000, 0b0010100, 0b0100010 }, // X + { 0b0100010, 0b0010100, 0b0001000, 0b0001000, 0b0001000 }, // Y + { 0b0111110, 0b0000100, 0b0001000, 0b0010000, 0b0111110 }, // Z + { 0b0001100, 0b0001000, 0b0001000, 0b0001000, 0b0001100 }, // [ + { 0b0100000, 0b0010000, 0b0001000, 0b0000100, 0b0000010 }, // backslash + { 0b0011000, 0b0001000, 0b0001000, 0b0001000, 0b0011000 }, // ] + { 0b0001000, 0b0010100, 0b0000000, 0b0000000, 0b0000000 }, // ^ + { 0b0000000, 0b0000000, 0b0000000, 0b0000000, 0b0111110 } // _ +}; + +/*! + \class HellClient + \brief Client for Hellschreiber transmissions. +*/ +class HellClient: public RadioLibPrint { + public: + /*! + \brief Constructor for 2-FSK mode. + \param phy Pointer to the wireless module providing PhysicalLayer communication. + */ + explicit HellClient(PhysicalLayer* phy); + + #if !RADIOLIB_EXCLUDE_AFSK + /*! + \brief Constructor for AFSK mode. + \param audio Pointer to the AFSK instance providing audio. + */ + explicit HellClient(AFSKClient* audio); + #endif + + // basic methods + + /*! + \brief Initialization method. + \param base Base RF frequency to be used in MHz (in 2-FSK mode), or the tone frequency in Hz (in AFSK mode). + \param rate Baud rate to be used during transmission. Defaults to 122.5 ("Feld Hell") + */ + int16_t begin(float base, float rate = 122.5); + + /*! + \brief Method to "print" a buffer of pixels, this is exposed to allow users to send custom characters. + \param buff Buffer of pixels to send, in a 7x7 pixel array. + \returns Always returns the number of printed glyphs (1). + */ + size_t printGlyph(const uint8_t* buff); + + /*! + \brief Invert text color. + \param inv Whether to enable color inversion (white text on black background), or not (black text on white background) + */ + void setInversion(bool inv); + + /*! + \brief Write one byte. Implementation of interface of the RadioLibPrint/Print class. + \param b Byte to write. + \returns 1 if the byte was written, 0 otherwise. + */ + size_t write(uint8_t b) override; + +#if !RADIOLIB_GODMODE + private: +#endif + PhysicalLayer* phyLayer; + #if !RADIOLIB_EXCLUDE_AFSK + AFSKClient* audioClient; + #endif + + uint32_t baseFreq = 0, baseFreqHz = 0; + uint32_t pixelDuration = 0; + bool invert = false; + + size_t printNumber(unsigned long, uint8_t); + size_t printFloat(double, uint8_t); + + int16_t transmitDirect(uint32_t freq = 0, uint32_t freqHz = 0); + int16_t standby(); +}; + +#endif + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/protocols/LoRaWAN/LoRaWAN.cpp b/software/firmware/source/libraries/RadioLib/src/protocols/LoRaWAN/LoRaWAN.cpp new file mode 100644 index 000000000..b8350003e --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/protocols/LoRaWAN/LoRaWAN.cpp @@ -0,0 +1,3453 @@ +#include "LoRaWAN.h" +#include +#if defined(ESP_PLATFORM) +#include "esp_attr.h" +#endif + +#if !RADIOLIB_EXCLUDE_LORAWAN + +LoRaWANNode::LoRaWANNode(PhysicalLayer* phy, const LoRaWANBand_t* band, uint8_t subBand) { + this->phyLayer = phy; + this->band = band; + this->channels[RADIOLIB_LORAWAN_DIR_RX2] = this->band->rx2; + this->txPowerMax = this->band->powerMax; + this->subBand = subBand; + this->dwellTimeEnabledUp = this->dwellTimeUp != 0; + this->dwellTimeEnabledDn = this->dwellTimeDn != 0; + memset(this->channelPlan, 0, sizeof(this->channelPlan)); +} + +#if defined(RADIOLIB_BUILD_ARDUINO) +int16_t LoRaWANNode::sendReceive(String& strUp, uint8_t fPort, String& strDown, bool isConfirmed, LoRaWANEvent_t* eventUp, LoRaWANEvent_t* eventDown) { + int16_t state = RADIOLIB_ERR_UNKNOWN; + + const char* dataUp = strUp.c_str(); + + // build a temporary buffer + // LoRaWAN downlinks can have 250 bytes at most with 1 extra byte for NULL + size_t lenDown = 0; + uint8_t dataDown[251]; + + state = this->sendReceive((uint8_t*)dataUp, strlen(dataUp), fPort, dataDown, &lenDown, isConfirmed, eventUp, eventDown); + + if(state == RADIOLIB_ERR_NONE) { + // add null terminator + dataDown[lenDown] = '\0'; + + // initialize Arduino String class + strDown = String((char*)dataDown); + } + + return(state); +} +#endif + +int16_t LoRaWANNode::sendReceive(const char* strUp, uint8_t fPort, bool isConfirmed, LoRaWANEvent_t* eventUp, LoRaWANEvent_t* eventDown) { + // build a temporary buffer + // LoRaWAN downlinks can have 250 bytes at most with 1 extra byte for NULL + size_t lenDown = 0; + uint8_t dataDown[251]; + + return(this->sendReceive((uint8_t*)strUp, strlen(strUp), fPort, dataDown, &lenDown, isConfirmed, eventUp, eventDown)); +} + +int16_t LoRaWANNode::sendReceive(const char* strUp, uint8_t fPort, uint8_t* dataDown, size_t* lenDown, bool isConfirmed, LoRaWANEvent_t* eventUp, LoRaWANEvent_t* eventDown) { + return(this->sendReceive((uint8_t*)strUp, strlen(strUp), fPort, dataDown, lenDown, isConfirmed, eventUp, eventDown)); +} + +int16_t LoRaWANNode::sendReceive(uint8_t* dataUp, size_t lenUp, uint8_t fPort, bool isConfirmed, LoRaWANEvent_t* eventUp, LoRaWANEvent_t* eventDown) { + // build a temporary buffer + // LoRaWAN downlinks can have 250 bytes at most with 1 extra byte for NULL + size_t lenDown = 0; + uint8_t dataDown[251]; + + return(this->sendReceive(dataUp, lenUp, fPort, dataDown, &lenDown, isConfirmed, eventUp, eventDown)); +} + +int16_t LoRaWANNode::sendReceive(uint8_t* dataUp, size_t lenUp, uint8_t fPort, uint8_t* dataDown, size_t* lenDown, bool isConfirmed, LoRaWANEvent_t* eventUp, LoRaWANEvent_t* eventDown) { + if(!dataUp || !dataDown || !lenDown) { + return(RADIOLIB_ERR_NULL_POINTER); + } + int16_t state = RADIOLIB_ERR_UNKNOWN; + Module* mod = this->phyLayer->getMod(); + + // if after (at) ADR_ACK_LIMIT frames no RekeyConf was received, revert to Join state + if(this->fCntUp == (1UL << this->adrLimitExp)) { + state = this->getMacPayload(RADIOLIB_LORAWAN_MAC_REKEY, this->fOptsUp, this->fOptsUpLen, NULL, RADIOLIB_LORAWAN_UPLINK); + if(state == RADIOLIB_ERR_NONE) { + this->clearSession(); + } + } + + // if not joined, don't do anything + if(!this->isActivated()) { + return(RADIOLIB_ERR_NETWORK_NOT_JOINED); + } + + // check if the requested payload + fPort are allowed, also given dutycycle + uint8_t totalLen = lenUp + this->fOptsUpLen; + state = this->isValidUplink(&totalLen, fPort); + RADIOLIB_ASSERT(state); + + // in case of TS009, a payload that is too long may have gotten clipped, + // so recalculate the actual payload length + // (outside of TS009, a payload that is too long throws an error) + lenUp = totalLen - this->fOptsUpLen; + + // the first 16 bytes are reserved for MIC calculation blocks + size_t uplinkMsgLen = RADIOLIB_LORAWAN_FRAME_LEN(lenUp, this->fOptsUpLen); + #if RADIOLIB_STATIC_ONLY + uint8_t uplinkMsg[RADIOLIB_STATIC_ARRAY_SIZE]; + #else + uint8_t* uplinkMsg = new uint8_t[uplinkMsgLen]; + #endif + + // build the encrypted uplink message + this->composeUplink(dataUp, lenUp, uplinkMsg, fPort, isConfirmed); + + // reset Time-on-Air as we are starting new uplink sequence + this->lastToA = 0; + + // repeat uplink+downlink up to 'nbTrans' times (ADR) + uint8_t trans = 0; + for(; trans < this->nbTrans; trans++) { + + // keep track of number of hopped channels + uint8_t numHops = this->maxChanges; + + // number of additional CAD tries + uint8_t numBackoff = 0; + if(this->backoffMax) { + numBackoff = this->phyLayer->random(1, this->backoffMax + 1); + } + + do { + // select a pair of Tx/Rx channels for uplink+downlink + this->selectChannels(); + + // generate and set uplink MIC (depends on selected channel) + this->micUplink(uplinkMsg, uplinkMsgLen); + + // if CSMA is enabled, repeat channel selection & encryption up to numHops times + } while(this->csmaEnabled && numHops-- > 0 && !this->csmaChannelClear(this->difsSlots, numBackoff)); + + // send it (without the MIC calculation blocks) + state = this->transmitUplink(&this->channels[RADIOLIB_LORAWAN_UPLINK], + &uplinkMsg[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS], + (uint8_t)(uplinkMsgLen - RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS), + trans > 0); + if(state != RADIOLIB_ERR_NONE) { + #if !RADIOLIB_STATIC_ONLY + delete[] uplinkMsg; + #endif + return(state); + } + + // handle Rx1 and Rx2 windows - returns window > 0 if a downlink is received + state = receiveCommon(RADIOLIB_LORAWAN_DOWNLINK, this->channels, this->rxDelays, 2, this->rxDelayStart); + + // RETRANSMIT_TIMEOUT is 2s +/- 1s (RP v1.0.4) + // must be present after any confirmed frame, so we force this here + if(isConfirmed) { + mod->hal->delay(this->phyLayer->random(1000, 3000)); + } + + // if an error occured or a downlink was received, stop retransmission + if(state != RADIOLIB_ERR_NONE) { + break; + } + // if no downlink was received, go on + + } // end of transmission & reception + + // note: if an error occured, it may still be the case that a transmission occured + // therefore, we act as if a transmission occured before throwing the actual error + // this feels to be the best way to comply to spec + + // increase frame counter by one for the next uplink + this->fCntUp += 1; + + // the downlink confirmation was acknowledged, so clear the counter value + this->confFCntDown = RADIOLIB_LORAWAN_FCNT_NONE; + + // pass the uplink info if requested + if(eventUp) { + eventUp->dir = RADIOLIB_LORAWAN_UPLINK; + eventUp->confirmed = isConfirmed; + eventUp->confirming = (this->confFCntDown != RADIOLIB_LORAWAN_FCNT_NONE); + eventUp->datarate = this->channels[RADIOLIB_LORAWAN_UPLINK].dr; + eventUp->freq = this->channels[RADIOLIB_LORAWAN_UPLINK].freq / 10000.0; + eventUp->power = this->txPowerMax - this->txPowerSteps * 2; + eventUp->fCnt = this->fCntUp; + eventUp->fPort = fPort; + eventUp->nbTrans = trans; + } + + #if !RADIOLIB_STATIC_ONLY + delete[] uplinkMsg; + #endif + + // if a hardware error occurred, return + if(state < RADIOLIB_ERR_NONE) { + return(state); + } + + uint8_t rxWindow = state; + + // if no downlink was received, do an early exit + if(rxWindow == 0) { + // check if ADR backoff must occur + if(this->adrEnabled) { + this->adrBackoff(); + } + // remove only non-persistent MAC commands, the other commands should be re-sent until downlink is received + LoRaWANNode::clearMacCommands(this->fOptsUp, &this->fOptsUpLen, RADIOLIB_LORAWAN_UPLINK); + return(rxWindow); + } + + // a downlink was received, so we can clear the whole MAC uplink buffer + memset(this->fOptsUp, 0, RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN); + this->fOptsUpLen = 0; + + state = this->parseDownlink(dataDown, lenDown, eventDown); + + // return an error code, if any, otherwise return Rx window (which is > 0) + RADIOLIB_ASSERT(state); + return(rxWindow); +} + +void LoRaWANNode::clearNonces() { + // clear & set all the device credentials + memset(this->bufferNonces, 0, RADIOLIB_LORAWAN_NONCES_BUF_SIZE); + this->keyCheckSum = 0; + this->devNonce = 0; + this->joinNonce = 0; + this->isActive = false; + this->rev = 0; +} + +uint8_t* LoRaWANNode::getBufferNonces() { + // set the device credentials + LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_VERSION], RADIOLIB_LORAWAN_NONCES_VERSION_VAL); + LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_MODE], this->lwMode); + LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_CLASS], this->lwClass); + LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_PLAN], this->band->bandNum); + LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_CHECKSUM], this->keyCheckSum); + + // generate the signature of the Nonces buffer, and store it in the last two bytes of the Nonces buffer + uint16_t signature = LoRaWANNode::checkSum16(this->bufferNonces, RADIOLIB_LORAWAN_NONCES_BUF_SIZE - 2); + LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_SIGNATURE], signature); + + return(this->bufferNonces); +} + +int16_t LoRaWANNode::setBufferNonces(uint8_t* persistentBuffer) { + if(this->isActivated()) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Did not update buffer: session already active"); + return(RADIOLIB_ERR_NONE); + } + + int16_t state = LoRaWANNode::checkBufferCommon(persistentBuffer, RADIOLIB_LORAWAN_NONCES_BUF_SIZE); + RADIOLIB_ASSERT(state); + + bool isSameKeys = LoRaWANNode::ntoh(&persistentBuffer[RADIOLIB_LORAWAN_NONCES_CHECKSUM]) == this->keyCheckSum; + bool isSameMode = LoRaWANNode::ntoh(&persistentBuffer[RADIOLIB_LORAWAN_NONCES_MODE]) == this->lwMode; + bool isSameClass = LoRaWANNode::ntoh(&persistentBuffer[RADIOLIB_LORAWAN_NONCES_CLASS]) == this->lwClass; + bool isSamePlan = LoRaWANNode::ntoh(&persistentBuffer[RADIOLIB_LORAWAN_NONCES_PLAN]) == this->band->bandNum; + + // check if Nonces buffer matches the current configuration + if(!isSameKeys || !isSameMode || !isSameClass || !isSamePlan) { + // if configuration did not match, discard whatever is currently in the buffers and start fresh + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Configuration mismatch (keys: %d, mode: %d, class: %d, plan: %d)", isSameKeys, isSameMode, isSameClass, isSamePlan); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Discarding the Nonces buffer:"); + RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(persistentBuffer, RADIOLIB_LORAWAN_NONCES_BUF_SIZE); + return(RADIOLIB_ERR_NONCES_DISCARDED); + } + + // copy the whole buffer over + memcpy(this->bufferNonces, persistentBuffer, RADIOLIB_LORAWAN_NONCES_BUF_SIZE); + + this->devNonce = LoRaWANNode::ntoh(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_DEV_NONCE]); + this->joinNonce = LoRaWANNode::ntoh(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_JOIN_NONCE], 3); + + // revert to inactive as long as no session is restored + this->bufferNonces[RADIOLIB_LORAWAN_NONCES_ACTIVE] = (uint8_t)false; + this->isActive = false; + + return(state); +} + +void LoRaWANNode::clearSession() { + memset(this->bufferSession, 0, RADIOLIB_LORAWAN_SESSION_BUF_SIZE); + memset(this->fOptsUp, 0, RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN); + memset(this->fOptsDown, 0, RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN); + this->bufferNonces[RADIOLIB_LORAWAN_NONCES_ACTIVE] = (uint8_t)false; + this->isActive = false; + + // reset all frame counters + this->fCntUp = 0; + this->aFCntDown = 0; + this->nFCntDown = 0; + this->confFCntUp = RADIOLIB_LORAWAN_FCNT_NONE; + this->confFCntDown = RADIOLIB_LORAWAN_FCNT_NONE; + this->adrFCnt = 0; + + // reset number of retransmissions from ADR + this->nbTrans = 1; + + // clear CSMA settings + this->csmaEnabled = false; + this->maxChanges = 0; + this->difsSlots = 0; + this->backoffMax = 0; +} + +void LoRaWANNode::createSession(uint16_t lwMode, uint8_t initialDr) { + this->clearSession(); + + // setup JoinRequest uplink/downlink frequencies and datarates + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { + this->selectChannelPlanDyn(true); + } else { + this->selectChannelPlanFix(); + } + + uint8_t drUp = RADIOLIB_LORAWAN_DATA_RATE_UNUSED; + + // on fixed bands, the first OTAA uplink (JoinRequest) is sent on fixed datarate + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_FIXED && lwMode == RADIOLIB_LORAWAN_MODE_OTAA) { + // randomly select one of 8 or 9 channels and find corresponding datarate + uint8_t numChannels = this->band->numTxSpans == 1 ? 8 : 9; + uint8_t rand = this->phyLayer->random(numChannels) + 1; // range 1-8 or 1-9 + if(rand <= 8) { + drUp = this->band->txSpans[0].drJoinRequest; // if one of the first 8 channels, select datarate of span 0 + } else { + drUp = this->band->txSpans[1].drJoinRequest; // if ninth channel, select datarate of span 1 + } + } else { + // on dynamic bands, the first OTAA uplink (JoinRequest) can be any available datarate + // this is also true for ABP on both dynamic and fixed bands, as there is no JoinRequest + if(initialDr != RADIOLIB_LORAWAN_DATA_RATE_UNUSED) { + uint8_t i = 0; + for(; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + if(this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].enabled) { + if(initialDr >= this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].drMin + && initialDr <= this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].drMax) { + drUp = initialDr; + break; + } + } + } + // if there is no channel that allowed the user-specified datarate, revert to default datarate + if(i == RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Datarate %d is not valid - using default", initialDr); + initialDr = RADIOLIB_LORAWAN_DATA_RATE_UNUSED; + } + } + + // if there is no (channel that allowed the) user-specified datarate, use a default datarate + // we use the floor of the average datarate of the first enabled channel + if(initialDr == RADIOLIB_LORAWAN_DATA_RATE_UNUSED) { + for(int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + if(this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].enabled) { + uint8_t drMin = this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].drMin; + uint8_t drMax = this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].drMax; + drUp = (drMin + drMax) / 2; + } + } + } + } + + uint8_t cOcts[5]; // 5 = maximum downlink payload length + uint8_t cid = RADIOLIB_LORAWAN_MAC_LINK_ADR; + uint8_t cLen = 1; // only apply Dr/Tx field + cOcts[0] = (drUp << 4); // set uplink datarate + cOcts[0] |= 0; // default to max Tx Power + (void)execMacCommand(cid, cOcts, cLen); + + cid = RADIOLIB_LORAWAN_MAC_DUTY_CYCLE; + this->getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK); + uint8_t maxDCyclePower = 0; + switch(this->band->dutyCycle) { + case(3600): + maxDCyclePower = 10; + break; + case(36000): + maxDCyclePower = 7; + break; + } + cOcts[0] = maxDCyclePower; + (void)execMacCommand(cid, cOcts, cLen); + + cid = RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP; + (void)this->getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK); + cOcts[0] = (RADIOLIB_LORAWAN_RX1_DR_OFFSET << 4); + cOcts[0] |= this->channels[RADIOLIB_LORAWAN_DIR_RX2].dr; // may be set by user, otherwise band's default upon initialization + LoRaWANNode::hton(&cOcts[1], this->channels[RADIOLIB_LORAWAN_DIR_RX2].freq, 3); + (void)execMacCommand(cid, cOcts, cLen); + + cid = RADIOLIB_LORAWAN_MAC_RX_TIMING_SETUP; + (void)this->getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK); + cOcts[0] = (RADIOLIB_LORAWAN_RECEIVE_DELAY_1_MS / 1000); + (void)execMacCommand(cid, cOcts, cLen); + + cid = RADIOLIB_LORAWAN_MAC_TX_PARAM_SETUP; + (void)this->getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK); + cOcts[0] = (this->band->dwellTimeDn > 0 ? 1 : 0) << 5; + cOcts[0] |= (this->band->dwellTimeUp > 0 ? 1 : 0) << 4; + uint8_t maxEIRPRaw; + switch(this->band->powerMax) { + case(12): + maxEIRPRaw = 2; + break; + case(14): + maxEIRPRaw = 4; + break; + case(16): + maxEIRPRaw = 5; + break; + case(19): // this option does not exist for the TxParamSetupReq but will be caught during execution + maxEIRPRaw = 7; + break; + case(30): + maxEIRPRaw = 13; + break; + default: + maxEIRPRaw = 2; + break; + } + cOcts[0] |= maxEIRPRaw; + (void)execMacCommand(cid, cOcts, cLen); + + cid = RADIOLIB_LORAWAN_MAC_ADR_PARAM_SETUP; + (void)this->getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK); + cOcts[0] = (RADIOLIB_LORAWAN_ADR_ACK_LIMIT_EXP << 4); + cOcts[0] |= RADIOLIB_LORAWAN_ADR_ACK_DELAY_EXP; + (void)execMacCommand(cid, cOcts, cLen); + + cid = RADIOLIB_LORAWAN_MAC_REJOIN_PARAM_SETUP; + (void)this->getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK); + cOcts[0] = (RADIOLIB_LORAWAN_REJOIN_MAX_TIME_N << 4); + cOcts[0] |= RADIOLIB_LORAWAN_REJOIN_MAX_COUNT_N; + (void)execMacCommand(cid, cOcts, cLen); +} + +uint8_t* LoRaWANNode::getBufferSession() { + // store all frame counters + LoRaWANNode::hton(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_A_FCNT_DOWN], this->aFCntDown); + LoRaWANNode::hton(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_N_FCNT_DOWN], this->nFCntDown); + LoRaWANNode::hton(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_CONF_FCNT_UP], this->confFCntUp); + LoRaWANNode::hton(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_CONF_FCNT_DOWN], this->confFCntDown); + LoRaWANNode::hton(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_ADR_FCNT], this->adrFCnt); + LoRaWANNode::hton(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_FCNT_UP], this->fCntUp); + + // store the enabled channels + uint64_t chMaskGrp0123 = 0; + uint32_t chMaskGrp45 = 0; + this->getChannelPlanMask(&chMaskGrp0123, &chMaskGrp45); + LoRaWANNode::hton(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_LINK_ADR] + 1, chMaskGrp0123); + LoRaWANNode::hton(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_LINK_ADR] + 9, chMaskGrp45); + + // store the available/unused channels + uint16_t chMask = 0x0000; + (void)this->getAvailableChannels(&chMask); + LoRaWANNode::hton(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_AVAILABLE_CHANNELS], chMask); + + // store the current uplink MAC command queue + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_MAC_QUEUE], this->fOptsUp, RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN); + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_MAC_QUEUE_LEN], &this->fOptsUpLen, 1); + + // generate the signature of the Session buffer, and store it in the last two bytes of the Session buffer + uint16_t signature = LoRaWANNode::checkSum16(this->bufferSession, RADIOLIB_LORAWAN_SESSION_BUF_SIZE - 2); + LoRaWANNode::hton(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_SIGNATURE], signature); + + return(this->bufferSession); +} + +int16_t LoRaWANNode::setBufferSession(uint8_t* persistentBuffer) { + if(this->isActivated()) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Did not update buffer: session already active"); + return(RADIOLIB_ERR_NONE); + } + + int16_t state = LoRaWANNode::checkBufferCommon(persistentBuffer, RADIOLIB_LORAWAN_SESSION_BUF_SIZE); + RADIOLIB_ASSERT(state); + + // the Nonces buffer holds a checksum signature - compare this to the signature that is in the session buffer + uint16_t signatureNonces = LoRaWANNode::ntoh(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_SIGNATURE]); + uint16_t signatureInSession = LoRaWANNode::ntoh(&persistentBuffer[RADIOLIB_LORAWAN_SESSION_NONCES_SIGNATURE]); + if(signatureNonces != signatureInSession) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("The Session buffer (%04x) does not match the Nonces buffer (%04x)", + signatureInSession, signatureNonces); + return(RADIOLIB_ERR_SESSION_DISCARDED); + } + + // copy the whole buffer over + memcpy(this->bufferSession, persistentBuffer, RADIOLIB_LORAWAN_SESSION_BUF_SIZE); + + //// this code can be used in case breaking chances must be caught: + // uint8_t nvm_table_version = this->bufferNonces[RADIOLIB_LORAWAN_NONCES_VERSION]; + // if (RADIOLIB_LORAWAN_NONCES_VERSION_VAL > nvm_table_version) { + // // set default values for variables that are new or something + // } + + // pull all authentication keys from persistent storage + this->devAddr = LoRaWANNode::ntoh(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_DEV_ADDR]); + memcpy(this->appSKey, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_APP_SKEY], RADIOLIB_AES128_BLOCK_SIZE); + memcpy(this->nwkSEncKey, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_NWK_SENC_KEY], RADIOLIB_AES128_BLOCK_SIZE); + memcpy(this->fNwkSIntKey, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_FNWK_SINT_KEY], RADIOLIB_AES128_BLOCK_SIZE); + memcpy(this->sNwkSIntKey, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_SNWK_SINT_KEY], RADIOLIB_AES128_BLOCK_SIZE); + + // restore session parameters + this->rev = LoRaWANNode::ntoh(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_VERSION]); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("LoRaWAN session: v1.%d", this->rev); + this->homeNetId = LoRaWANNode::ntoh(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_HOMENET_ID]); + this->aFCntDown = LoRaWANNode::ntoh(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_A_FCNT_DOWN]); + this->nFCntDown = LoRaWANNode::ntoh(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_N_FCNT_DOWN]); + this->confFCntUp = LoRaWANNode::ntoh(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_CONF_FCNT_UP]); + this->confFCntDown = LoRaWANNode::ntoh(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_CONF_FCNT_DOWN]); + this->adrFCnt = LoRaWANNode::ntoh(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_ADR_FCNT]); + this->fCntUp = LoRaWANNode::ntoh(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_FCNT_UP]); + + // restore the complete MAC state + + uint8_t cOcts[14] = { 0 }; // TODO explain + uint8_t cid; + uint8_t cLen = 0; + + // setup the default channels + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { + this->selectChannelPlanDyn(); + } else { // type == RADIOLIB_LORAWAN_BAND_FIXED) + this->selectChannelPlanFix(); + } + + // for dynamic bands, the additional channels must be restored per-channel + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { + // all-zero buffer used for checking if MAC commands are set + uint8_t bufferZeroes[RADIOLIB_LORAWAN_MAX_MAC_COMMAND_LEN_DOWN] = { 0 }; + + // restore the session channels + uint8_t *startChannelsUp = &this->bufferSession[RADIOLIB_LORAWAN_SESSION_UL_CHANNELS]; + + cid = RADIOLIB_LORAWAN_MAC_NEW_CHANNEL; + (void)this->getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK); + for(int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + memcpy(cOcts, startChannelsUp + (i * cLen), cLen); + if(memcmp(cOcts, bufferZeroes, cLen) != 0) { // only execute if it is not all zeroes + (void)execMacCommand(cid, cOcts, cLen); + } + } + + uint8_t *startChannelsDown = &this->bufferSession[RADIOLIB_LORAWAN_SESSION_DL_CHANNELS]; + + cid = RADIOLIB_LORAWAN_MAC_DL_CHANNEL; + (void)this->getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK); + for(int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + memcpy(cOcts, startChannelsDown + (i * cLen), cLen); + if(memcmp(cOcts, bufferZeroes, cLen) != 0) { // only execute if it is not all zeroes + (void)execMacCommand(cid, cOcts, cLen); + } + } + } + + // restore the MAC state - ADR needs special care, the rest is straight default + cid = RADIOLIB_LORAWAN_MAC_LINK_ADR; + cLen = 14; // special internal ADR command + memcpy(cOcts, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_LINK_ADR], cLen); + (void)execMacCommand(cid, cOcts, cLen); + + uint8_t cids[6] = { + RADIOLIB_LORAWAN_MAC_DUTY_CYCLE, RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP, + RADIOLIB_LORAWAN_MAC_RX_TIMING_SETUP, RADIOLIB_LORAWAN_MAC_TX_PARAM_SETUP, + RADIOLIB_LORAWAN_MAC_ADR_PARAM_SETUP, RADIOLIB_LORAWAN_MAC_REJOIN_PARAM_SETUP + }; + uint16_t locs[6] = { + RADIOLIB_LORAWAN_SESSION_DUTY_CYCLE, RADIOLIB_LORAWAN_SESSION_RX_PARAM_SETUP, + RADIOLIB_LORAWAN_SESSION_RX_TIMING_SETUP, RADIOLIB_LORAWAN_SESSION_TX_PARAM_SETUP, + RADIOLIB_LORAWAN_SESSION_ADR_PARAM_SETUP, RADIOLIB_LORAWAN_SESSION_REJOIN_PARAM_SETUP + }; + + for(uint8_t i = 0; i < 6; i++) { + (void)this->getMacLen(cids[i], &cLen, RADIOLIB_LORAWAN_DOWNLINK); + memcpy(cOcts, &this->bufferSession[locs[i]], cLen); + (void)execMacCommand(cids[i], cOcts, cLen); + } + + // set the available channels + uint16_t chMask = LoRaWANNode::ntoh(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_AVAILABLE_CHANNELS]); + this->setAvailableChannels(chMask); + + // copy uplink MAC command queue back in place + memcpy(this->fOptsUp, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_MAC_QUEUE], RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN); + memcpy(&this->fOptsUpLen, &this->bufferSession[RADIOLIB_LORAWAN_SESSION_MAC_QUEUE_LEN], 1); + + // as both the Nonces and session are restored, revert to active session + this->bufferNonces[RADIOLIB_LORAWAN_NONCES_ACTIVE] = (uint8_t)true; + + return(state); +} + +int16_t LoRaWANNode::beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKey, uint8_t* appKey) { + if(!appKey) { + return(RADIOLIB_ERR_NULL_POINTER); + } + // clear all the device credentials in case there were any + this->clearNonces(); + + this->joinEUI = joinEUI; + this->devEUI = devEUI; + memcpy(this->appKey, appKey, RADIOLIB_AES128_KEY_SIZE); + if(nwkKey) { + this->rev = 1; + memcpy(this->nwkKey, nwkKey, RADIOLIB_AES128_KEY_SIZE); + } + + // generate activation key checksum + this->keyCheckSum ^= LoRaWANNode::checkSum16(reinterpret_cast(&joinEUI), sizeof(uint64_t)); + this->keyCheckSum ^= LoRaWANNode::checkSum16(reinterpret_cast(&devEUI), sizeof(uint64_t)); + this->keyCheckSum ^= LoRaWANNode::checkSum16(appKey, RADIOLIB_AES128_KEY_SIZE); + if(nwkKey) { + this->keyCheckSum ^= LoRaWANNode::checkSum16(nwkKey, RADIOLIB_AES128_KEY_SIZE); + } + + this->lwMode = RADIOLIB_LORAWAN_MODE_OTAA; + this->lwClass = RADIOLIB_LORAWAN_CLASS_A; + + return(RADIOLIB_ERR_NONE); +} + +int16_t LoRaWANNode::beginABP(uint32_t addr, uint8_t* fNwkSIntKey, uint8_t* sNwkSIntKey, uint8_t* nwkSEncKey, uint8_t* appSKey) { + if(!nwkSEncKey || !appSKey) { + return(RADIOLIB_ERR_NULL_POINTER); + } + // clear all the device credentials in case there were any + this->clearNonces(); + + this->devAddr = addr; + memcpy(this->appSKey, appSKey, RADIOLIB_AES128_KEY_SIZE); + memcpy(this->nwkSEncKey, nwkSEncKey, RADIOLIB_AES128_KEY_SIZE); + if(fNwkSIntKey && sNwkSIntKey) { + this->rev = 1; + memcpy(this->fNwkSIntKey, fNwkSIntKey, RADIOLIB_AES128_KEY_SIZE); + memcpy(this->sNwkSIntKey, sNwkSIntKey, RADIOLIB_AES128_KEY_SIZE); + } else { + memcpy(this->fNwkSIntKey, nwkSEncKey, RADIOLIB_AES128_KEY_SIZE); + memcpy(this->sNwkSIntKey, nwkSEncKey, RADIOLIB_AES128_KEY_SIZE); + } + + // generate activation key checksum + this->keyCheckSum ^= LoRaWANNode::checkSum16(reinterpret_cast(&addr), sizeof(uint32_t)); + this->keyCheckSum ^= LoRaWANNode::checkSum16(nwkSEncKey, RADIOLIB_AES128_KEY_SIZE); + this->keyCheckSum ^= LoRaWANNode::checkSum16(appSKey, RADIOLIB_AES128_KEY_SIZE); + if(fNwkSIntKey) { this->keyCheckSum ^= LoRaWANNode::checkSum16(fNwkSIntKey, RADIOLIB_AES128_KEY_SIZE); } + if(sNwkSIntKey) { this->keyCheckSum ^= LoRaWANNode::checkSum16(sNwkSIntKey, RADIOLIB_AES128_KEY_SIZE); } + + this->lwMode = RADIOLIB_LORAWAN_MODE_ABP; + this->lwClass = RADIOLIB_LORAWAN_CLASS_A; + + return(RADIOLIB_ERR_NONE); +} + +void LoRaWANNode::composeJoinRequest(uint8_t* out) { + // copy devNonce currently in use + uint16_t devNonceUsed = this->devNonce; + + // set the packet fields + out[0] = RADIOLIB_LORAWAN_MHDR_MTYPE_JOIN_REQUEST | RADIOLIB_LORAWAN_MHDR_MAJOR_R1; + LoRaWANNode::hton(&out[RADIOLIB_LORAWAN_JOIN_REQUEST_JOIN_EUI_POS], this->joinEUI); + LoRaWANNode::hton(&out[RADIOLIB_LORAWAN_JOIN_REQUEST_DEV_EUI_POS], this->devEUI); + LoRaWANNode::hton(&out[RADIOLIB_LORAWAN_JOIN_REQUEST_DEV_NONCE_POS], devNonceUsed); + + // add the authentication code + uint32_t mic = 0; + if(this->rev == 1) { + mic =this->generateMIC(out, RADIOLIB_LORAWAN_JOIN_REQUEST_LEN - sizeof(uint32_t), this->nwkKey); + } else { + mic =this->generateMIC(out, RADIOLIB_LORAWAN_JOIN_REQUEST_LEN - sizeof(uint32_t), this->appKey); + } + LoRaWANNode::hton(&out[RADIOLIB_LORAWAN_JOIN_REQUEST_LEN - sizeof(uint32_t)], mic); +} + +int16_t LoRaWANNode::processJoinAccept(LoRaWANJoinEvent_t *joinEvent) { + int16_t state = RADIOLIB_ERR_UNKNOWN; + + // build the buffer for the reply data + uint8_t joinAcceptMsgEnc[RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN]; + + // check received length + size_t lenRx = this->phyLayer->getPacketLength(true); + if((lenRx != RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN) && (lenRx != RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN - RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_LEN)) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("JoinAccept reply length mismatch, expected %dB got %luB", RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN, (unsigned long)lenRx); + return(RADIOLIB_ERR_DOWNLINK_MALFORMED); + } + + // read the packet + state = this->phyLayer->readData(joinAcceptMsgEnc, lenRx); + // downlink frames are sent without CRC, which will raise error on SX127x + // we can ignore that error + if(state != RADIOLIB_ERR_LORA_HEADER_DAMAGED) { + RADIOLIB_ASSERT(state); + } else { + state = RADIOLIB_ERR_NONE; + } + + // check reply message type + if((joinAcceptMsgEnc[0] & RADIOLIB_LORAWAN_MHDR_MTYPE_MASK) != RADIOLIB_LORAWAN_MHDR_MTYPE_JOIN_ACCEPT) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("JoinAccept reply message type invalid, expected 0x%02x got 0x%02x", RADIOLIB_LORAWAN_MHDR_MTYPE_JOIN_ACCEPT, joinAcceptMsgEnc[0]); + return(RADIOLIB_ERR_DOWNLINK_MALFORMED); + } + + // decrypt the join accept message + // this is done by encrypting again in ECB mode + // the first byte is the MAC header which is not encrypted + uint8_t joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN]; + joinAcceptMsg[0] = joinAcceptMsgEnc[0]; + if(this->rev == 1) { + RadioLibAES128Instance.init(this->nwkKey); + } else { + RadioLibAES128Instance.init(this->appKey); + } + RadioLibAES128Instance.encryptECB(&joinAcceptMsgEnc[1], RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN - 1, &joinAcceptMsg[1]); + + // get current joinNonce from downlink + uint32_t joinNonceNew = LoRaWANNode::ntoh(&joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_JOIN_NONCE_POS], 3); + + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("JoinAccept (JoinNonce = %lu, previously %lu):", (unsigned long)joinNonceNew, (unsigned long)this->joinNonce); + RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(joinAcceptMsg, lenRx); + + if(this->rev == 1) { + // for v1.1, the JoinNonce received must be greater than the last joinNonce heard, else error + if((this->joinNonce > 0) && (joinNonceNew <= this->joinNonce)) { + return(RADIOLIB_ERR_JOIN_NONCE_INVALID); + } + } else { + // for v1.0.4, the JoinNonce is simply a non-repeating value (we only check the last value) + if(joinNonceNew == this->joinNonce) { + return(RADIOLIB_ERR_JOIN_NONCE_INVALID); + } + } + this->joinNonce = joinNonceNew; + + this->homeNetId = LoRaWANNode::ntoh(&joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_HOME_NET_ID_POS], 3); + this->devAddr = LoRaWANNode::ntoh(&joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_ADDR_POS]); + + // check LoRaWAN revision (the MIC verification depends on this) + uint8_t dlSettings = joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_DL_SETTINGS_POS]; + this->rev = (dlSettings & RADIOLIB_LORAWAN_JOIN_ACCEPT_R_1_1) >> 7; + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("LoRaWAN revision: 1.%d", this->rev); + + // verify MIC + if(this->rev == 1) { + // 1.1 version, first we need to derive the join accept integrity key + uint8_t keyDerivationBuff[RADIOLIB_AES128_BLOCK_SIZE] = { 0 }; + keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_JS_INT_KEY; + LoRaWANNode::hton(&keyDerivationBuff[1], this->devEUI); + RadioLibAES128Instance.init(this->nwkKey); + RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->jSIntKey); + + // prepare the buffer for MIC calculation + uint8_t micBuff[3*RADIOLIB_AES128_BLOCK_SIZE] = { 0 }; + micBuff[0] = RADIOLIB_LORAWAN_JOIN_REQUEST_TYPE; + LoRaWANNode::hton(&micBuff[1], this->joinEUI); + LoRaWANNode::hton(&micBuff[9], this->devNonce - 1); + memcpy(&micBuff[11], joinAcceptMsg, lenRx); + + if(!verifyMIC(micBuff, lenRx + 11, this->jSIntKey)) { + return(RADIOLIB_ERR_CRC_MISMATCH); + } + + } else { + // 1.0 version + if(!verifyMIC(joinAcceptMsg, lenRx, this->appKey)) { + return(RADIOLIB_ERR_CRC_MISMATCH); + } + + } + + // in case of dynamic band, reset the channels to clear JoinRequest-specific channels + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { + this->selectChannelPlanDyn(false); + } + + uint8_t cOcts[5]; + uint8_t cid = RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP; + uint8_t cLen = 0; + (void)this->getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK); + cOcts[0] = dlSettings & 0x7F; + LoRaWANNode::hton(&cOcts[1], this->channels[RADIOLIB_LORAWAN_DIR_RX2].freq, 3); + (void)execMacCommand(cid, cOcts, cLen); + + cid = RADIOLIB_LORAWAN_MAC_RX_TIMING_SETUP; + (void)this->getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK); + cOcts[0] = joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_RX_DELAY_POS]; + (void)execMacCommand(cid, cOcts, cLen); + + // process CFlist if present (and if CFListType matches used band type) + if(lenRx == RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN && joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_TYPE_POS] == this->band->bandType) { + this->processCFList(&joinAcceptMsg[RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_POS]); + } + // if no (valid) CFList was received, default or subband are already setup so don't need to do anything else + + uint8_t keyDerivationBuff[RADIOLIB_AES128_BLOCK_SIZE] = { 0 }; + LoRaWANNode::hton(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_AES_JOIN_NONCE_POS], this->joinNonce, 3); + + // check protocol version (1.0 vs 1.1) + if(this->rev == 1) { + // 1.1 version, derive the keys + LoRaWANNode::hton(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_AES_JOIN_EUI_POS], this->joinEUI); + LoRaWANNode::hton(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_AES_DEV_NONCE_POS], this->devNonce - 1); + keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_APP_S_KEY; + + RadioLibAES128Instance.init(this->appKey); + RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->appSKey); + + keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_F_NWK_S_INT_KEY; + RadioLibAES128Instance.init(this->nwkKey); + RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->fNwkSIntKey); + + keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_S_NWK_S_INT_KEY; + RadioLibAES128Instance.init(this->nwkKey); + RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->sNwkSIntKey); + + keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_NWK_S_ENC_KEY; + RadioLibAES128Instance.init(this->nwkKey); + RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->nwkSEncKey); + + } else { + // 1.0 version, just derive the keys + LoRaWANNode::hton(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_HOME_NET_ID_POS], this->homeNetId, 3); + LoRaWANNode::hton(&keyDerivationBuff[RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_ADDR_POS], this->devNonce - 1); + keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_APP_S_KEY; + RadioLibAES128Instance.init(this->appKey); + RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->appSKey); + + keyDerivationBuff[0] = RADIOLIB_LORAWAN_JOIN_ACCEPT_F_NWK_S_INT_KEY; + RadioLibAES128Instance.init(this->appKey); + RadioLibAES128Instance.encryptECB(keyDerivationBuff, RADIOLIB_AES128_BLOCK_SIZE, this->fNwkSIntKey); + + memcpy(this->sNwkSIntKey, this->fNwkSIntKey, RADIOLIB_AES128_KEY_SIZE); + memcpy(this->nwkSEncKey, this->fNwkSIntKey, RADIOLIB_AES128_KEY_SIZE); + + } + + // for LW v1.1, send the RekeyInd MAC command + if(this->rev == 1) { + // enqueue the RekeyInd MAC command to be sent in the next uplink + cid = RADIOLIB_LORAWAN_MAC_REKEY; + this->getMacLen(cid, &cLen, RADIOLIB_LORAWAN_UPLINK); + cOcts[0] = this->rev; + state = LoRaWANNode::pushMacCommand(cid, cOcts, this->fOptsUp, &this->fOptsUpLen, RADIOLIB_LORAWAN_UPLINK); + RADIOLIB_ASSERT(state); + } + + LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_JOIN_NONCE], this->joinNonce, 3); + + this->bufferNonces[RADIOLIB_LORAWAN_NONCES_ACTIVE] = (uint8_t)true; + + // generate the signature of the Nonces buffer, and store it in the last two bytes of the Nonces buffer + uint16_t signature = LoRaWANNode::checkSum16(this->bufferNonces, RADIOLIB_LORAWAN_NONCES_BUF_SIZE - 2); + LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_SIGNATURE], signature); + + // store DevAddr and all keys + LoRaWANNode::hton(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_DEV_ADDR], this->devAddr); + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_APP_SKEY], this->appSKey, RADIOLIB_AES128_KEY_SIZE); + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_NWK_SENC_KEY], this->nwkSEncKey, RADIOLIB_AES128_KEY_SIZE); + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_FNWK_SINT_KEY], this->fNwkSIntKey, RADIOLIB_AES128_KEY_SIZE); + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_SNWK_SINT_KEY], this->sNwkSIntKey, RADIOLIB_AES128_KEY_SIZE); + + // set the signature of the Nonces buffer in the Session buffer + LoRaWANNode::hton(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_NONCES_SIGNATURE], signature); + + // store network parameters + LoRaWANNode::hton(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_HOMENET_ID], this->homeNetId); + LoRaWANNode::hton(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_VERSION], this->rev); + + this->isActive = true; + + // received JoinAccept, so update JoinNonce value in event + if(joinEvent) { + joinEvent->joinNonce = this->joinNonce; + } + + return(state); +} + +int16_t LoRaWANNode::activateOTAA(uint8_t joinDr, LoRaWANJoinEvent_t *joinEvent) { + // check if there is an active session + if(this->isActivated()) { + // already activated, don't do anything + return(RADIOLIB_ERR_NONE); + } + if(this->bufferNonces[RADIOLIB_LORAWAN_NONCES_ACTIVE]) { + // session restored but not yet activated - do so now + this->isActive = true; + return(RADIOLIB_LORAWAN_SESSION_RESTORED); + } + + int16_t state = RADIOLIB_ERR_UNKNOWN; + Module* mod = this->phyLayer->getMod(); + + // starting a new session, so make sure to update event fields already + if(joinEvent) { + joinEvent->newSession = true; + joinEvent->devNonce = this->devNonce; + joinEvent->joinNonce = this->joinNonce; + } + + // setup all MAC properties to default values + this->createSession(RADIOLIB_LORAWAN_MODE_OTAA, joinDr); + + // build the JoinRequest message + uint8_t joinRequestMsg[RADIOLIB_LORAWAN_JOIN_REQUEST_LEN]; + this->composeJoinRequest(joinRequestMsg); + + // select a random pair of Tx/Rx channels + state = this->selectChannels(); + RADIOLIB_ASSERT(state); + + // set the physical layer configuration for uplink + state = this->setPhyProperties(&this->channels[RADIOLIB_LORAWAN_UPLINK], + RADIOLIB_LORAWAN_UPLINK, + this->txPowerMax - 2*this->txPowerSteps); + RADIOLIB_ASSERT(state); + + // calculate JoinRequest time-on-air in milliseconds + if(this->dwellTimeEnabledUp) { + RadioLibTime_t toa = this->phyLayer->getTimeOnAir(RADIOLIB_LORAWAN_JOIN_REQUEST_LEN) / 1000; + if(toa > this->dwellTimeUp) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Dwell time exceeded: ToA = %lu, max = %d", (unsigned long)toa, this->dwellTimeUp); + return(RADIOLIB_ERR_DWELL_TIME_EXCEEDED); + } + } + + // if requested, delay until transmitting JoinRequest + RadioLibTime_t tNow = mod->hal->millis(); + if(this->tUplink > tNow) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Delaying transmission by %lu ms", (unsigned long)(this->tUplink - tNow)); + if(this->tUplink > mod->hal->millis()) { + mod->hal->delay(this->tUplink - mod->hal->millis()); + } + } + + // send it + state = this->phyLayer->transmit(joinRequestMsg, RADIOLIB_LORAWAN_JOIN_REQUEST_LEN); + this->rxDelayStart = mod->hal->millis(); + RADIOLIB_ASSERT(state); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("JoinRequest sent (DevNonce = %d) <-- Rx Delay start", this->devNonce); + RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(joinRequestMsg, RADIOLIB_LORAWAN_JOIN_REQUEST_LEN); + + // JoinRequest successfully sent, so increase & save devNonce + this->devNonce += 1; + LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_DEV_NONCE], this->devNonce); + + // set the Time on Air of the JoinRequest + this->lastToA = this->phyLayer->getTimeOnAir(RADIOLIB_LORAWAN_JOIN_REQUEST_LEN) / 1000; + + // configure Rx1 and Rx2 delay for JoinAccept message - these are re-configured once a valid JoinAccept is received + this->rxDelays[1] = RADIOLIB_LORAWAN_JOIN_ACCEPT_DELAY_1_MS; + this->rxDelays[2] = RADIOLIB_LORAWAN_JOIN_ACCEPT_DELAY_2_MS; + + // handle Rx1 and Rx2 windows - returns window > 0 if a downlink is received + state = receiveCommon(RADIOLIB_LORAWAN_DOWNLINK, this->channels, this->rxDelays, 2, this->rxDelayStart); + if(state < RADIOLIB_ERR_NONE) { + return(state); + } else if (state == RADIOLIB_ERR_NONE) { + return(RADIOLIB_ERR_NO_JOIN_ACCEPT); + } + + // process JoinAccept message + state = this->processJoinAccept(joinEvent); + RADIOLIB_ASSERT(state); + + return(RADIOLIB_LORAWAN_NEW_SESSION); +} + +int16_t LoRaWANNode::activateABP(uint8_t initialDr) { + // check if there is an active session + if(this->isActivated()) { + // already activated, don't do anything + return(RADIOLIB_ERR_NONE); + } + if(this->bufferNonces[RADIOLIB_LORAWAN_NONCES_ACTIVE]) { + // session restored but not yet activated - do so now + this->isActive = true; + return(RADIOLIB_LORAWAN_SESSION_RESTORED); + } + + // setup all MAC properties to default values + this->createSession(RADIOLIB_LORAWAN_MODE_ABP, initialDr); + + // new session all good, so set active-bit to true + this->bufferNonces[RADIOLIB_LORAWAN_NONCES_ACTIVE] = (uint8_t)true; + + // generate the signature of the Nonces buffer, and store it in the last two bytes of the Nonces buffer + uint16_t signature = LoRaWANNode::checkSum16(this->bufferNonces, RADIOLIB_LORAWAN_NONCES_BUF_SIZE - 2); + LoRaWANNode::hton(&this->bufferNonces[RADIOLIB_LORAWAN_NONCES_SIGNATURE], signature); + + // store DevAddr and all keys + LoRaWANNode::hton(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_DEV_ADDR], this->devAddr); + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_APP_SKEY], this->appSKey, RADIOLIB_AES128_BLOCK_SIZE); + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_NWK_SENC_KEY], this->nwkSEncKey, RADIOLIB_AES128_BLOCK_SIZE); + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_FNWK_SINT_KEY], this->fNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE); + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_SNWK_SINT_KEY], this->sNwkSIntKey, RADIOLIB_AES128_BLOCK_SIZE); + + // set the signature of the Nonces buffer in the Session buffer + LoRaWANNode::hton(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_NONCES_SIGNATURE], signature); + + // store network parameters + LoRaWANNode::hton(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_HOMENET_ID], this->homeNetId); + LoRaWANNode::hton(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_VERSION], this->rev); + + this->isActive = true; + + return(RADIOLIB_LORAWAN_NEW_SESSION); +} + +void LoRaWANNode::processCFList(uint8_t* cfList) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Processing CFList"); + + uint8_t cOcts[14] = { 0 }; // TODO explain + uint8_t cid; + uint8_t cLen = 0; + + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { + // retrieve number of existing (default) channels + size_t num = 0; + for(int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + if(!this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].enabled) { + break; + } + num++; + } + + cid = RADIOLIB_LORAWAN_MAC_NEW_CHANNEL; + (void)this->getMacLen(cid, &cLen, RADIOLIB_LORAWAN_DOWNLINK); + + uint8_t freqZero[3] = { 0 }; + + // datarate range for all new channels is equal to the default channels + cOcts[4] = (this->band->txFreqs[0].drMax << 4) | this->band->txFreqs[0].drMin; + for(uint8_t i = 0; i < 5; i++, num++) { + // if the frequency fields are all zero, there are no more channels in the CFList + if(memcmp(&cfList[i*3], freqZero, 3) == 0) { + break; + } + cOcts[0] = num; + memcpy(&cOcts[1], &cfList[i*3], 3); + (void)execMacCommand(cid, cOcts, cLen); + } + } else { // RADIOLIB_LORAWAN_BAND_FIXED + // complete channel mask received, so clear all existing channels + for(int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i] = RADIOLIB_LORAWAN_CHANNEL_NONE; + } + + // copy channel mask straight over to LinkAdr MAC command + cid = RADIOLIB_LORAWAN_MAC_LINK_ADR; + cLen = 14; // special internal ADR length + cOcts[0] = 0xFF; // same datarate and cOcts + memcpy(&cOcts[1], cfList, 12); // copy mask + cOcts[13] = 0; // set NbTrans = 0 -> keep the same + (void)execMacCommand(cid, cOcts, cLen); + } + +} + +bool LoRaWANNode::isActivated() { + return(this->isActive); +} + +int16_t LoRaWANNode::isValidUplink(uint8_t* len, uint8_t fPort) { + // check destination fPort + switch(fPort) { + case RADIOLIB_LORAWAN_FPORT_MAC_COMMAND: { + // MAC FPort only good if internally overruled + if (!this->isMACPayload) { + return(RADIOLIB_ERR_INVALID_PORT); + } + // if this is MAC only payload, continue and reset for next uplink + this->isMACPayload = false; + } break; + case RADIOLIB_LORAWAN_FPORT_PAYLOAD_MIN ... RADIOLIB_LORAWAN_FPORT_PAYLOAD_MAX: { + // all good + } break; + case RADIOLIB_LORAWAN_FPORT_TS009: { + // TS009 FPort only good if overruled during verification testing + if(!this->TS009) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Requested uplink at FPort %d - rejected! This FPort is not enabled.", fPort); + return(RADIOLIB_ERR_INVALID_PORT); + } + } break; + case RADIOLIB_LORAWAN_FPORT_TS011: { + // TS011 FPort only good if overruled during relay exchange + if(!this->TS011) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Requested uplink at FPort %d - rejected! This FPort is not enabled.", fPort); + return(RADIOLIB_ERR_INVALID_PORT); + } + } break; + default: { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Requested uplink at FPort %d - rejected! This FPort is reserved.", fPort); + } break; + } + + // check maximum payload len as defined in band + uint8_t maxPayLen = this->band->payloadLenMax[this->channels[RADIOLIB_LORAWAN_UPLINK].dr]; + if(this->TS011) { + maxPayLen = RADIOLIB_MIN(maxPayLen, 230); // payload length is limited to 230 if under repeater + } + if(*len > maxPayLen) { + // normally, throw an error if the packet is too long + if(this->TS009 == false) { + return(RADIOLIB_ERR_PACKET_TOO_LONG); + } + // if testing with TS009 Specification Verification Protocol, don't throw error but clip the message + *len = maxPayLen; + } + + return(RADIOLIB_ERR_NONE); +} + +void LoRaWANNode::adrBackoff() { + // check if we need to do ADR stuff + uint32_t adrLimit = 0x01 << this->adrLimitExp; + uint32_t adrDelay = 0x01 << this->adrDelayExp; + + // check if we already tried everything (adrFCnt == FCNT_NONE) + if(this->adrFCnt == RADIOLIB_LORAWAN_FCNT_NONE) { + return; + } + + // no need to do any backoff for first Limit+Delay uplinks + if((this->fCntUp - this->adrFCnt) < (adrLimit + adrDelay)) { + return; + } + + // only perform backoff every Delay uplinks + if((this->fCntUp - this->adrFCnt - adrLimit) % adrDelay != 0) { + return; + } + + // if we hit the Limit + Delay, try one of three, in order: + // set TxPower to max, set DR to min, enable all default channels + + // if the TxPower field has some offset, remove it and switch to maximum power + if(this->txPowerSteps > 0) { + // set the maximum power supported by both the module and the band + if(this->setTxPower(this->txPowerMax) == RADIOLIB_ERR_NONE) { + return; + } + } + + // try to decrease the datarate + if(this->channels[RADIOLIB_LORAWAN_UPLINK].dr > 0) { + if(this->setDatarate(this->channels[RADIOLIB_LORAWAN_UPLINK].dr - 1) == RADIOLIB_ERR_NONE) { + return; + } + } + + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { + this->selectChannelPlanDyn(false); // revert to default frequencies + } else { + this->selectChannelPlanFix(); // go back to default selected subband + } + this->nbTrans = 1; + + // as there is nothing more to do, set ADR counter to maximum value to indicate that we've tried everything + this->adrFCnt = RADIOLIB_LORAWAN_FCNT_NONE; + + return; +} + +void LoRaWANNode::composeUplink(uint8_t* in, uint8_t lenIn, uint8_t* out, uint8_t fPort, bool isConfirmed) { + // set the packet fields + if(isConfirmed) { + out[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS] = RADIOLIB_LORAWAN_MHDR_MTYPE_CONF_DATA_UP; + this->confFCntUp = this->fCntUp; + } else { + out[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS] = RADIOLIB_LORAWAN_MHDR_MTYPE_UNCONF_DATA_UP; + } + out[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS] |= RADIOLIB_LORAWAN_MHDR_MAJOR_R1; + LoRaWANNode::hton(&out[RADIOLIB_LORAWAN_FHDR_DEV_ADDR_POS], this->devAddr); + + out[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] = 0x00; + if(this->adrEnabled) { + out[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] |= RADIOLIB_LORAWAN_FCTRL_ADR_ENABLED; + + // AdrAckReq is set if no downlink has been received for >=Limit uplinks + // but it is unset once backoff has been completed (which is internally denoted by adrFCnt == FCNT_NONE) + uint32_t adrLimit = 0x01 << this->adrLimitExp; + if(this->adrFCnt != RADIOLIB_LORAWAN_FCNT_NONE && (this->fCntUp - this->adrFCnt) >= adrLimit) { + out[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] |= RADIOLIB_LORAWAN_FCTRL_ADR_ACK_REQ; + } + } + + // check if we have some MAC commands to append + out[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] |= this->fOptsUpLen; + + // if the saved confirm-fCnt is set, set the ACK bit + if(this->confFCntDown != RADIOLIB_LORAWAN_FCNT_NONE) { + out[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] |= RADIOLIB_LORAWAN_FCTRL_ACK; + } + + LoRaWANNode::hton(&out[RADIOLIB_LORAWAN_FHDR_FCNT_POS], (uint16_t)this->fCntUp); + + if(this->fOptsUpLen > 0) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Uplink MAC payload:"); + RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(this->fOptsUp, this->fOptsUpLen); + + if(this->rev == 1) { + // in LoRaWAN v1.1, the FOpts are encrypted using the NwkSEncKey + processAES(this->fOptsUp, this->fOptsUpLen, this->nwkSEncKey, &out[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], this->fCntUp, RADIOLIB_LORAWAN_UPLINK, 0x01, true); + } else { + // in LoRaWAN v1.0.x, the FOpts are unencrypted + memcpy(&out[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], this->fOptsUp, this->fOptsUpLen); + } + + } + + // set the fPort + out[RADIOLIB_LORAWAN_FHDR_FPORT_POS(this->fOptsUpLen)] = fPort; + + // select encryption key based on the target fPort + uint8_t* encKey = this->appSKey; + if((fPort == RADIOLIB_LORAWAN_FPORT_MAC_COMMAND) || (fPort == RADIOLIB_LORAWAN_FPORT_TS011)) { + encKey = this->nwkSEncKey; + } + + // encrypt the frame payload + processAES(in, lenIn, encKey, &out[RADIOLIB_LORAWAN_FRAME_PAYLOAD_POS(this->fOptsUpLen)], this->fCntUp, RADIOLIB_LORAWAN_UPLINK, 0x00, true); +} + +void LoRaWANNode::micUplink(uint8_t* inOut, uint8_t lenInOut) { + // create blocks for MIC calculation + uint8_t block0[RADIOLIB_AES128_BLOCK_SIZE] = { 0 }; + block0[RADIOLIB_LORAWAN_BLOCK_MAGIC_POS] = RADIOLIB_LORAWAN_MIC_BLOCK_MAGIC; + block0[RADIOLIB_LORAWAN_BLOCK_DIR_POS] = RADIOLIB_LORAWAN_UPLINK; + LoRaWANNode::hton(&block0[RADIOLIB_LORAWAN_BLOCK_DEV_ADDR_POS], this->devAddr); + LoRaWANNode::hton(&block0[RADIOLIB_LORAWAN_BLOCK_FCNT_POS], this->fCntUp); + block0[RADIOLIB_LORAWAN_MIC_BLOCK_LEN_POS] = lenInOut - RADIOLIB_AES128_BLOCK_SIZE - sizeof(uint32_t); + + uint8_t block1[RADIOLIB_AES128_BLOCK_SIZE] = { 0 }; + memcpy(block1, block0, RADIOLIB_AES128_BLOCK_SIZE); + if(this->confFCntDown != RADIOLIB_LORAWAN_FCNT_NONE) { + LoRaWANNode::hton(&block1[RADIOLIB_LORAWAN_BLOCK_CONF_FCNT_POS], (uint16_t)this->confFCntDown); + } + block1[RADIOLIB_LORAWAN_MIC_DATA_RATE_POS] = this->channels[RADIOLIB_LORAWAN_UPLINK].dr; + block1[RADIOLIB_LORAWAN_MIC_CH_INDEX_POS] = this->channels[RADIOLIB_LORAWAN_UPLINK].idx; + + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Uplink (FCntUp = %lu) decoded:", (unsigned long)this->fCntUp); + RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(inOut, lenInOut); + + // calculate authentication codes + memcpy(inOut, block1, RADIOLIB_AES128_BLOCK_SIZE); + uint32_t micS = this->generateMIC(inOut, lenInOut - sizeof(uint32_t), this->sNwkSIntKey); + memcpy(inOut, block0, RADIOLIB_AES128_BLOCK_SIZE); + uint32_t micF = this->generateMIC(inOut, lenInOut - sizeof(uint32_t), this->fNwkSIntKey); + + // check LoRaWAN revision + if(this->rev == 1) { + uint32_t mic = ((uint32_t)(micF & 0x0000FF00) << 16) | ((uint32_t)(micF & 0x0000000FF) << 16) | ((uint32_t)(micS & 0x0000FF00) >> 0) | ((uint32_t)(micS & 0x0000000FF) >> 0); + LoRaWANNode::hton(&inOut[lenInOut - sizeof(uint32_t)], mic); + } else { + LoRaWANNode::hton(&inOut[lenInOut - sizeof(uint32_t)], micF); + } +} + +int16_t LoRaWANNode::transmitUplink(LoRaWANChannel_t* chnl, uint8_t* in, uint8_t len, bool retrans) { + int16_t state = RADIOLIB_ERR_UNKNOWN; + Module* mod = this->phyLayer->getMod(); + + // check if the Rx windows were closed after sending the previous uplink + // this FORCES a user to call downlink() after an uplink() + if(this->rxDelayEnd < this->rxDelayStart) { + // not enough time elapsed since the last uplink, we may still be in an Rx window + return(RADIOLIB_ERR_UPLINK_UNAVAILABLE); + } + + RadioLibTime_t tNow = mod->hal->millis(); + // if scheduled uplink time is in the past, reschedule to now + if(this->tUplink < tNow) { + this->tUplink = tNow; + } + + // if dutycycle is enabled and the time since last uplink + interval has not elapsed, return an error + // but: don't check this for retransmissions + if(!retrans && this->dutyCycleEnabled) { + if(this->rxDelayStart + (RadioLibTime_t)dutyCycleInterval(this->dutyCycle, this->lastToA) > this->tUplink) { + return(RADIOLIB_ERR_UPLINK_UNAVAILABLE); + } + } + + // set the physical layer configuration for uplink + state = this->setPhyProperties(chnl, + RADIOLIB_LORAWAN_UPLINK, + this->txPowerMax - 2*this->txPowerSteps); + RADIOLIB_ASSERT(state); + + // if requested, wait until transmitting uplink + tNow = mod->hal->millis(); + if(this->tUplink > tNow) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Delaying transmission by %lu ms", (unsigned long)(this->tUplink - tNow)); + if(this->tUplink > mod->hal->millis()) { + mod->hal->delay(this->tUplink - mod->hal->millis()); + } + } + + state = this->phyLayer->transmit(in, len); + + // set the timestamp so that we can measure when to start receiving + this->rxDelayStart = mod->hal->millis(); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Uplink sent <-- Rx Delay start"); + + // increase Time on Air of the uplink sequence + this->lastToA += this->phyLayer->getTimeOnAir(len) / 1000; + + return(state); +} + +// flag to indicate whether there was some action during Rx mode (timeout or downlink) +static volatile bool downlinkAction = false; + +// interrupt service routine to handle downlinks automatically +#if defined(ESP8266) || defined(ESP32) + IRAM_ATTR +#endif +static void LoRaWANNodeOnDownlinkAction(void) { + downlinkAction = true; +} + +int16_t LoRaWANNode::receiveCommon(uint8_t dir, const LoRaWANChannel_t* dlChannels, const RadioLibTime_t* dlDelays, uint8_t numWindows, RadioLibTime_t tReference) { + Module* mod = this->phyLayer->getMod(); + + int16_t state = RADIOLIB_ERR_UNKNOWN; + + // check if there are any upcoming Rx windows + // if the Rx1 window has already started, you're too late, because most downlinks happen in Rx1 + RadioLibTime_t now = mod->hal->millis(); // fix the current timestamp to prevent negative delays + if(now > tReference + dlDelays[1] - this->scanGuard) { + // if function was called while Rx windows are in progress, + // wait until last window closes to prevent very bad stuff + if(now < tReference + dlDelays[numWindows]) { + mod->hal->delay(dlDelays[numWindows] + tReference - now); + } + // update the end timestamp in case user got stuck between uplink and downlink + this->rxDelayEnd = mod->hal->millis(); + return(RADIOLIB_ERR_NO_RX_WINDOW); + } + + // setup interrupt + this->phyLayer->setPacketReceivedAction(LoRaWANNodeOnDownlinkAction); + + RadioLibTime_t tOpen = 0; + int16_t timedOut = 0; + + // listen during the specified windows + uint8_t window = 1; + for(; window <= numWindows; window++) { + downlinkAction = false; + + // set the physical layer configuration for downlink + this->phyLayer->standby(); + state = this->setPhyProperties(&dlChannels[window], dir, this->txPowerMax - 2*this->txPowerSteps); + RADIOLIB_ASSERT(state); + + // calculate the Rx timeout + RadioLibTime_t timeoutHost = this->phyLayer->getTimeOnAir(0) + 2*this->scanGuard*1000; + RadioLibTime_t timeoutMod = this->phyLayer->calculateRxTimeout(timeoutHost); + + // wait for the start of the Rx window + RadioLibTime_t waitLen = tReference + dlDelays[window] - mod->hal->millis(); + // make sure that no underflow occured; if so, clip the delay (although this will likely miss any downlink) + if(waitLen > dlDelays[window]) { + waitLen = dlDelays[window]; + } + // the waiting duration is shortened a bit to cover any possible timing errors + if(waitLen > this->scanGuard) { + waitLen -= this->scanGuard; + } + mod->hal->delay(waitLen); + + // open Rx window by starting receive with specified timeout + // TODO remove default arguments + state = this->phyLayer->startReceive(timeoutMod, RADIOLIB_IRQ_RX_DEFAULT_FLAGS, RADIOLIB_IRQ_RX_DEFAULT_MASK, 0); + tOpen = mod->hal->millis(); + RADIOLIB_ASSERT(state); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Opening Rx%d window (%d ms timeout)... <-- Rx Delay end ", window, (int)(timeoutHost / 1000 + scanGuard / 2)); + + // wait for the timeout to complete (and a small additional delay) + mod->hal->delay(timeoutHost / 1000 + this->scanGuard / 2); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Closing Rx%d window", window); + + // if the IRQ bit for Rx Timeout is not set, something is received, so stop the windows + timedOut = this->phyLayer->checkIrq(RADIOLIB_IRQ_TIMEOUT); + if(timedOut == RADIOLIB_ERR_UNSUPPORTED) { + return(timedOut); + } + if(!timedOut) { + break; + } + } + // Rx windows are now closed + this->rxDelayEnd = mod->hal->millis(); + + // if we got here due to a timeout, stop ongoing activities + if(timedOut) { + this->phyLayer->standby(); + return(RADIOLIB_ERR_NONE); + } + + // get the maximum allowed Time-on-Air of a packet given the current datarate + uint8_t maxPayLen = this->band->payloadLenMax[dlChannels[window].dr]; + if(this->TS011) { + maxPayLen = RADIOLIB_MIN(maxPayLen, 222); // payload length is limited to 222 if under repeater + } + RadioLibTime_t tMax = this->phyLayer->getTimeOnAir(maxPayLen + 13) / 1000; // mandatory FHDR is 12/13 bytes + bool downlinkComplete = true; + + // wait for the DIO to fire indicating a downlink is received + while(!downlinkAction) { + mod->hal->yield(); + // stay in Rx mode for the maximum allowed Time-on-Air plus small grace period + if(mod->hal->millis() - tOpen > tMax + scanGuard) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Downlink missing!"); + downlinkComplete = false; + break; + } + } + + // update time of downlink reception + if(downlinkComplete) { + this->tDownlink = mod->hal->millis(); + } + + // we have a message, clear actions, go to standby + this->phyLayer->clearPacketReceivedAction(); + this->phyLayer->standby(); + + // if all windows passed without receiving anything, set return value to 0 + if(!downlinkComplete) { + state = 0; + + // if we received something during a window, set return value to the window number + } else { + state = window; + } + + // Any frame received by an end-device containing a MACPayload greater than + // the specified maximum length M over the data rate used to receive the frame + // SHALL be silently discarded. + if(this->phyLayer->getPacketLength() > (size_t)(maxPayLen + 13)) { // mandatory FHDR is 12/13 bytes + return(0); // act as if no downlink was received + } + + return(state); +} + +int16_t LoRaWANNode::parseDownlink(uint8_t* data, size_t* len, LoRaWANEvent_t* event) { + int16_t state = RADIOLIB_ERR_UNKNOWN; + + // set user-data length to 0 to prevent undefined behaviour in case of bad use + // if there is user-data, this will be handled at the appropriate place + *len = 0; + + // get the packet length + size_t downlinkMsgLen = this->phyLayer->getPacketLength(); + + // check the minimum required frame length + // an extra byte is subtracted because downlink frames may not have a fPort + if(downlinkMsgLen < RADIOLIB_LORAWAN_FRAME_LEN(0, 0) - 1 - RADIOLIB_AES128_BLOCK_SIZE) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Downlink message too short (%lu bytes)", (unsigned long)downlinkMsgLen); + return(RADIOLIB_ERR_DOWNLINK_MALFORMED); + } + + // build the buffer for the downlink message + // the first 16 bytes are reserved for MIC calculation block + #if !RADIOLIB_STATIC_ONLY + uint8_t* downlinkMsg = new uint8_t[RADIOLIB_AES128_BLOCK_SIZE + downlinkMsgLen]; + #else + uint8_t downlinkMsg[RADIOLIB_STATIC_ARRAY_SIZE]; + #endif + + // read the data + state = this->phyLayer->readData(&downlinkMsg[RADIOLIB_AES128_BLOCK_SIZE], downlinkMsgLen); + // downlink frames are sent without CRC, which will raise error on SX127x + // we can ignore that error + if(state == RADIOLIB_ERR_LORA_HEADER_DAMAGED) { + state = RADIOLIB_ERR_NONE; + } + + if(state != RADIOLIB_ERR_NONE) { + #if !RADIOLIB_STATIC_ONLY + delete[] downlinkMsg; + #endif + return(state); + } + + // check the address + uint32_t addr = LoRaWANNode::ntoh(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_DEV_ADDR_POS]); + if(addr != this->devAddr) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Device address mismatch, expected 0x%08lX, got 0x%08lX", + (unsigned long)this->devAddr, (unsigned long)addr); + #if !RADIOLIB_STATIC_ONLY + delete[] downlinkMsg; + #endif + return(RADIOLIB_ERR_DOWNLINK_MALFORMED); + } + + // calculate length of piggy-backed FOpts + uint8_t fOptsPbLen = downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] & RADIOLIB_LORAWAN_FHDR_FOPTS_LEN_MASK; + + // MHDR(1) - DevAddr(4) - FCtrl(1) - FCnt(2) - FOptsPb - Payload - MIC(4) + // potentially also an FPort, will find out next + uint8_t payLen = downlinkMsgLen - 1 - 4 - 1 - 2 - fOptsPbLen - 4; + + // in LoRaWAN v1.1, a frame is a Network frame if there is no Application payload + // i.e.: either no payload at all (empty frame or FOpts only), or MAC only payload + uint8_t fPort = RADIOLIB_LORAWAN_FPORT_MAC_COMMAND; + bool isAppDownlink = false; + if(this->rev == 0) { + isAppDownlink = true; + } + if(payLen > 0) { + payLen -= 1; // subtract one as fPort is set + fPort = downlinkMsg[RADIOLIB_LORAWAN_FHDR_FPORT_POS(fOptsPbLen)]; + + // check if fPort value is actually allowed + switch(fPort) { + case RADIOLIB_LORAWAN_FPORT_MAC_COMMAND: { + // payload consists of all MAC commands (or is empty) + } break; + case RADIOLIB_LORAWAN_FPORT_PAYLOAD_MIN ... RADIOLIB_LORAWAN_FPORT_PAYLOAD_MAX: { + // payload is user-defined (or empty) - may carry piggybacked MAC commands + isAppDownlink = true; + } break; + case RADIOLIB_LORAWAN_FPORT_TS009: { + // TS009 FPort only good if overruled during verification testing + if(!this->TS009) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Downlink at FPort %d - rejected! This FPort is not enabled.", fPort); + #if !RADIOLIB_STATIC_ONLY + delete[] downlinkMsg; + #endif + return(RADIOLIB_ERR_INVALID_PORT); + } + isAppDownlink = true; + } break; + case RADIOLIB_LORAWAN_FPORT_TS011: { + // TS011 FPort only good if overruled during relay exchange + if(!this->TS011) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Downlink at FPort %d - rejected! This FPort is not enabled.", fPort); + #if !RADIOLIB_STATIC_ONLY + delete[] downlinkMsg; + #endif + return(RADIOLIB_ERR_INVALID_PORT); + } + isAppDownlink = true; + } break; + default: { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Downlink at FPort %d - rejected! This FPort is reserved.", fPort); + #if !RADIOLIB_STATIC_ONLY + delete[] downlinkMsg; + #endif + return(RADIOLIB_ERR_INVALID_PORT); + } break; + } + + } + + // MAC commands SHALL NOT be present in the payload field and the frame options field simultaneously. + // Should this occur, the end-device SHALL silently discard the frame. + if(fOptsPbLen > 0 && payLen > 0 && fPort == RADIOLIB_LORAWAN_FPORT_MAC_COMMAND) { + #if !RADIOLIB_STATIC_ONLY + delete[] downlinkMsg; + #endif + return(RADIOLIB_ERR_INVALID_PORT); + } + + // get the frame counter + uint16_t fCnt16 = LoRaWANNode::ntoh(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCNT_POS]); + + // check the fCntDown value (Network or Application) + uint32_t fCntDownPrev = 0; + if (isAppDownlink) { + fCntDownPrev = this->aFCntDown; + } else { + fCntDownPrev = this->nFCntDown; + } + + // if this is not the first downlink... + // assume a 16-bit to 32-bit rollover if difference between counters in LSB is smaller than MAX_FCNT_GAP + // if that isn't the case and the received fCnt is smaller or equal to the last heard fCnt, then error + uint32_t fCnt32 = fCnt16; + if(fCntDownPrev > 0) { + if((fCnt16 <= fCntDownPrev) && ((0xFFFF - (uint16_t)fCntDownPrev + fCnt16) > RADIOLIB_LORAWAN_MAX_FCNT_GAP)) { + #if !RADIOLIB_STATIC_ONLY + delete[] downlinkMsg; + #endif + if (isAppDownlink) { + return(RADIOLIB_ERR_A_FCNT_DOWN_INVALID); + } else { + return(RADIOLIB_ERR_N_FCNT_DOWN_INVALID); + } + } else if (fCnt16 <= fCntDownPrev) { + uint16_t msb = (fCntDownPrev >> 16) + 1; // assume a rollover + fCnt32 |= ((uint32_t)msb << 16); // add back the MSB part + } + } + + // check if the ACK bit is set, indicating this frame acknowledges the previous uplink + bool isConfirmingUp = false; + if((downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] & RADIOLIB_LORAWAN_FCTRL_ACK)) { + isConfirmingUp = true; + } + + // set the MIC calculation blocks + memset(downlinkMsg, 0x00, RADIOLIB_AES128_BLOCK_SIZE); + downlinkMsg[RADIOLIB_LORAWAN_BLOCK_MAGIC_POS] = RADIOLIB_LORAWAN_MIC_BLOCK_MAGIC; + // if this downlink is confirming an uplink, the MIC was generated with the least-significant 16 bits of that fCntUp + // (LoRaWAN v1.1 only) + if(isConfirmingUp && (this->rev == 1)) { + LoRaWANNode::hton(&downlinkMsg[RADIOLIB_LORAWAN_BLOCK_CONF_FCNT_POS], (uint16_t)this->confFCntUp); + } + downlinkMsg[RADIOLIB_LORAWAN_BLOCK_DIR_POS] = RADIOLIB_LORAWAN_DOWNLINK; + LoRaWANNode::hton(&downlinkMsg[RADIOLIB_LORAWAN_BLOCK_DEV_ADDR_POS], this->devAddr); + LoRaWANNode::hton(&downlinkMsg[RADIOLIB_LORAWAN_BLOCK_FCNT_POS], fCnt32); + downlinkMsg[RADIOLIB_LORAWAN_MIC_BLOCK_LEN_POS] = downlinkMsgLen - sizeof(uint32_t); + + // check the MIC + if(!verifyMIC(downlinkMsg, RADIOLIB_AES128_BLOCK_SIZE + downlinkMsgLen, this->sNwkSIntKey)) { + #if !RADIOLIB_STATIC_ONLY + delete[] downlinkMsg; + #endif + return(RADIOLIB_ERR_CRC_MISMATCH); + } + + // save current fCnt to respective frame counter + if (isAppDownlink) { + this->aFCntDown = fCnt32; + } else { + this->nFCntDown = fCnt32; + } + + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Downlink (%sFCntDown = %lu) encoded:", + isAppDownlink ? "A" : "N", + (unsigned long)(isAppDownlink ? this->aFCntDown : this->nFCntDown)); + RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(downlinkMsg, RADIOLIB_AES128_BLOCK_SIZE + downlinkMsgLen); + + // if this is a confirmed frame, save the downlink number (only app frames can be confirmed) + bool isConfirmedDown = false; + if((downlinkMsg[RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS] & 0xFE) == RADIOLIB_LORAWAN_MHDR_MTYPE_CONF_DATA_DOWN) { + this->confFCntDown = this->aFCntDown; + isConfirmedDown = true; + } + + // a downlink was received, so reset the ADR counter to the last uplink's fCnt + this->adrFCnt = this->getFCntUp(); + + // if this downlink is on FPort 0, the FOptsLen is the length of the payload + // in any other case, the payload (length) is user accessible + uint8_t fOptsLen = fOptsPbLen; + if(fPort == RADIOLIB_LORAWAN_FPORT_MAC_COMMAND && payLen > 0) { + fOptsLen = payLen; + } else { + *len = payLen; + } + + #if !RADIOLIB_STATIC_ONLY + uint8_t* fOpts = new uint8_t[fOptsLen]; + #else + uint8_t fOpts[RADIOLIB_STATIC_ARRAY_SIZE]; + #endif + + // figure out if the payload should end up in user data or internal FOpts buffer + uint8_t* dest; + if(fPort == RADIOLIB_LORAWAN_FPORT_MAC_COMMAND) { + dest = fOpts; + } else { + dest = data; + } + + // figure out which key to use to decrypt the payload + uint8_t* encKey = this->appSKey; + if((fPort == RADIOLIB_LORAWAN_FPORT_MAC_COMMAND) || (fPort == RADIOLIB_LORAWAN_FPORT_TS011)) { + encKey = this->nwkSEncKey; + } + + // decrypt the frame payload + processAES(&downlinkMsg[RADIOLIB_LORAWAN_FRAME_PAYLOAD_POS(fOptsPbLen)], payLen, encKey, dest, fCnt32, RADIOLIB_LORAWAN_DOWNLINK, 0x00, true); + + // decrypt any piggy-backed FOpts + if(fOptsPbLen > 0) { + // the decryption depends on the LoRaWAN version + if(this->rev == 1) { + // in LoRaWAN v1.1, the piggy-backed FOpts are encrypted using the NwkSEncKey + uint8_t ctrId = 0x01 + isAppDownlink; // see LoRaWAN v1.1 errata + processAES(&downlinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], (size_t)fOptsPbLen, this->nwkSEncKey, fOpts, fCnt32, RADIOLIB_LORAWAN_DOWNLINK, ctrId, true); + } else { + // in LoRaWAN v1.0.x, the piggy-backed FOpts are unencrypted + memcpy(fOpts, &downlinkMsg[RADIOLIB_LORAWAN_FHDR_FOPTS_POS], (size_t)fOptsPbLen); + } + } + + // clear the previous MAC commands, if any + memset(this->fOptsDown, 0, RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN); + + // process FOpts (if there are any) + uint8_t cid; + uint8_t fLen = 1; + uint8_t* mPtr = fOpts; + uint8_t procLen = 0; + + #if !RADIOLIB_STATIC_ONLY + uint8_t* fOptsRe = new uint8_t[250]; + #else + uint8_t fOptsRe[RADIOLIB_STATIC_ARRAY_SIZE]; + #endif + uint8_t fOptsReLen = 0; + + // indication whether LinkAdr MAC command has been processed + bool mAdr = false; + + while(procLen < fOptsLen) { + cid = *mPtr; // MAC id is the first byte + state = this->getMacLen(cid, &fLen, RADIOLIB_LORAWAN_DOWNLINK, true); + RADIOLIB_ASSERT(state); + uint8_t fLenRe = 0; + state = this->getMacLen(cid, &fLenRe, RADIOLIB_LORAWAN_UPLINK, true); + RADIOLIB_ASSERT(state); + + if(procLen + fLen > fOptsLen) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Incomplete MAC command %02x (%d bytes, expected %d)", cid, fOptsLen, fLen); + return(RADIOLIB_ERR_INVALID_CID); + } + + bool reply = false; + + // if this is a LinkAdr MAC command, pre-process contiguous commands into one atomic block + if(cid == RADIOLIB_LORAWAN_MAC_LINK_ADR) { + // if there was any LinkAdr command before, set NACK and continue without processing + if(mAdr) { + reply = true; + fOptsRe[fOptsReLen + 1] = 0x00; + + // if this is the first LinkAdr command, do some special treatment: + } else { + mAdr = true; + uint8_t fAdrLen = 5; + uint8_t mAdrOpt[14] = { 0 }; + + // retrieve all contiguous LinkAdr commands + while(procLen + fLen + fAdrLen < fOptsLen + 1 && *(mPtr + fLen) == RADIOLIB_LORAWAN_MAC_LINK_ADR) { + fLen += 5; // ADR command is 5 bytes + fLenRe += 2; // ADR response is 2 bytes + } + + // pre-process them into a single complete channel mask (stored in mAdrOpt) + LoRaWANNode::preprocessMacLinkAdr(mPtr, fLen, mAdrOpt); + + // execute like a normal MAC command (but pointing to mAdrOpt instead) + reply = this->execMacCommand(cid, mAdrOpt, 14, &fOptsRe[fOptsReLen + 1]); + + // in LoRaWAN v1.0.x, all ACK bytes should have equal status - fix in post-processing + if(this->rev == 0) { + LoRaWANNode::postprocessMacLinkAdr(&fOptsRe[fOptsReLen], fLen); + + // in LoRaWAN v1.1, just provide one ACK, so no post-processing but cut off reply length + } else { + fLenRe = 2; + } + } + + // MAC command other than LinkAdr, just process the payload + } else { + reply = this->execMacCommand(cid, mPtr + 1, fLen - 1, &fOptsRe[fOptsReLen + 1]); + } + + if(reply) { + fOptsRe[fOptsReLen] = cid; + fOptsReLen += fLenRe; + } + + procLen += fLen; + mPtr += fLen; + } + + // remove all MAC commands except those whose payload can be requested by the user + // (which are LinkCheck and DeviceTime) + if(fOptsLen > 0) { + LoRaWANNode::clearMacCommands(fOpts, &fOptsLen, RADIOLIB_LORAWAN_DOWNLINK); + memcpy(this->fOptsDown, fOpts, fOptsLen); + } + this->fOptsDownLen = fOptsLen; + + // if fOptsLen for the next uplink is larger than can be piggybacked onto an uplink, send separate uplink + if(fOptsReLen > RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN) { + + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Uplink MAC-only payload (%d bytes):", fOptsReLen); + RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(fOptsRe, fOptsReLen); + + this->isMACPayload = true; + // temporarily lift dutyCycle restrictions to allow immediate MAC response + bool prevDC = this->dutyCycleEnabled; + this->dutyCycleEnabled = false; + + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Sending MAC-only uplink .. "); + + this->sendReceive(fOptsRe, fOptsReLen, RADIOLIB_LORAWAN_FPORT_MAC_COMMAND); + + this->dutyCycleEnabled = prevDC; + + } else { // fOptsReLen <= 15 + memcpy(this->fOptsUp, fOptsRe, fOptsReLen); + this->fOptsUpLen = fOptsReLen; + } + + // pass the extra info if requested + if(event) { + event->dir = RADIOLIB_LORAWAN_DOWNLINK; + event->confirmed = isConfirmedDown; + event->confirming = isConfirmingUp; + event->frmPending = (downlinkMsg[RADIOLIB_LORAWAN_FHDR_FCTRL_POS] & RADIOLIB_LORAWAN_FCTRL_FRAME_PENDING) != 0; + event->datarate = this->channels[RADIOLIB_LORAWAN_DOWNLINK].dr; + event->freq = channels[event->dir].freq / 10000.0; + event->power = this->txPowerMax - this->txPowerSteps * 2; + event->fCnt = isAppDownlink ? this->aFCntDown : this->nFCntDown; + event->fPort = fPort; + } + + #if !RADIOLIB_STATIC_ONLY + delete[] fOpts; + delete[] fOptsRe; + delete[] downlinkMsg; + #endif + + return(RADIOLIB_ERR_NONE); +} + +bool LoRaWANNode::execMacCommand(uint8_t cid, uint8_t* optIn, uint8_t lenIn) { + uint8_t buff[RADIOLIB_LORAWAN_MAX_MAC_COMMAND_LEN_DOWN]; + return(this->execMacCommand(cid, optIn, lenIn, buff)); +} + +bool LoRaWANNode::execMacCommand(uint8_t cid, uint8_t* optIn, uint8_t lenIn, uint8_t* optOut) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("[MAC] 0x%02x", cid); + RADIOLIB_DEBUG_PROTOCOL_HEXDUMP(optIn, lenIn); + + if(cid >= RADIOLIB_LORAWAN_MAC_PROPRIETARY) { + // TODO call user-provided callback for proprietary MAC commands? + return(false); + } + + switch(cid) { + case(RADIOLIB_LORAWAN_MAC_RESET): { + // get the server version + uint8_t srvVersion = optIn[0]; + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("ResetConf: server version 1.%d", srvVersion); + if(srvVersion == this->rev) { + // valid server version, stop sending the ResetInd MAC command + LoRaWANNode::deleteMacCommand(RADIOLIB_LORAWAN_MAC_RESET, this->fOptsUp, &this->fOptsUpLen, RADIOLIB_LORAWAN_UPLINK); + } + return(false); + } break; + + case(RADIOLIB_LORAWAN_MAC_LINK_CHECK): { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("LinkCheckAns: [user]"); + + return(false); + } break; + + case(RADIOLIB_LORAWAN_MAC_LINK_ADR): { + // get the ADR configuration + uint8_t macDrUp = (optIn[0] & 0xF0) >> 4; + uint8_t macTxSteps = optIn[0] & 0x0F; + + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("LinkAdrReq: dataRate = %d, txSteps = %d, nbTrans = %d", macDrUp, macTxSteps, lenIn > 1 ? optIn[13] : 0); + + uint8_t chMaskAck = 0; + uint8_t drAck = 0; + uint8_t pwrAck = 0; + + // first, get current configuration + uint64_t chMaskGrp0123 = 0; + uint32_t chMaskGrp45 = 0; + this->getChannelPlanMask(&chMaskGrp0123, &chMaskGrp45); + uint16_t chMaskActive = 0; + (void)this->getAvailableChannels(&chMaskActive); + uint8_t currentDr = this->channels[RADIOLIB_LORAWAN_UPLINK].dr; + + // only apply channel mask if present (internal Dr/Tx commands do not set channel mask) + if(lenIn > 1) { + uint64_t macChMaskGrp0123 = LoRaWANNode::ntoh(&optIn[1]); + uint32_t macChMaskGrp45 = LoRaWANNode::ntoh(&optIn[9]); + // apply requested channel mask and enable all of them for testing datarate + chMaskAck = this->applyChannelMask(macChMaskGrp0123, macChMaskGrp45); + } else { + chMaskAck = true; + } + + this->setAvailableChannels(0xFFFF); + + int16_t state; + + // try to apply the datarate configuration + // if value is set to 'keep current values', retrieve current value + if(macDrUp == 0x0F) { + macDrUp = currentDr; + } + + if (this->band->dataRates[macDrUp] != RADIOLIB_LORAWAN_DATA_RATE_UNUSED) { + // check if the module supports this data rate + DataRate_t dr; + state = this->findDataRate(macDrUp, &dr); + + // if datarate in hardware all good, set datarate for now + // and check if there are any available Tx channels for this datarate + if(state == RADIOLIB_ERR_NONE) { + this->channels[RADIOLIB_LORAWAN_UPLINK].dr = macDrUp; + + // only if we have available Tx channels, we set an Ack + if(this->getAvailableChannels(NULL) > 0) { + drAck = 1; + } else { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("ADR: no channels available for datarate %d", macDrUp); + } + } else { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("ADR: hardware failure configurating datarate %d, code %d", macDrUp, state); + } + + } + + // try to apply the power configuration + // if value is set to 'keep current values', retrieve current value + if(macTxSteps == 0x0F) { + macTxSteps = this->txPowerSteps; + } + + int8_t power = this->txPowerMax - 2*macTxSteps; + int8_t powerActual = 0; + state = this->phyLayer->checkOutputPower(power, &powerActual); + // only acknowledge if the radio is able to operate at or below the requested power level + if(state == RADIOLIB_ERR_NONE || (state == RADIOLIB_ERR_INVALID_OUTPUT_POWER && powerActual < power)) { + pwrAck = 1; + this->txPowerSteps = macTxSteps; + } else { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("ADR failed to configure Tx power %d, code %d!", power, state); + } + + // set ACK bits + optOut[0] = (pwrAck << 2) | (drAck << 1) | (chMaskAck << 0); + + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("LinkAdrAns: %02x", optOut[0]); + + // if ACK not completely successful, revert and stop + if(optOut[0] != 0x07) { + this->applyChannelMask(chMaskGrp0123, chMaskGrp45); + this->setAvailableChannels(chMaskActive); + this->channels[RADIOLIB_LORAWAN_UPLINK].dr = currentDr; + // Tx power was not modified + return(true); + } + + // ACK successful, so apply and save + this->txPowerSteps = macTxSteps; + if(lenIn > 1) { + uint8_t macNbTrans = optIn[13] & 0x0F; + + // if there is a value for NbTrans > 0, apply it + if(macNbTrans) { + this->nbTrans = macNbTrans; + } else { + // for LoRaWAN v1.0.4, if NbTrans == 0, the end-device SHALL use the default value (being 1) + if(this->rev == 0) { + this->nbTrans = 1; + } + // for LoRaWAN v1.1, if NbTrans == 0, the end-device SHALL keep the current NbTrans value unchanged + // so, don't do anything + } + + } + + // restore original active channels + this->setAvailableChannels(chMaskActive); + + // save to the ADR MAC location + // but first re-set the Dr/Tx/NbTrans field to make sure they're not set to 0xF + optIn[0] = (this->channels[RADIOLIB_LORAWAN_UPLINK].dr) << 4; + optIn[0] |= this->txPowerSteps; + if(lenIn > 1) { + optIn[13] = this->nbTrans; + } + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_LINK_ADR], optIn, lenIn); + + return(true); + } break; + + case(RADIOLIB_LORAWAN_MAC_DUTY_CYCLE): { + uint8_t maxDutyCycle = optIn[0] & 0x0F; + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("DutyCycleReq: max duty cycle = 1/2^%d", maxDutyCycle); + if(maxDutyCycle == 0) { + this->dutyCycle = this->band->dutyCycle; + } else { + this->dutyCycle = (RadioLibTime_t)60 * (RadioLibTime_t)60 * (RadioLibTime_t)1000 / (RadioLibTime_t)(1UL << maxDutyCycle); + } + + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_DUTY_CYCLE], optIn, lenIn); + + return(true); + } break; + + case(RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP): { + // get the configuration + uint8_t macRx1DrOffset = (optIn[0] & 0x70) >> 4; + uint8_t macRx2Dr = optIn[0] & 0x0F; + uint32_t macRx2Freq = LoRaWANNode::ntoh(&optIn[1], 3); + + uint8_t rx1DrOsAck = 0; + uint8_t rx2DrAck = 0; + uint8_t rx2FreqAck = 0; + + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("RXParamSetupReq: Rx1DrOffset = %d, rx2DataRate = %d, freq = %7.3f", + macRx1DrOffset, macRx2Dr, macRx2Freq / 10000.0); + + // check the requested configuration + uint8_t uplinkDr = this->channels[RADIOLIB_LORAWAN_UPLINK].dr; + DataRate_t dr; + if(this->band->rx1DrTable[uplinkDr][macRx1DrOffset] != RADIOLIB_LORAWAN_DATA_RATE_UNUSED) { + if(this->findDataRate(this->band->rx1DrTable[uplinkDr][macRx1DrOffset], &dr) == RADIOLIB_ERR_NONE) { + rx1DrOsAck = 1; + } + } + if(macRx2Dr >= this->band->rx2.drMin && macRx2Dr <= this->band->rx2.drMax) { + if(this->band->dataRates[macRx2Dr] != RADIOLIB_LORAWAN_DATA_RATE_UNUSED) { + if(this->findDataRate(macRx2Dr, &dr) == RADIOLIB_ERR_NONE) { + rx2DrAck = 1; + } + } + } + if(macRx2Freq >= this->band->freqMin && macRx2Freq <= this->band->freqMax) { + if(this->phyLayer->setFrequency(macRx2Freq / 10000.0) == RADIOLIB_ERR_NONE) { + rx2FreqAck = 1; + } + } + optOut[0] = (rx1DrOsAck << 2) | (rx2DrAck << 1) | (rx2FreqAck << 0); + + // if not fully acknowledged, return now without applying the requested configuration + if(optOut[0] != 0x07) { + return(true); + } + + // passed ACK, so apply configuration + this->rx1DrOffset = macRx1DrOffset; + this->channels[RADIOLIB_LORAWAN_DIR_RX2].dr = macRx2Dr; + this->channels[RADIOLIB_LORAWAN_DIR_RX2].freq = macRx2Freq; + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_RX_PARAM_SETUP], optIn, lenIn); + + return(true); + } break; + + case(RADIOLIB_LORAWAN_MAC_DEV_STATUS): { + // set the uplink reply + optOut[0] = this->battLevel; + int8_t snr = this->phyLayer->getSNR(); + optOut[1] = snr & 0x3F; + + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("DevStatusAns: status = 0x%02x%02x", optOut[0], optOut[1]); + return(true); + } break; + + case(RADIOLIB_LORAWAN_MAC_NEW_CHANNEL): { + // only implemented on dynamic bands + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_FIXED) { + return(false); + } + + // get the configuration + uint8_t macChIndex = optIn[0]; + uint32_t macFreq = LoRaWANNode::ntoh(&optIn[1], 3); + uint8_t macDrMax = (optIn[4] & 0xF0) >> 4; + uint8_t macDrMin = optIn[4] & 0x0F; + + uint8_t drAck = 0; + uint8_t freqAck = 0; + + // the default channels shall not be modified, so check if this is a default channel + // if the channel index is set, this channel is defined, so return a NACK + if(macChIndex < 3 && this->band->txFreqs[macChIndex].idx != RADIOLIB_LORAWAN_CHANNEL_INDEX_NONE) { + optOut[0] = 0; + return(true); + } + + // check if the outermost datarates are defined and if the device supports them + DataRate_t dr; + if(this->band->dataRates[macDrMin] != RADIOLIB_LORAWAN_DATA_RATE_UNUSED && this->findDataRate(macDrMin, &dr) == RADIOLIB_ERR_NONE) { + if(this->band->dataRates[macDrMax] != RADIOLIB_LORAWAN_DATA_RATE_UNUSED && this->findDataRate(macDrMax, &dr) == RADIOLIB_ERR_NONE) { + drAck = 1; + } + } + + // check if the frequency is allowed and possible + if(macFreq >= this->band->freqMin && macFreq <= this->band->freqMax) { + if(this->phyLayer->setFrequency((float)macFreq / 10000.0) == RADIOLIB_ERR_NONE) { + freqAck = 1; + } + // otherwise, if frequency is 0, disable the channel which is also a valid option + } else if(macFreq == 0) { + freqAck = 1; + } + + // set ACK bits + optOut[0] = (drAck << 1) | (freqAck << 0); + + // if not fully acknowledged, return now without applying the requested configuration + if(optOut[0] != 0x03) { + return(true); + } + + // ACK successful, so apply and save + if(macFreq > 0) { + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].enabled = true; + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].idx = macChIndex; + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].freq = macFreq; + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].drMin = macDrMin; + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].drMax = macDrMax; + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].available = true; + // downlink channel is identical to uplink channel + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][macChIndex] = this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex]; + } else { + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex] = RADIOLIB_LORAWAN_CHANNEL_NONE; + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][macChIndex] = RADIOLIB_LORAWAN_CHANNEL_NONE; + + } + + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("UL: %3d %d %7.3f (%d - %d) | DL: %3d %d %7.3f (%d - %d)", + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].idx, + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].enabled, + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].freq / 10000.0, + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].drMin, + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].drMax, + + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][macChIndex].idx, + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][macChIndex].enabled, + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][macChIndex].freq / 10000.0, + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][macChIndex].drMin, + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][macChIndex].drMax + ); + + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_UL_CHANNELS] + macChIndex * lenIn, optIn, lenIn); + + return(true); + } break; + + case(RADIOLIB_LORAWAN_MAC_DL_CHANNEL): { + // only implemented on dynamic bands + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_FIXED) { + return(false); + } + + // get the configuration + uint8_t macChIndex = optIn[0]; + uint32_t macFreq = LoRaWANNode::ntoh(&optIn[1], 3); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("DlChannelReq: index = %d, freq = %7.3f MHz", macChIndex, macFreq / 10000.0); + uint8_t freqDlAck = 0; + uint8_t freqUlAck = 0; + + // check if the frequency is allowed possible + if(macFreq >= this->band->freqMin && macFreq <= this->band->freqMax) { + if(this->phyLayer->setFrequency(macFreq / 10000.0) == RADIOLIB_ERR_NONE) { + freqDlAck = 1; + } + } + + // check if the corresponding uplink frequency is actually set + if(this->channelPlan[RADIOLIB_LORAWAN_UPLINK][macChIndex].freq > 0) { + freqUlAck = 1; + } + + // set ACK bits + optOut[0] = (freqUlAck << 1) | (freqDlAck << 0); + + // if not fully acknowledged, return now without applying the requested configuration + if(optOut[0] != 0x03) { + return(true); + } + + // ACK successful, so apply and save + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][macChIndex].freq = macFreq; + + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_DL_CHANNELS] + macChIndex * lenIn, optIn, lenIn); + + return(true); + } break; + + case(RADIOLIB_LORAWAN_MAC_RX_TIMING_SETUP): { + // get the configuration + uint8_t delay = optIn[0] & 0x0F; + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("RXTimingSetupReq: delay = %d sec", delay); + + // apply the configuration + if(delay == 0) { + delay = 1; + } + this->rxDelays[1] = delay * 1000; // Rx1 delay + this->rxDelays[2] = this->rxDelays[1] + 1000; // Rx2 delay + + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_RX_TIMING_SETUP], optIn, lenIn); + + return(true); + } break; + + case(RADIOLIB_LORAWAN_MAC_TX_PARAM_SETUP): { + // TxParamSetupReq is only supported on a subset of bands + // in other bands, silently ignore without response + if(!this->band->txParamSupported) { + return(false); + } + uint8_t dlDwell = (optIn[0] & 0x20) >> 5; + uint8_t ulDwell = (optIn[0] & 0x10) >> 4; + uint8_t maxEirpRaw = optIn[0] & 0x0F; + + // who the f came up with this ... + const uint8_t eirpEncoding[] = { 8, 10, 12, 13, 14, 16, 18, 20, 21, 24, 26, 27, 29, 30, 33, 36 }; + this->txPowerMax = eirpEncoding[maxEirpRaw]; + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("TxParamSetupReq: dlDwell = %d, ulDwell = %d, maxEirp = %d dBm", dlDwell, ulDwell, eirpEncoding[maxEirpRaw]); + + this->dwellTimeEnabledUp = ulDwell ? true : false; + this->dwellTimeUp = ulDwell ? RADIOLIB_LORAWAN_DWELL_TIME : 0; + + this->dwellTimeEnabledDn = dlDwell ? true : false; + this->dwellTimeDn = dlDwell ? RADIOLIB_LORAWAN_DWELL_TIME : 0; + + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_TX_PARAM_SETUP], optIn, lenIn); + + return(true); + } break; + + case(RADIOLIB_LORAWAN_MAC_REKEY): { + // get the server version + uint8_t srvVersion = optIn[0]; + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("RekeyConf: server version = 1.%d", srvVersion); + + // If the server’s version is invalid the device SHALL discard the RekeyConf command and retransmit the RekeyInd in the next uplink frame + if((srvVersion > 0) && (srvVersion <= this->rev)) { + // valid server version, accept + this->rev = srvVersion; + + } else { + // if not a valid server version, retransmit RekeyInd + uint8_t cLen = 0; + this->getMacLen(cid, &cLen, RADIOLIB_LORAWAN_UPLINK); + uint8_t cOcts[1] = { this->rev }; + (void)LoRaWANNode::pushMacCommand(cid, cOcts, this->fOptsUp, &this->fOptsUpLen, RADIOLIB_LORAWAN_UPLINK); + + // discard RekeyConf, therefore return false so it doesn't send a reply + return(false); + } + + optOut[0] = this->rev; + + LoRaWANNode::hton(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_VERSION], this->rev); + return(false); + } break; + + case(RADIOLIB_LORAWAN_MAC_ADR_PARAM_SETUP): { + this->adrLimitExp = (optIn[0] & 0xF0) >> 4; + this->adrDelayExp = optIn[0] & 0x0F; + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("ADRParamSetupReq: limitExp = %d, delayExp = %d", this->adrLimitExp, this->adrDelayExp); + + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_ADR_PARAM_SETUP], optIn, lenIn); + + return(true); + } break; + + case(RADIOLIB_LORAWAN_MAC_DEVICE_TIME): { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("DeviceTimeAns: [user]"); + + return(false); + } break; + + case(RADIOLIB_LORAWAN_MAC_FORCE_REJOIN): { + // TODO implement this + uint16_t rejoinReq = LoRaWANNode::ntoh(optIn); + uint8_t period = (rejoinReq & 0x3800) >> 11; + uint8_t maxRetries = (rejoinReq & 0x0700) >> 8; + uint8_t rejoinType = (rejoinReq & 0x0070) >> 4; + uint8_t dr = rejoinReq & 0x000F; + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("ForceRejoinReq: period = %d, maxRetries = %d, rejoinType = %d, dr = %d", period, maxRetries, rejoinType, dr); + (void)period; + (void)maxRetries; + (void)rejoinType; + (void)dr; + return(false); + } break; + + case(RADIOLIB_LORAWAN_MAC_REJOIN_PARAM_SETUP): { + // TODO implement this + uint8_t maxTime = (optIn[0] & 0xF0) >> 4; + uint8_t maxCount = optIn[0] & 0x0F; + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("RejoinParamSetupReq: maxTime = %d, maxCount = %d", maxTime, maxCount); + + memcpy(&this->bufferSession[RADIOLIB_LORAWAN_SESSION_REJOIN_PARAM_SETUP], optIn, lenIn); + + lenIn = 0; + optIn[0] = (1 << 1) | 1; + + (void)maxTime; + (void)maxCount; + return(true); + } break; + + default: { + // derived classes may implement additional MAC commands + return(derivedMacHandler(cid, optIn, lenIn, optOut)); + } + } + + return(false); +} + +bool LoRaWANNode::derivedMacHandler(uint8_t cid, uint8_t* optIn, uint8_t lenIn, uint8_t* optOut) { + (void)cid; + (void)optIn; + (void)lenIn; + (void)optOut; + return(false); +} + +void LoRaWANNode::preprocessMacLinkAdr(uint8_t* mPtr, uint8_t cLen, uint8_t* mAdrOpt) { + uint8_t fLen = 5; // single ADR command is 5 bytes + uint8_t numOpts = cLen / fLen; + uint64_t chMaskGrp0123 = 0; + uint32_t chMaskGrp45 = 0; + + // set Dr/Tx field from last MAC command + mAdrOpt[0] = mPtr[cLen - fLen + 1]; + + // set NbTrans partial field from last MAC command + mAdrOpt[13] = mPtr[cLen - fLen + 4] & 0x0F; + + uint8_t opt = 0; + while(opt < numOpts) { + uint8_t chMaskCntl = (mPtr[opt * fLen + 4] & 0x70) >> 4; + uint16_t chMask = LoRaWANNode::ntoh(&mPtr[opt * fLen + 2]); + switch(chMaskCntl) { + case 0 ... 3: + chMaskGrp0123 |= (uint64_t)chMask << (16 * chMaskCntl); + break; + case 4: + chMaskGrp45 |= (uint32_t)chMask; + break; + case 5: + // for CN500, this is just a normal channel mask + // for all other bands, the first 10 bits enable banks of 8 125kHz channels + if(this->band->bandNum == BandCN500) { + chMaskGrp45 |= (uint32_t)chMask << 16; + } else { + int bank = 0; + for(; bank < 8; bank++) { + if(chMask & ((uint16_t)1 << bank)) { + chMaskGrp0123 |= (0xFF << (8 * bank)); + } + } + for(; bank < 10; bank++) { + if(chMask & ((uint16_t)1 << bank)) { + chMaskGrp45 |= (0xFF << (8 * (bank - 8))); + } + } + } + break; + case 6: + // for dynamic bands: all channels ON (that are currently defined) + // for fixed bands: all 125kHz channels ON, channel mask similar to ChMask = 4 + // except for CN500: all 125kHz channels ON + + // for dynamic bands: retrieve all currently defined channels + // for fixed bands: cannot store all defined channels, so select a random one from each bank + this->getChannelPlanMask(&chMaskGrp0123, &chMaskGrp45); + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_FIXED && this->band->bandNum != BandCN500) { + chMaskGrp45 |= (uint32_t)chMask; + } + break; + case 7: + // for fixed bands: all 125kHz channels ON, channel mask similar to ChMask = 4 + // except for CN500: RFU + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_FIXED && this->band->bandNum != BandCN500) { + chMaskGrp0123 = 0; + chMaskGrp45 |= (uint32_t)chMask; + } + break; + } + opt++; + } + LoRaWANNode::hton(&mAdrOpt[1], chMaskGrp0123); + LoRaWANNode::hton(&mAdrOpt[9], chMaskGrp45); +} + +void LoRaWANNode::postprocessMacLinkAdr(uint8_t* ack, uint8_t cLen) { + uint8_t fLen = 5; // single ADR command is 5 bytes + uint8_t numOpts = cLen / fLen; + + // duplicate the ACK bits of the atomic block response 'numOpts' times + // skip one, as the first response is already there + for(int opt = 1; opt < numOpts; opt++) { + ack[opt*2 + 0] = RADIOLIB_LORAWAN_MAC_LINK_ADR; + ack[opt*2 + 1] = ack[1]; + } +} + +int16_t LoRaWANNode::getMacCommand(uint8_t cid, LoRaWANMacCommand_t* cmd) { + for(size_t i = 0; i < RADIOLIB_LORAWAN_NUM_MAC_COMMANDS; i++) { + if(MacTable[i].cid == cid) { + memcpy((void*)cmd, (void*)&MacTable[i], sizeof(LoRaWANMacCommand_t)); + return(RADIOLIB_ERR_NONE); + } + } + // didn't find this CID, check if derived class can help (if any) + int16_t state = this->derivedMacFinder(cid, cmd); + return(state); +} + +int16_t LoRaWANNode::derivedMacFinder(uint8_t cid, LoRaWANMacCommand_t* cmd) { + (void)cid; + (void)cmd; + return(RADIOLIB_ERR_INVALID_CID); +} + +int16_t LoRaWANNode::sendMacCommandReq(uint8_t cid) { + LoRaWANMacCommand_t cmd = RADIOLIB_LORAWAN_MAC_COMMAND_NONE; + int16_t state = this->getMacCommand(cid, &cmd); + RADIOLIB_ASSERT(state); + if(!cmd.user) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("You are not allowed to request this MAC command"); + return(RADIOLIB_ERR_INVALID_CID); + } + + // if there are already 15 MAC bytes in the uplink queue, we can't add a new one + if(fOptsUpLen >= RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("The maximum size of FOpts payload was reached"); + return(RADIOLIB_ERR_COMMAND_QUEUE_FULL); + } + + // if this MAC command is already in the queue, silently stop + if(this->getMacPayload(cid, this->fOptsUp, this->fOptsUpLen, NULL, RADIOLIB_LORAWAN_UPLINK) == RADIOLIB_ERR_NONE) { + return(RADIOLIB_ERR_NONE); + } + + state = LoRaWANNode::pushMacCommand(cid, NULL, this->fOptsUp, &this->fOptsUpLen, RADIOLIB_LORAWAN_UPLINK); + return(state); +} + +int16_t LoRaWANNode::getMacLinkCheckAns(uint8_t* margin, uint8_t* gwCnt) { + uint8_t payload[2] = { 0 }; + int16_t state = this->getMacPayload(RADIOLIB_LORAWAN_MAC_LINK_CHECK, this->fOptsDown, fOptsDownLen, payload, RADIOLIB_LORAWAN_DOWNLINK); + RADIOLIB_ASSERT(state); + + if(margin) { *margin = payload[0]; } + if(gwCnt) { *gwCnt = payload[1]; } + + return(RADIOLIB_ERR_NONE); +} + +int16_t LoRaWANNode::getMacDeviceTimeAns(uint32_t* gpsEpoch, uint8_t* fraction, bool returnUnix) { + uint8_t payload[5] = { 0 }; + int16_t state = this->getMacPayload(RADIOLIB_LORAWAN_MAC_DEVICE_TIME, this->fOptsDown, fOptsDownLen, payload, RADIOLIB_LORAWAN_DOWNLINK); + RADIOLIB_ASSERT(state); + + if(gpsEpoch) { + *gpsEpoch = LoRaWANNode::ntoh(&payload[0]); + if(returnUnix) { + uint32_t unixOffset = 315964800UL - 18UL; // 18 leap seconds since GPS epoch (Jan. 6th 1980) + *gpsEpoch += unixOffset; + } + } + if(fraction) { *fraction = payload[4]; } + + return(RADIOLIB_ERR_NONE); +} + +int16_t LoRaWANNode::getMacLen(uint8_t cid, uint8_t* len, uint8_t dir, bool inclusive) { + LoRaWANMacCommand_t cmd = RADIOLIB_LORAWAN_MAC_COMMAND_NONE; + int16_t state = this->getMacCommand(cid, &cmd); + RADIOLIB_ASSERT(state); + if(dir == RADIOLIB_LORAWAN_UPLINK) { + *len = cmd.lenUp; + } else { + *len = cmd.lenDn; + } + if(inclusive) { + *len += 1; // add one byte for CID + } + return(RADIOLIB_ERR_NONE); +} + +bool LoRaWANNode::isPersistentMacCommand(uint8_t cid, uint8_t dir) { + // if this MAC command doesn't exist, it wouldn't even get into the queue, so don't care about outcome + LoRaWANMacCommand_t cmd = RADIOLIB_LORAWAN_MAC_COMMAND_NONE; + (void)this->getMacCommand(cid, &cmd); + + // in the uplink direction, MAC payload should persist per spec + if(dir == RADIOLIB_LORAWAN_UPLINK) { + return(cmd.persist); + + // in the downlink direction, MAC payload should persist if it is user-accessible + // which is the case for LinkCheck and DeviceTime + } else { + return(cmd.user); + } + return(false); +} + +int16_t LoRaWANNode::pushMacCommand(uint8_t cid, uint8_t* cOcts, uint8_t* out, uint8_t* lenOut, uint8_t dir) { + uint8_t fLen = 0; + int16_t state = this->getMacLen(cid, &fLen, dir, true); + RADIOLIB_ASSERT(state); + + // check if we can even append the MAC command into the buffer + if(*lenOut + fLen > RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN) { + return(RADIOLIB_ERR_COMMAND_QUEUE_FULL); + } + + out[*lenOut] = cid; // add MAC id + memcpy(&out[*lenOut + 1], cOcts, fLen - 1); // copy payload into buffer + *lenOut += fLen; // payload + command ID + + return(RADIOLIB_ERR_NONE); +} + +int16_t LoRaWANNode::getMacPayload(uint8_t cid, uint8_t* in, uint8_t lenIn, uint8_t* out, uint8_t dir) { + size_t i = 0; + + while(i < lenIn) { + uint8_t id = in[i]; + uint8_t fLen = 0; + int16_t state = this->getMacLen(id, &fLen, dir, true); + RADIOLIB_ASSERT(state); + if(lenIn < i + fLen) { + return(RADIOLIB_ERR_INVALID_CID); + } + + // if this is the requested MAC id, copy the payload over + if(id == cid) { + // only copy payload if destination is supplied + if(out) { + memcpy(out, &in[i + 1], fLen - 1); + } + return(RADIOLIB_ERR_NONE); + } + + // move on to next MAC command + i += fLen; + } + + return(RADIOLIB_ERR_COMMAND_QUEUE_ITEM_NOT_FOUND); +} + +int16_t LoRaWANNode::deleteMacCommand(uint8_t cid, uint8_t* inOut, uint8_t* lenInOut, uint8_t dir) { + size_t i = 0; + while(i < *lenInOut) { + uint8_t id = inOut[i]; + uint8_t fLen = 0; + int16_t state = this->getMacLen(id, &fLen, dir); + RADIOLIB_ASSERT(state); + if(*lenInOut < i + fLen) { + return(RADIOLIB_ERR_INVALID_CID); + } + + // if this is the requested MAC id, + if(id == cid) { + // remove it by moving the rest of the payload forward + memmove(&inOut[i], &inOut[i + fLen], *lenInOut - i - fLen); + + // set the remainder of the queue to 0 + memset(&inOut[i + fLen], 0, *lenInOut - i - fLen); + + *lenInOut -= fLen; + return(RADIOLIB_ERR_NONE); + } + + // move on to next MAC command + i += fLen; + } + + return(RADIOLIB_ERR_COMMAND_QUEUE_ITEM_NOT_FOUND); +} + +void LoRaWANNode::clearMacCommands(uint8_t* inOut, uint8_t* lenInOut, uint8_t dir) { + size_t i = 0; + uint8_t numDeleted = 0; + while(i < *lenInOut) { + uint8_t id = inOut[i]; + uint8_t fLen = 1; // if there is an incorrect MAC command, we should at least move forward by one byte + (void)this->getMacLen(id, &fLen, dir, true); + + // only clear MAC command if it should not persist until a downlink is received + if(!this->isPersistentMacCommand(id, dir)) { + // remove it by moving the rest of the payload forward + memmove(&inOut[i], &inOut[i + fLen], *lenInOut - i - fLen); + + // set the remainder of the queue to 0 + memset(&inOut[i + fLen], 0, *lenInOut - i - fLen); + + numDeleted += fLen; + } + + // move on to next MAC command + i += fLen; + } + *lenInOut -= numDeleted; +} + +int16_t LoRaWANNode::setDatarate(uint8_t drUp) { + // scan through all enabled channels and check if the requested datarate is available + bool isValidDR = false; + for(size_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + LoRaWANChannel_t *chnl = &(this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i]); + if(chnl->enabled) { + if(drUp >= chnl->drMin && drUp <= chnl->drMax) { + isValidDR = true; + break; + } + } + } + if(!isValidDR) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("No defined channel allows datarate %d", drUp); + return(RADIOLIB_ERR_INVALID_DATA_RATE); + } + + uint8_t cOcts[1]; + uint8_t cAck[1]; + uint8_t cid = RADIOLIB_LORAWAN_MAC_LINK_ADR; + uint8_t cLen = 1; // only apply Dr/Tx field + cOcts[0] = (drUp << 4); // set requested datarate + cOcts[0] |= 0x0F; // keep Tx Power the same + (void)execMacCommand(cid, cOcts, cLen, cAck); + + // check if ACK is set for Datarate + if(!(cAck[0] & 0x02)) { + return(RADIOLIB_ERR_INVALID_DATA_RATE); + } + + return(RADIOLIB_ERR_NONE); +} + +int16_t LoRaWANNode::setTxPower(int8_t txPower) { + // only allow values within the band's (or MAC state) maximum + if(txPower > this->txPowerMax) { + return(RADIOLIB_ERR_INVALID_OUTPUT_POWER); + } + // Tx Power is set in steps of two + // the selected value is rounded down to nearest multiple of two away from txPowerMax + // e.g. on EU868, max is 16; if 13 is selected then we set to 12 + uint8_t numSteps = (this->txPowerMax - txPower + 1) / (-RADIOLIB_LORAWAN_POWER_STEP_SIZE_DBM); + + uint8_t cOcts[1]; + uint8_t cAck[1]; + uint8_t cid = RADIOLIB_LORAWAN_MAC_LINK_ADR; + uint8_t cLen = 1; // only apply Dr/Tx field + cOcts[0] = 0xF0; // keep datarate the same + cOcts[0] |= numSteps; // set requested Tx Power + (void)execMacCommand(cid, cOcts, cLen, cAck); + + // check if ACK is set for Tx Power + if(!(cAck[0] & 0x04)) { + return(RADIOLIB_ERR_INVALID_OUTPUT_POWER); + } + + return(RADIOLIB_ERR_NONE); +} + +int16_t LoRaWANNode::setRx2Dr(uint8_t dr) { + // this can only be configured in ABP mode + if(this->lwMode != RADIOLIB_LORAWAN_MODE_ABP) { + return(RADIOLIB_ERR_INVALID_MODE); + } + + // can only configure different datarate for dynamic bands + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_FIXED) { + return(RADIOLIB_ERR_NO_CHANNEL_AVAILABLE); + } + + // check if datarate is available in the selected band + if(this->band->dataRates[dr] == RADIOLIB_LORAWAN_DATA_RATE_UNUSED) { + return(RADIOLIB_ERR_INVALID_DATA_RATE); + } + + // find and check if the datarate is available for this radio module + DataRate_t dataRate; + int16_t state = findDataRate(dr, &dataRate); + RADIOLIB_ASSERT(state); + + // passed all checks, so configure the datarate + this->channels[RADIOLIB_LORAWAN_DIR_RX2].dr = dr; + + return(state); +} + +void LoRaWANNode::setADR(bool enable) { + this->adrEnabled = enable; +} + +void LoRaWANNode::setDutyCycle(bool enable, RadioLibTime_t msPerHour) { + this->dutyCycleEnabled = enable; + if(!enable) { + this->dutyCycle = 0; + } + if(msPerHour == 0) { + this->dutyCycle = this->band->dutyCycle; + } else { + this->dutyCycle = msPerHour; + } +} + +void LoRaWANNode::setDwellTime(bool enable, RadioLibTime_t msPerUplink) { + this->dwellTimeEnabledUp = enable; + if(msPerUplink == 0) { + this->dwellTimeUp = this->band->dwellTimeUp; + } else { + this->dwellTimeUp = msPerUplink; + } +} + +// A user may enable CSMA to provide frames an additional layer of protection from interference. +// https://resources.lora-alliance.org/technical-recommendations/tr013-1-0-0-csma +void LoRaWANNode::setCSMA(bool csmaEnabled, uint8_t maxChanges, uint8_t backoffMax, uint8_t difsSlots) { + this->csmaEnabled = csmaEnabled; + if(csmaEnabled) { + this->maxChanges = maxChanges; + this->difsSlots = difsSlots; + this->backoffMax = backoffMax; + } else { + // disable all values + this->maxChanges = 0; + this->difsSlots = 0; + this->backoffMax = 0; + } +} + +void LoRaWANNode::setDeviceStatus(uint8_t battLevel) { + this->battLevel = battLevel; +} + +void LoRaWANNode::scheduleTransmission(RadioLibTime_t tUplink) { + this->tUplink = tUplink; +} + +// return fCnt of last uplink; also return 0 if no uplink occured yet +uint32_t LoRaWANNode::getFCntUp() { + if(this->fCntUp == 0) { + return(0); + } + return(this->fCntUp - 1); +} + +uint32_t LoRaWANNode::getNFCntDown() { + return(this->nFCntDown); +} + +uint32_t LoRaWANNode::getAFCntDown() { + return(this->aFCntDown); +} + +void LoRaWANNode::resetFCntDown() { + this->nFCntDown = 0; + this->aFCntDown = 0; +} + +uint32_t LoRaWANNode::getDevAddr() { + return(this->devAddr); +} + +RadioLibTime_t LoRaWANNode::getLastToA() { + return(this->lastToA); +} + +int16_t LoRaWANNode::setPhyProperties(const LoRaWANChannel_t* chnl, uint8_t dir, int8_t pwr, size_t pre) { + // set the physical layer configuration + int16_t state = this->phyLayer->standby(); + if(state != RADIOLIB_ERR_NONE) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Failed to set radio into standby - is it connected?"); + return(state); + } + + // get the currently configured modem from the radio + ModemType_t modem; + state = this->phyLayer->getModem(&modem); + RADIOLIB_ASSERT(state); + + // set modem-dependent functions + switch(this->band->dataRates[chnl->dr] & RADIOLIB_LORAWAN_DATA_RATE_MODEM) { + case(RADIOLIB_LORAWAN_DATA_RATE_LORA): + if(modem != ModemType_t::RADIOLIB_MODEM_LORA) { + state = this->phyLayer->setModem(ModemType_t::RADIOLIB_MODEM_LORA); + RADIOLIB_ASSERT(state); + } + modem = ModemType_t::RADIOLIB_MODEM_LORA; + // downlink messages are sent with inverted IQ + if(dir == RADIOLIB_LORAWAN_DOWNLINK) { + state = this->phyLayer->invertIQ(true); + } else { + state = this->phyLayer->invertIQ(false); + } + RADIOLIB_ASSERT(state); + break; + + case(RADIOLIB_LORAWAN_DATA_RATE_FSK): + if(modem != ModemType_t::RADIOLIB_MODEM_FSK) { + state = this->phyLayer->setModem(ModemType_t::RADIOLIB_MODEM_FSK); + RADIOLIB_ASSERT(state); + } + modem = ModemType_t::RADIOLIB_MODEM_FSK; + state = this->phyLayer->setDataShaping(RADIOLIB_SHAPING_1_0); + RADIOLIB_ASSERT(state); + state = this->phyLayer->setEncoding(RADIOLIB_ENCODING_WHITENING); + RADIOLIB_ASSERT(state); + break; + + case(RADIOLIB_LORAWAN_DATA_RATE_LR_FHSS): + if(modem != ModemType_t::RADIOLIB_MODEM_LRFHSS) { + state = this->phyLayer->setModem(ModemType_t::RADIOLIB_MODEM_LRFHSS); + RADIOLIB_ASSERT(state); + } + modem = ModemType_t::RADIOLIB_MODEM_LRFHSS; + break; + + default: + return(RADIOLIB_ERR_UNSUPPORTED); + } + + RADIOLIB_DEBUG_PROTOCOL_PRINTLN(""); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("PHY: Frequency = %7.3f MHz, TX = %d dBm", chnl->freq / 10000.0, pwr); + state = this->phyLayer->setFrequency(chnl->freq / 10000.0); + RADIOLIB_ASSERT(state); + + // at this point, assume that Tx power value is already checked, so ignore the return value + // this call is only used to clip a value that is higher than the module supports + (void)this->phyLayer->checkOutputPower(pwr, &pwr); + state = this->phyLayer->setOutputPower(pwr); + RADIOLIB_ASSERT(state); + + DataRate_t dr; + state = findDataRate(chnl->dr, &dr); + RADIOLIB_ASSERT(state); + state = this->phyLayer->setDataRate(dr); + RADIOLIB_ASSERT(state); + + // this only needs to be done once-ish + uint8_t syncWord[4] = { 0 }; + uint8_t syncWordLen = 0; + size_t preLen = 0; + switch(modem) { + case(ModemType_t::RADIOLIB_MODEM_FSK): { + preLen = 8*RADIOLIB_LORAWAN_GFSK_PREAMBLE_LEN; + syncWord[0] = (uint8_t)(RADIOLIB_LORAWAN_GFSK_SYNC_WORD >> 16); + syncWord[1] = (uint8_t)(RADIOLIB_LORAWAN_GFSK_SYNC_WORD >> 8); + syncWord[2] = (uint8_t)RADIOLIB_LORAWAN_GFSK_SYNC_WORD; + syncWordLen = 3; + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("FSK: BR = %4.1f, FD = %4.1f kHz", + dr.fsk.bitRate, dr.fsk.freqDev); + } break; + + case(ModemType_t::RADIOLIB_MODEM_LORA): { + preLen = RADIOLIB_LORAWAN_LORA_PREAMBLE_LEN; + syncWord[0] = RADIOLIB_LORAWAN_LORA_SYNC_WORD; + syncWordLen = 1; + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("LoRa: SF = %d, BW = %5.1f kHz, CR = 4/%d, IQ: %c", + dr.lora.spreadingFactor, dr.lora.bandwidth, dr.lora.codingRate, dir ? 'D' : 'U'); + } break; + + case(ModemType_t::RADIOLIB_MODEM_LRFHSS): { + syncWord[0] = (uint8_t)(RADIOLIB_LORAWAN_LR_FHSS_SYNC_WORD >> 24); + syncWord[1] = (uint8_t)(RADIOLIB_LORAWAN_LR_FHSS_SYNC_WORD >> 16); + syncWord[2] = (uint8_t)(RADIOLIB_LORAWAN_LR_FHSS_SYNC_WORD >> 8); + syncWord[3] = (uint8_t)RADIOLIB_LORAWAN_LR_FHSS_SYNC_WORD; + syncWordLen = 4; + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("LR-FHSS: BW = 0x%02x, CR = 0x%02x kHz, grid = %c", + dr.lrFhss.bw, dr.lrFhss.cr, dr.lrFhss.narrowGrid ? 'N' : 'W'); + } break; + + default: + return(RADIOLIB_ERR_WRONG_MODEM); + } + + state = this->phyLayer->setSyncWord(syncWord, syncWordLen); + RADIOLIB_ASSERT(state); + + // if a preamble length is supplied, overrule the 'calculated' preamble length + if(pre) { + preLen = pre; + } + if(modem != ModemType_t::RADIOLIB_MODEM_LRFHSS) { + state = this->phyLayer->setPreambleLength(preLen); + } + return(state); +} + +// The following function implements LMAC, a CSMA scheme for LoRa as specified +// in the LoRa Alliance Technical Recommendation #13. +bool LoRaWANNode::csmaChannelClear(uint8_t difs, uint8_t numBackoff) { + // DIFS phase: perform #DIFS CAD operations + uint16_t numCads = 0; + for (; numCads < difs; numCads++) { + if (!this->cadChannelClear()) { + return(false); + } + } + + // BO phase: perform #numBackoff additional CAD operations + for (; numCads < difs + numBackoff; numCads++) { + if (!this->cadChannelClear()) { + return(false); + } + } + + // none of the CADs showed activity, so all clear + return(true); +} + +bool LoRaWANNode::cadChannelClear() { + int16_t state = this->phyLayer->scanChannel(); + // if activity was detected, channel is not clear + if ((state == RADIOLIB_PREAMBLE_DETECTED) || (state == RADIOLIB_LORA_DETECTED)) { + return(false); + } + return(true); +} + +void LoRaWANNode::getChannelPlanMask(uint64_t* chMaskGrp0123, uint32_t* chMaskGrp45) { + // clear masks in case anything was set + *chMaskGrp0123 = 0; + *chMaskGrp45 = 0; + + // if there are any channels selected, create the mask from those channels + // channels are always selected for dynamic bands and/or when a device is active + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC || this->isActivated()) { + for(int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + uint8_t idx = this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].idx; + if(idx != RADIOLIB_LORAWAN_CHANNEL_INDEX_NONE) { + if(idx < 64) { + *chMaskGrp0123 |= ((uint64_t)1 << idx); + } else { + *chMaskGrp45 |= ((uint32_t)1 << (idx - 64)); + } + } + } + return; + + } else { // bandType == RADIOLIB_LORAWAN_BAND_FIXED + // if a subband is set, we can set the channel indices straight from subband + if(this->subBand > 0 && this->subBand <= 8) { + // for sub band 1-8, set bank of 8 125kHz + single 500kHz channel + *chMaskGrp0123 |= 0xFF << ((this->subBand - 1) * 8); + *chMaskGrp45 |= 0x01 << ((this->subBand - 1) * 8); + } else if(this->subBand > 8 && this->subBand <= 12) { + // CN500 only: for sub band 9-12, set bank of 8 125kHz channels + *chMaskGrp45 |= 0xFF << ((this->subBand - 9) * 8); + } else { + // if subband is set to 0, all 125kHz channels are enabled. + // however, we can 'only' store 16 channels, so we don't use all channels at once. + // instead, we select a random channel from each bank of 8 channels + 1 from second plan. + uint8_t num125kHz = this->band->txSpans[0].numChannels; + uint8_t numBanks = num125kHz / 8; + for(uint8_t bank = 0; bank < numBanks; bank++) { + uint8_t bankIdx = this->phyLayer->random(8); + uint8_t idx = bank * 8 + bankIdx; + if(idx < 64) { + *chMaskGrp0123 |= ((uint64_t)1 << idx); + } else { + *chMaskGrp45 |= ((uint32_t)1 << (idx - 64)); + } + } + // the 500 kHz channels are in the usual channel plan however + // these are the channel indices 64-71 for bands other than CN500 + if(this->band->bandNum != BandCN500) { + for(int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + uint8_t idx = this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].idx; + if(idx != RADIOLIB_LORAWAN_CHANNEL_INDEX_NONE && idx >= 64) { + *chMaskGrp45 |= ((uint32_t)1 << (idx - 64)); + } + } + } + } + } +} + +void LoRaWANNode::selectChannelPlanDyn(bool joinRequest) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Setting up dynamic channels"); + + size_t num = 0; + // copy the default defined channels into the first slots (where Tx = Rx) + for(; num < 3 && this->band->txFreqs[num].enabled; num++) { + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][num] = this->band->txFreqs[num]; + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][num] = this->band->txFreqs[num]; + } + + // if we're about to send a JoinRequest, copy the JoinRequest channels to the next slots + if(joinRequest) { + size_t numJR = 0; + for(; numJR < 3 && this->band->txJoinReq[num].enabled; numJR++, num++) { + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][num] = this->band->txFreqs[num]; + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][num] = this->band->txFreqs[num]; + } + } + + // clear all remaining channels + for(; num < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; num++) { + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][num] = RADIOLIB_LORAWAN_CHANNEL_NONE; + } + + // make sure the Rx2 settings are back to this band's default + this->channels[RADIOLIB_LORAWAN_DIR_RX2] = this->band->rx2; + + // make all enabled channels available for uplink selection + this->setAvailableChannels(0xFFFF); + + #if RADIOLIB_DEBUG_PROTOCOL + this->printChannels(); + #endif +} + +// setup a subband and its corresponding JoinRequest datarate +// WARNING: subBand starts at 1 (corresponds to all populair schemes) +void LoRaWANNode::selectChannelPlanFix() { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Setting up fixed channels (subband %d)", this->subBand); + + // clear all existing channels + for(size_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i] = RADIOLIB_LORAWAN_CHANNEL_NONE; + } + + // get channel masks for this subband + uint64_t chMaskGrp0123 = 0; + uint32_t chMaskGrp45 = 0; + this->getChannelPlanMask(&chMaskGrp0123, &chMaskGrp45); + + // apply channel mask + this->applyChannelMask(chMaskGrp0123, chMaskGrp45); + + // make sure the Rx2 settings are back to this band's default + this->channels[RADIOLIB_LORAWAN_DIR_RX2] = this->band->rx2; + + // make all enabled channels available for uplink selection + this->setAvailableChannels(0xFFFF); +} + +uint8_t LoRaWANNode::getAvailableChannels(uint16_t* chMask) { + uint8_t num = 0; + uint16_t mask = 0; + uint8_t currentDr = this->channels[RADIOLIB_LORAWAN_UPLINK].dr; + for(uint8_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + // if channel is available and usable for current datarate, set corresponding bit + if(this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].available) { + if(currentDr >= this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].drMin && + currentDr <= this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].drMax) { + num++; + mask |= (0x0001 << i); + } + } + } + if(chMask) { + *chMask = mask; + } + return(num); +} + +void LoRaWANNode::setAvailableChannels(uint16_t mask) { + for(uint8_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + // if channel is enabled, set to available + if(mask & (0x0001 << i) && this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].enabled) { + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].available = true; + } else { + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].available = false; + } + } +} + +int16_t LoRaWANNode::selectChannels() { + uint16_t chMask = 0x0000; + uint8_t numChannels = this->getAvailableChannels(&chMask); + + // if there are no available channels, try resetting them all to available + if(numChannels == 0) { + this->setAvailableChannels(0xFFFF); + numChannels = this->getAvailableChannels(&chMask); + + // if there are still no channels available, give up + if(numChannels == 0) { + return(RADIOLIB_ERR_NO_CHANNEL_AVAILABLE); + } + } + + // select a random value within the number of possible channels + int chRand = this->phyLayer->random(numChannels); + + // retrieve the index of this channel by looping through the channel mask + int chIdx = -1; + while(chRand >= 0) { + chIdx++; + if(chMask & 0x0001) { + chRand--; + } + chMask >>= 1; + } + + // as we are now going to use this channel, mark unavailable for next uplink + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][chIdx].available = false; + + uint8_t currentDr = this->channels[RADIOLIB_LORAWAN_UPLINK].dr; + this->channels[RADIOLIB_LORAWAN_UPLINK] = this->channelPlan[RADIOLIB_LORAWAN_UPLINK][chIdx]; + this->channels[RADIOLIB_LORAWAN_UPLINK].dr = currentDr; + + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { + // for dynamic bands, the downlink channel is the one matched to the uplink channel + this->channels[RADIOLIB_LORAWAN_DOWNLINK] = this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][chIdx]; + + } else { // RADIOLIB_LORAWAN_BAND_FIXED + // for fixed bands, the downlink channel is the uplink channel ID `modulo` number of downlink channels + LoRaWANChannel_t channelDn = RADIOLIB_LORAWAN_CHANNEL_NONE; + channelDn.enabled = true; + channelDn.idx = this->channels[RADIOLIB_LORAWAN_UPLINK].idx % this->band->rx1Span.numChannels; + channelDn.freq = this->band->rx1Span.freqStart + channelDn.idx*this->band->rx1Span.freqStep; + channelDn.drMin = this->band->rx1Span.drMin; + channelDn.drMax = this->band->rx1Span.drMax; + this->channels[RADIOLIB_LORAWAN_DOWNLINK] = channelDn; + + } + uint8_t rx1Dr = this->band->rx1DrTable[currentDr][this->rx1DrOffset]; + + // if downlink dwelltime is enabled, datarate < 2 cannot be used, so clip to 2 + // only in use on AS923_x bands + if(this->dwellTimeEnabledDn && rx1Dr < 2) { + rx1Dr = 2; + } + this->channels[RADIOLIB_LORAWAN_DOWNLINK].dr = rx1Dr; + + return(RADIOLIB_ERR_NONE); +} + +bool LoRaWANNode::applyChannelMask(uint64_t chMaskGrp0123, uint32_t chMaskGrp45) { + if(this->band->bandType == RADIOLIB_LORAWAN_BAND_DYNAMIC) { + for(int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + if(chMaskGrp0123 & ((uint64_t)1 << i)) { + // if it should be enabled but is not currently defined, stop immediately + if(this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].idx == RADIOLIB_LORAWAN_CHANNEL_INDEX_NONE) { + return(false); + } + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].enabled = true; + } else { + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].enabled = false; + } + } + } else { // bandType == RADIOLIB_LORAWAN_BAND_FIXED + // full channel mask received, so clear all existing channels + LoRaWANChannel_t chnl = RADIOLIB_LORAWAN_CHANNEL_NONE; + for(size_t i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i] = chnl; + } + int num = 0; + uint8_t spanNum = 0; + int chNum = 0; + int chOfs = 0; + for(; chNum < 64; chNum++) { + if(chMaskGrp0123 & ((uint64_t)1 << chNum)) { + chnl.enabled = true; + chnl.idx = chNum; + chnl.freq = this->band->txSpans[spanNum].freqStart + chNum*this->band->txSpans[spanNum].freqStep; + chnl.drMin = this->band->txSpans[spanNum].drMin; + chnl.drMax = this->band->txSpans[spanNum].drMax; + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][num++] = chnl; + } + } + if(this->band->numTxSpans > 1) { + spanNum += 1; + chNum = 0; + chOfs = 64; + } + for(; chNum < this->band->txSpans[spanNum].numChannels; chNum++) { + if(chMaskGrp45 & ((uint32_t)1 << chNum)) { + chnl.enabled = true; + chnl.idx = chNum + chOfs; + chnl.freq = this->band->txSpans[spanNum].freqStart + chNum*this->band->txSpans[spanNum].freqStep; + chnl.drMin = this->band->txSpans[spanNum].drMin; + chnl.drMax = this->band->txSpans[spanNum].drMax; + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][num++] = chnl; + } + } + } + +#if RADIOLIB_DEBUG_PROTOCOL + this->printChannels(); +#endif + + return(true); +} + +#if RADIOLIB_DEBUG_PROTOCOL +void LoRaWANNode::printChannels() { + for (int i = 0; i < RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS; i++) { + if(this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].enabled) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("UL: %3d %d %7.3f (%d - %d) | DL: %3d %d %7.3f (%d - %d)", + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].idx, + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].enabled, + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].freq / 10000.0, + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].drMin, + this->channelPlan[RADIOLIB_LORAWAN_UPLINK][i].drMax, + + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][i].idx, + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][i].enabled, + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][i].freq / 10000.0, + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][i].drMin, + this->channelPlan[RADIOLIB_LORAWAN_DOWNLINK][i].drMax + ); + } + } +} +#endif + +uint32_t LoRaWANNode::generateMIC(uint8_t* msg, size_t len, uint8_t* key) { + if((msg == NULL) || (len == 0)) { + return(0); + } + + RadioLibAES128Instance.init(key); + uint8_t cmac[RADIOLIB_AES128_BLOCK_SIZE]; + RadioLibAES128Instance.generateCMAC(msg, len, cmac); + return(((uint32_t)cmac[0]) | ((uint32_t)cmac[1] << 8) | ((uint32_t)cmac[2] << 16) | ((uint32_t)cmac[3]) << 24); +} + +bool LoRaWANNode::verifyMIC(uint8_t* msg, size_t len, uint8_t* key) { + if((msg == NULL) || (len < sizeof(uint32_t))) { + return(0); + } + + // extract MIC from the message + uint32_t micReceived = LoRaWANNode::ntoh(&msg[len - sizeof(uint32_t)]); + + // calculate the expected value and compare + uint32_t micCalculated = generateMIC(msg, len - sizeof(uint32_t), key); + if(micCalculated != micReceived) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("MIC mismatch, expected %08lx, got %08lx", + (unsigned long)micCalculated, (unsigned long)micReceived); + return(false); + } + + return(true); +} + +// given an airtime in milliseconds, calculate the minimum uplink interval +// to adhere to a given dutyCycle +RadioLibTime_t LoRaWANNode::dutyCycleInterval(RadioLibTime_t msPerHour, RadioLibTime_t airtime) { + if(msPerHour == 0 || airtime == 0) { + return(0); + } + RadioLibTime_t oneHourInMs = (RadioLibTime_t)60 * (RadioLibTime_t)60 * (RadioLibTime_t)1000; + float numPackets = msPerHour / airtime; + RadioLibTime_t delayMs = oneHourInMs / numPackets + 1; // + 1 to prevent rounding problems + return(delayMs); +} + +RadioLibTime_t LoRaWANNode::timeUntilUplink() { + Module* mod = this->phyLayer->getMod(); + RadioLibTime_t nextUplink = this->rxDelayStart + dutyCycleInterval(this->dutyCycle, this->lastToA); + if(mod->hal->millis() > nextUplink){ + return(0); + } + return(nextUplink - mod->hal->millis() + 1); +} + +uint8_t LoRaWANNode::getMaxPayloadLen() { + // configure the uplink channel properties + this->setPhyProperties(&this->channels[RADIOLIB_LORAWAN_UPLINK], + RADIOLIB_LORAWAN_UPLINK, + this->txPowerMax - 2*this->txPowerSteps); + + uint8_t minLen = 0; + uint8_t maxLen = this->band->payloadLenMax[this->channels[RADIOLIB_LORAWAN_UPLINK].dr]; + if(this->TS011) { + maxLen = RADIOLIB_MIN(maxLen, 222); // payload length is limited to N=222 if under repeater + } + maxLen += 13; // mandatory FHDR is 12/13 bytes + + // if not limited by dwell-time, just return maximum + if(!this->dwellTimeEnabledUp) { + // subtract FHDR (13 bytes) as well as any FOpts + return(maxLen - 13 - this->fOptsUpLen); + } + + // fast exit in case upper limit is already good + if(this->phyLayer->getTimeOnAir(maxLen) / 1000 <= this->dwellTimeUp) { + // subtract FHDR (13 bytes) as well as any FOpts + return(maxLen - 13 - this->fOptsUpLen); + } + + // do some binary search to find maximum allowed length + uint8_t curLen = (minLen + maxLen) / 2; + while(curLen != minLen && curLen != maxLen) { + if(this->phyLayer->getTimeOnAir(curLen) / 1000 > this->dwellTimeUp) { + maxLen = curLen; + } else { + minLen = curLen; + } + curLen = (minLen + maxLen) / 2; + } + // subtract FHDR (13 bytes) as well as any FOpts + return(curLen - 13 - this->fOptsUpLen); +} + +int16_t LoRaWANNode::findDataRate(uint8_t dr, DataRate_t* dataRate) { + int16_t state = this->phyLayer->standby(); + if(state != RADIOLIB_ERR_NONE) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Failed to set radio into standby - is it connected?"); + return(state); + } + + ModemType_t modemNew; + + uint8_t dataRateBand = this->band->dataRates[dr]; + + switch(dataRateBand & RADIOLIB_LORAWAN_DATA_RATE_MODEM) { + case(RADIOLIB_LORAWAN_DATA_RATE_LORA): + modemNew = ModemType_t::RADIOLIB_MODEM_LORA; + dataRate->lora.spreadingFactor = ((dataRateBand & RADIOLIB_LORAWAN_DATA_RATE_SF) >> 3) + 7; + switch(dataRateBand & RADIOLIB_LORAWAN_DATA_RATE_BW) { + case(RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ): + dataRate->lora.bandwidth = 125.0; + break; + case(RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ): + dataRate->lora.bandwidth = 250.0; + break; + case(RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ): + dataRate->lora.bandwidth = 500.0; + break; + default: + return(RADIOLIB_ERR_UNSUPPORTED); + } + dataRate->lora.codingRate = 5; + break; + case(RADIOLIB_LORAWAN_DATA_RATE_FSK): + modemNew = ModemType_t::RADIOLIB_MODEM_FSK; + dataRate->fsk.bitRate = 50; + dataRate->fsk.freqDev = 25; + break; + case(RADIOLIB_LORAWAN_DATA_RATE_LR_FHSS): + modemNew = ModemType_t::RADIOLIB_MODEM_LRFHSS; + switch(dataRateBand & RADIOLIB_LORAWAN_DATA_RATE_BW) { + case(RADIOLIB_LORAWAN_DATA_RATE_BW_137_KHZ): + dataRate->lrFhss.bw = 0x02; // specific encoding + dataRate->lrFhss.narrowGrid = 1; + break; + case(RADIOLIB_LORAWAN_DATA_RATE_BW_336_KHZ): + dataRate->lrFhss.bw = 0x04; // specific encoding + dataRate->lrFhss.narrowGrid = 1; + break; + case(RADIOLIB_LORAWAN_DATA_RATE_BW_1523_KHZ): + dataRate->lrFhss.bw = 0x08; // specific encoding + dataRate->lrFhss.narrowGrid = 0; + break; + default: + return(RADIOLIB_ERR_UNSUPPORTED); + } + switch(dataRateBand & RADIOLIB_LORAWAN_DATA_RATE_CR) { + case(RADIOLIB_LORAWAN_DATA_RATE_CR_1_3): + dataRate->lrFhss.cr = 0x03; + break; + case(RADIOLIB_LORAWAN_DATA_RATE_CR_2_3): + dataRate->lrFhss.cr = 0x01; + break; + default: + return(RADIOLIB_ERR_UNSUPPORTED); + } + break; + default: + return(RADIOLIB_ERR_UNSUPPORTED); + } + + // get the currently configured modem from the radio + ModemType_t modemCurrent; + state = this->phyLayer->getModem(&modemCurrent); + RADIOLIB_ASSERT(state); + + // if the required modem is different than the current one, change over + if(modemNew != modemCurrent) { + state = this->phyLayer->setModem(modemNew); + RADIOLIB_ASSERT(state); + } + + state = this->phyLayer->checkDataRate(*dataRate); + return(state); +} + +void LoRaWANNode::processAES(const uint8_t* in, size_t len, uint8_t* key, uint8_t* out, uint32_t fCnt, uint8_t dir, uint8_t ctrId, bool counter) { + // figure out how many encryption blocks are there + size_t numBlocks = len/RADIOLIB_AES128_BLOCK_SIZE; + if(len % RADIOLIB_AES128_BLOCK_SIZE) { + numBlocks++; + } + + // generate the encryption blocks + uint8_t encBuffer[RADIOLIB_AES128_BLOCK_SIZE] = { 0 }; + uint8_t encBlock[RADIOLIB_AES128_BLOCK_SIZE] = { 0 }; + encBlock[RADIOLIB_LORAWAN_BLOCK_MAGIC_POS] = RADIOLIB_LORAWAN_ENC_BLOCK_MAGIC; + encBlock[RADIOLIB_LORAWAN_ENC_BLOCK_COUNTER_ID_POS] = ctrId; + encBlock[RADIOLIB_LORAWAN_BLOCK_DIR_POS] = dir; + LoRaWANNode::hton(&encBlock[RADIOLIB_LORAWAN_BLOCK_DEV_ADDR_POS], this->devAddr); + LoRaWANNode::hton(&encBlock[RADIOLIB_LORAWAN_BLOCK_FCNT_POS], fCnt); + + // now encrypt the input + // on downlink frames, this has a decryption effect because server actually "decrypts" the plaintext + size_t remLen = len; + for(size_t i = 0; i < numBlocks; i++) { + + if(counter) { + encBlock[RADIOLIB_LORAWAN_ENC_BLOCK_COUNTER_POS] = i + 1; + } + + // encrypt the buffer + RadioLibAES128Instance.init(key); + RadioLibAES128Instance.encryptECB(encBlock, RADIOLIB_AES128_BLOCK_SIZE, encBuffer); + + // now xor the buffer with the input + size_t xorLen = remLen; + if(xorLen > RADIOLIB_AES128_BLOCK_SIZE) { + xorLen = RADIOLIB_AES128_BLOCK_SIZE; + } + for(uint8_t j = 0; j < xorLen; j++) { + out[i*RADIOLIB_AES128_BLOCK_SIZE + j] = in[i*RADIOLIB_AES128_BLOCK_SIZE + j] ^ encBuffer[j]; + } + remLen -= xorLen; + } +} + +int16_t LoRaWANNode::checkBufferCommon(uint8_t *buffer, uint16_t size) { + // check if there are actually values in the buffer + size_t i = 0; + for(; i < size; i++) { + if(buffer[i]) { + break; + } + } + if(i == size) { + return(RADIOLIB_ERR_NETWORK_NOT_JOINED); + } + + // check integrity of the whole buffer (compare checksum to included checksum) + uint16_t checkSum = LoRaWANNode::checkSum16(buffer, size - 2); + uint16_t signature = LoRaWANNode::ntoh(&buffer[size - 2]); + if(signature != checkSum) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("Calculated checksum: %04x, expected: %04x", checkSum, signature); + return(RADIOLIB_ERR_CHECKSUM_MISMATCH); + } + return(RADIOLIB_ERR_NONE); +} + +uint16_t LoRaWANNode::checkSum16(const uint8_t *key, uint16_t keyLen) { + uint16_t checkSum = 0; + for(uint16_t i = 0; i < keyLen; i += 2) { + uint16_t word = (key[i] << 8); + if(i + 1 < keyLen) { + word |= key[i + 1]; + } + checkSum ^= word; + } + return(checkSum); +} + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/protocols/LoRaWAN/LoRaWAN.h b/software/firmware/source/libraries/RadioLib/src/protocols/LoRaWAN/LoRaWAN.h new file mode 100644 index 000000000..cc9c36419 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/protocols/LoRaWAN/LoRaWAN.h @@ -0,0 +1,1152 @@ +#if !defined(_RADIOLIB_LORAWAN_H) && !RADIOLIB_EXCLUDE_LORAWAN +#define _RADIOLIB_LORAWAN_H + +#include "../../TypeDef.h" +#include "../PhysicalLayer/PhysicalLayer.h" +#include "../../utils/Cryptography.h" + +// activation mode +#define RADIOLIB_LORAWAN_MODE_OTAA (0x07AA) +#define RADIOLIB_LORAWAN_MODE_ABP (0x0AB9) +#define RADIOLIB_LORAWAN_MODE_NONE (0x0000) + +// operation mode +#define RADIOLIB_LORAWAN_CLASS_A (0x0A) +#define RADIOLIB_LORAWAN_CLASS_B (0x0B) +#define RADIOLIB_LORAWAN_CLASS_C (0x0C) + +// preamble format +#define RADIOLIB_LORAWAN_LORA_SYNC_WORD (0x34) +#define RADIOLIB_LORAWAN_LORA_PREAMBLE_LEN (8) +#define RADIOLIB_LORAWAN_GFSK_SYNC_WORD (0xC194C1) +#define RADIOLIB_LORAWAN_GFSK_PREAMBLE_LEN (5) +#define RADIOLIB_LORAWAN_LR_FHSS_SYNC_WORD (0x2C0F7995) + +// MAC header field encoding MSB LSB DESCRIPTION +#define RADIOLIB_LORAWAN_MHDR_MTYPE_JOIN_REQUEST (0x00 << 5) // 7 5 message type: join request +#define RADIOLIB_LORAWAN_MHDR_MTYPE_JOIN_ACCEPT (0x01 << 5) // 7 5 join accept +#define RADIOLIB_LORAWAN_MHDR_MTYPE_UNCONF_DATA_UP (0x02 << 5) // 7 5 unconfirmed data up +#define RADIOLIB_LORAWAN_MHDR_MTYPE_UNCONF_DATA_DOWN (0x03 << 5) // 7 5 unconfirmed data down +#define RADIOLIB_LORAWAN_MHDR_MTYPE_CONF_DATA_UP (0x04 << 5) // 7 5 confirmed data up +#define RADIOLIB_LORAWAN_MHDR_MTYPE_CONF_DATA_DOWN (0x05 << 5) // 7 5 confirmed data down +#define RADIOLIB_LORAWAN_MHDR_MTYPE_PROPRIETARY (0x07 << 5) // 7 5 proprietary +#define RADIOLIB_LORAWAN_MHDR_MTYPE_MASK (0x07 << 5) // 7 5 bitmask of all possible options +#define RADIOLIB_LORAWAN_MHDR_MAJOR_R1 (0x00 << 0) // 1 0 major version: LoRaWAN R1 + +// frame control field encoding +#define RADIOLIB_LORAWAN_FCTRL_ADR_ENABLED (0x01 << 7) // 7 7 adaptive data rate: enabled +#define RADIOLIB_LORAWAN_FCTRL_ADR_DISABLED (0x00 << 7) // 7 7 disabled +#define RADIOLIB_LORAWAN_FCTRL_ADR_ACK_REQ (0x01 << 6) // 6 6 adaptive data rate ACK request +#define RADIOLIB_LORAWAN_FCTRL_ACK (0x01 << 5) // 5 5 confirmed message acknowledge +#define RADIOLIB_LORAWAN_FCTRL_FRAME_PENDING (0x01 << 4) // 4 4 downlink frame is pending + +// fPort field +#define RADIOLIB_LORAWAN_FPORT_MAC_COMMAND (0x00 << 0) // 7 0 payload contains MAC commands only +#define RADIOLIB_LORAWAN_FPORT_PAYLOAD_MIN (0x01 << 0) // 7 0 start of user-allowed fPort range +#define RADIOLIB_LORAWAN_FPORT_PAYLOAD_MAX (0xDF << 0) // 7 0 end of user-allowed fPort range +#define RADIOLIB_LORAWAN_FPORT_TS009 (0xE0 << 0) // 7 0 fPort used for TS009 testing +#define RADIOLIB_LORAWAN_FPORT_TS011 (0xE2 << 0) // 7 0 fPort used for TS011 Forwarding +#define RADIOLIB_LORAWAN_FPORT_RESERVED (0xE0 << 0) // 7 0 fPort values equal to and larger than this are reserved + +// data rate encoding +#define RADIOLIB_LORAWAN_DATA_RATE_MODEM (0x03 << 6) // 7 6 modem mask +#define RADIOLIB_LORAWAN_DATA_RATE_LORA (0x00 << 6) // 7 6 use LoRa modem +#define RADIOLIB_LORAWAN_DATA_RATE_FSK (0x01 << 6) // 7 6 use FSK modem +#define RADIOLIB_LORAWAN_DATA_RATE_LR_FHSS (0x02 << 6) // 7 6 use LR-FHSS modem +#define RADIOLIB_LORAWAN_DATA_RATE_SF (0x07 << 3) // 5 3 LoRa spreading factor mask +#define RADIOLIB_LORAWAN_DATA_RATE_SF_12 (0x05 << 3) // 5 3 LoRa spreading factor: SF12 +#define RADIOLIB_LORAWAN_DATA_RATE_SF_11 (0x04 << 3) // 5 3 SF11 +#define RADIOLIB_LORAWAN_DATA_RATE_SF_10 (0x03 << 3) // 5 3 SF10 +#define RADIOLIB_LORAWAN_DATA_RATE_SF_9 (0x02 << 3) // 5 3 SF9 +#define RADIOLIB_LORAWAN_DATA_RATE_SF_8 (0x01 << 3) // 5 3 SF8 +#define RADIOLIB_LORAWAN_DATA_RATE_SF_7 (0x00 << 3) // 5 3 SF7 +#define RADIOLIB_LORAWAN_DATA_RATE_BW (0x03 << 1) // 2 1 bandwidth mask +#define RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ (0x00 << 1) // 2 1 125 kHz +#define RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ (0x01 << 1) // 2 1 250 kHz +#define RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ (0x02 << 1) // 2 1 LoRa bandwidth: 500 kHz +#define RADIOLIB_LORAWAN_DATA_RATE_BW_137_KHZ (0x00 << 1) // 2 1 137 kHz +#define RADIOLIB_LORAWAN_DATA_RATE_BW_336_KHZ (0x01 << 1) // 2 1 336 kHz +#define RADIOLIB_LORAWAN_DATA_RATE_BW_1523_KHZ (0x02 << 1) // 2 1 LR-FHSS bandwidth: 1523 kHz +#define RADIOLIB_LORAWAN_DATA_RATE_CR (0x01 << 0) // 0 0 coding rate mask +#define RADIOLIB_LORAWAN_DATA_RATE_CR_1_3 (0x00 << 0) // 0 0 LR-FHSS coding rate: 1/3 +#define RADIOLIB_LORAWAN_DATA_RATE_CR_2_3 (0x01 << 0) // 0 0 2/3 +#define RADIOLIB_LORAWAN_DATA_RATE_UNUSED (0xFF << 0) // 7 0 unused data rate + +// channels and channel plans +#define RADIOLIB_LORAWAN_UPLINK (0x00 << 0) +#define RADIOLIB_LORAWAN_DOWNLINK (0x01 << 0) +#define RADIOLIB_LORAWAN_DIR_RX2 (0x02 << 0) +#define RADIOLIB_LORAWAN_BAND_DYNAMIC (0) +#define RADIOLIB_LORAWAN_BAND_FIXED (1) +#define RADIOLIB_LORAWAN_CHANNEL_NUM_DATARATES (15) +#define RADIOLIB_LORAWAN_CHANNEL_INDEX_NONE (0xFF >> 0) + +// recommended default settings +#define RADIOLIB_LORAWAN_RECEIVE_DELAY_1_MS (1000) +#define RADIOLIB_LORAWAN_RECEIVE_DELAY_2_MS ((RADIOLIB_LORAWAN_RECEIVE_DELAY_1_MS) + 1000) +#define RADIOLIB_LORAWAN_RX1_DR_OFFSET (0) +#define RADIOLIB_LORAWAN_JOIN_ACCEPT_DELAY_1_MS (5000) +#define RADIOLIB_LORAWAN_JOIN_ACCEPT_DELAY_2_MS (6000) +#define RADIOLIB_LORAWAN_MAX_FCNT_GAP (16384) +#define RADIOLIB_LORAWAN_ADR_ACK_LIMIT_EXP (0x06) +#define RADIOLIB_LORAWAN_ADR_ACK_DELAY_EXP (0x05) +#define RADIOLIB_LORAWAN_RETRANSMIT_TIMEOUT_MIN_MS (1000) +#define RADIOLIB_LORAWAN_RETRANSMIT_TIMEOUT_MAX_MS (3000) +#define RADIOLIB_LORAWAN_POWER_STEP_SIZE_DBM (-2) +#define RADIOLIB_LORAWAN_REJOIN_MAX_COUNT_N (10) // send rejoin request 16384 uplinks +#define RADIOLIB_LORAWAN_REJOIN_MAX_TIME_N (15) // once every year, not actually implemented + +// join request message layout +#define RADIOLIB_LORAWAN_JOIN_REQUEST_LEN (23) +#define RADIOLIB_LORAWAN_JOIN_REQUEST_JOIN_EUI_POS (1) +#define RADIOLIB_LORAWAN_JOIN_REQUEST_DEV_EUI_POS (9) +#define RADIOLIB_LORAWAN_JOIN_REQUEST_DEV_NONCE_POS (17) +#define RADIOLIB_LORAWAN_JOIN_REQUEST_TYPE (0xFF) +#define RADIOLIB_LORAWAN_JOIN_REQUEST_TYPE_0 (0x00) +#define RADIOLIB_LORAWAN_JOIN_REQUEST_TYPE_1 (0x01) +#define RADIOLIB_LORAWAN_JOIN_REQUEST_TYPE_2 (0x02) + +// join accept message layout +#define RADIOLIB_LORAWAN_JOIN_ACCEPT_MAX_LEN (33) +#define RADIOLIB_LORAWAN_JOIN_ACCEPT_JOIN_NONCE_POS (1) +#define RADIOLIB_LORAWAN_JOIN_ACCEPT_HOME_NET_ID_POS (4) +#define RADIOLIB_LORAWAN_JOIN_ACCEPT_DEV_ADDR_POS (7) +#define RADIOLIB_LORAWAN_JOIN_ACCEPT_DL_SETTINGS_POS (11) +#define RADIOLIB_LORAWAN_JOIN_ACCEPT_RX_DELAY_POS (12) +#define RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_POS (13) +#define RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_LEN (16) +#define RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_TYPE_POS (RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_POS + RADIOLIB_LORAWAN_JOIN_ACCEPT_CFLIST_LEN - 1) + +// join accept key derivation layout +#define RADIOLIB_LORAWAN_JOIN_ACCEPT_AES_JOIN_NONCE_POS (1) // regular keys +#define RADIOLIB_LORAWAN_JOIN_ACCEPT_AES_JOIN_EUI_POS (4) +#define RADIOLIB_LORAWAN_JOIN_ACCEPT_AES_DEV_NONCE_POS (12) +#define RADIOLIB_LORAWAN_JOIN_ACCEPT_AES_DEV_ADDR_POS (1) // relay keys + +// join accept message variables +#define RADIOLIB_LORAWAN_JOIN_ACCEPT_R_1_0 (0x00 << 7) // 7 7 LoRaWAN revision: 1.0 +#define RADIOLIB_LORAWAN_JOIN_ACCEPT_R_1_1 (0x01 << 7) // 7 7 1.1 +#define RADIOLIB_LORAWAN_JOIN_ACCEPT_F_NWK_S_INT_KEY (0x01) +#define RADIOLIB_LORAWAN_JOIN_ACCEPT_APP_S_KEY (0x02) +#define RADIOLIB_LORAWAN_JOIN_ACCEPT_S_NWK_S_INT_KEY (0x03) +#define RADIOLIB_LORAWAN_JOIN_ACCEPT_NWK_S_ENC_KEY (0x04) +#define RADIOLIB_LORAWAN_JOIN_ACCEPT_JS_ENC_KEY (0x05) +#define RADIOLIB_LORAWAN_JOIN_ACCEPT_JS_INT_KEY (0x06) +#define RADIOLIB_LORAWAN_JOIN_ACCEPT_ROOT_WOR_S_KEY (0x01) +#define RADIOLIB_LORAWAN_JOIN_ACCEPT_WOR_S_INT_KEY (0x01) +#define RADIOLIB_LORAWAN_JOIN_ACCEPT_WOR_S_ENC_KEY (0x02) + +// frame header layout +#define RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS (16) +#define RADIOLIB_LORAWAN_FHDR_DEV_ADDR_POS (RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS + 1) +#define RADIOLIB_LORAWAN_FHDR_FCTRL_POS (RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS + 5) +#define RADIOLIB_LORAWAN_FHDR_FCNT_POS (RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS + 6) +#define RADIOLIB_LORAWAN_FHDR_FOPTS_POS (RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS + 8) +#define RADIOLIB_LORAWAN_FHDR_FOPTS_LEN_MASK (0x0F) +#define RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN (15) +#define RADIOLIB_LORAWAN_FHDR_FPORT_POS(FOPTS) (RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS + 8 + (FOPTS)) +#define RADIOLIB_LORAWAN_FRAME_PAYLOAD_POS(FOPTS) (RADIOLIB_LORAWAN_FHDR_LEN_START_OFFS + 9 + (FOPTS)) +#define RADIOLIB_LORAWAN_FRAME_LEN(PAYLOAD, FOPTS) (16 + 13 + (PAYLOAD) + (FOPTS)) + +// payload encryption/MIC blocks common layout +#define RADIOLIB_LORAWAN_BLOCK_MAGIC_POS (0) +#define RADIOLIB_LORAWAN_BLOCK_CONF_FCNT_POS (1) +#define RADIOLIB_LORAWAN_BLOCK_DIR_POS (5) +#define RADIOLIB_LORAWAN_BLOCK_DEV_ADDR_POS (6) +#define RADIOLIB_LORAWAN_BLOCK_FCNT_POS (10) + +// payload encryption block layout +#define RADIOLIB_LORAWAN_ENC_BLOCK_MAGIC (0x01) +#define RADIOLIB_LORAWAN_ENC_BLOCK_COUNTER_ID_POS (4) +#define RADIOLIB_LORAWAN_ENC_BLOCK_COUNTER_POS (15) + +// payload MIC blocks layout +#define RADIOLIB_LORAWAN_MIC_BLOCK_MAGIC (0x49) +#define RADIOLIB_LORAWAN_MIC_BLOCK_LEN_POS (15) +#define RADIOLIB_LORAWAN_MIC_DATA_RATE_POS (3) +#define RADIOLIB_LORAWAN_MIC_CH_INDEX_POS (4) + +// maximum allowed dwell time on bands that implement dwell time limitations +#define RADIOLIB_LORAWAN_DWELL_TIME (400) + +// unused frame counter value +#define RADIOLIB_LORAWAN_FCNT_NONE (0xFFFFFFFF) + +// TR013 CSMA recommended values +#define RADIOLIB_LORAWAN_DIFS_DEFAULT (2) +#define RADIOLIB_LORAWAN_BACKOFF_MAX_DEFAULT (6) +#define RADIOLIB_LORAWAN_MAX_CHANGES_DEFAULT (4) + +// MAC commands +#define RADIOLIB_LORAWAN_NUM_MAC_COMMANDS (23) + +#define RADIOLIB_LORAWAN_MAC_RESET (0x01) +#define RADIOLIB_LORAWAN_MAC_LINK_CHECK (0x02) +#define RADIOLIB_LORAWAN_MAC_LINK_ADR (0x03) +#define RADIOLIB_LORAWAN_MAC_DUTY_CYCLE (0x04) +#define RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP (0x05) +#define RADIOLIB_LORAWAN_MAC_DEV_STATUS (0x06) +#define RADIOLIB_LORAWAN_MAC_NEW_CHANNEL (0x07) +#define RADIOLIB_LORAWAN_MAC_RX_TIMING_SETUP (0x08) +#define RADIOLIB_LORAWAN_MAC_TX_PARAM_SETUP (0x09) +#define RADIOLIB_LORAWAN_MAC_DL_CHANNEL (0x0A) +#define RADIOLIB_LORAWAN_MAC_REKEY (0x0B) +#define RADIOLIB_LORAWAN_MAC_ADR_PARAM_SETUP (0x0C) +#define RADIOLIB_LORAWAN_MAC_DEVICE_TIME (0x0D) +#define RADIOLIB_LORAWAN_MAC_FORCE_REJOIN (0x0E) +#define RADIOLIB_LORAWAN_MAC_REJOIN_PARAM_SETUP (0x0F) +#define RADIOLIB_LORAWAN_MAC_PROPRIETARY (0x80) + +// the length of internal MAC command queue - hopefully this is enough for most use cases +#define RADIOLIB_LORAWAN_MAC_COMMAND_QUEUE_SIZE (9) + +// the maximum number of simultaneously available channels +#define RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS (16) + +// maximum MAC command sizes +#define RADIOLIB_LORAWAN_MAX_MAC_COMMAND_LEN_DOWN (5) +#define RADIOLIB_LORAWAN_MAX_MAC_COMMAND_LEN_UP (2) +#define RADIOLIB_LORAWAN_MAX_NUM_ADR_COMMANDS (8) + +/*! + \struct LoRaWANMacCommand_t + \brief MAC command specification structure. +*/ +struct LoRaWANMacCommand_t { + /*! \brief Command ID */ + const uint8_t cid; + + /*! \brief Uplink message length */ + const uint8_t lenDn; + + /*! \brief Downlink message length */ + const uint8_t lenUp; + + /*! \brief Some commands must be resent until Class A downlink received */ + const bool persist; + + /*! \brief Whether this MAC command can be issued by the user or not */ + const bool user; +}; + +#define RADIOLIB_LORAWAN_MAC_COMMAND_NONE { .cid = 0, .lenDn = 0, .lenUp = 0, .persist = false, .user = false } + +constexpr LoRaWANMacCommand_t MacTable[RADIOLIB_LORAWAN_NUM_MAC_COMMANDS] = { + { RADIOLIB_LORAWAN_MAC_RESET, 1, 1, false, false }, + { RADIOLIB_LORAWAN_MAC_LINK_CHECK, 2, 0, false, true }, + { RADIOLIB_LORAWAN_MAC_LINK_ADR, 4, 1, false, false }, + { RADIOLIB_LORAWAN_MAC_DUTY_CYCLE, 1, 0, false, false }, + { RADIOLIB_LORAWAN_MAC_RX_PARAM_SETUP, 4, 1, true, false }, + { RADIOLIB_LORAWAN_MAC_DEV_STATUS, 0, 2, false, false }, + { RADIOLIB_LORAWAN_MAC_NEW_CHANNEL, 5, 1, false, false }, + { RADIOLIB_LORAWAN_MAC_RX_TIMING_SETUP, 1, 0, true, false }, + { RADIOLIB_LORAWAN_MAC_TX_PARAM_SETUP, 1, 0, true, false }, + { RADIOLIB_LORAWAN_MAC_DL_CHANNEL, 4, 1, true, false }, + { RADIOLIB_LORAWAN_MAC_REKEY, 1, 1, false, false }, + { RADIOLIB_LORAWAN_MAC_ADR_PARAM_SETUP, 1, 0, false, false }, + { RADIOLIB_LORAWAN_MAC_DEVICE_TIME, 5, 0, false, true }, + { RADIOLIB_LORAWAN_MAC_FORCE_REJOIN, 2, 0, false, false }, + { RADIOLIB_LORAWAN_MAC_REJOIN_PARAM_SETUP, 1, 1, false, false }, + { RADIOLIB_LORAWAN_MAC_PROPRIETARY, 5, 0, false, true }, +}; + +#define RADIOLIB_LORAWAN_NONCES_VERSION_VAL (0x0001) + +enum LoRaWANSchemeBase_t { + RADIOLIB_LORAWAN_NONCES_START = 0x00, + RADIOLIB_LORAWAN_NONCES_VERSION = RADIOLIB_LORAWAN_NONCES_START, // 2 bytes + RADIOLIB_LORAWAN_NONCES_MODE = RADIOLIB_LORAWAN_NONCES_VERSION + sizeof(uint16_t), // 2 bytes + RADIOLIB_LORAWAN_NONCES_CLASS = RADIOLIB_LORAWAN_NONCES_MODE + sizeof(uint16_t), // 1 byte + RADIOLIB_LORAWAN_NONCES_PLAN = RADIOLIB_LORAWAN_NONCES_CLASS + sizeof(uint8_t), // 1 byte + RADIOLIB_LORAWAN_NONCES_CHECKSUM = RADIOLIB_LORAWAN_NONCES_PLAN + sizeof(uint8_t), // 2 bytes + RADIOLIB_LORAWAN_NONCES_DEV_NONCE = RADIOLIB_LORAWAN_NONCES_CHECKSUM + sizeof(uint16_t), // 2 bytes + RADIOLIB_LORAWAN_NONCES_JOIN_NONCE = RADIOLIB_LORAWAN_NONCES_DEV_NONCE + sizeof(uint16_t), // 3 bytes + RADIOLIB_LORAWAN_NONCES_ACTIVE = RADIOLIB_LORAWAN_NONCES_JOIN_NONCE + 3, // 1 byte + RADIOLIB_LORAWAN_NONCES_SIGNATURE = RADIOLIB_LORAWAN_NONCES_ACTIVE + sizeof(uint8_t), // 2 bytes + RADIOLIB_LORAWAN_NONCES_BUF_SIZE = RADIOLIB_LORAWAN_NONCES_SIGNATURE + sizeof(uint16_t) // Nonces buffer size +}; + +enum LoRaWANSchemeSession_t { + RADIOLIB_LORAWAN_SESSION_START = 0x00, + RADIOLIB_LORAWAN_SESSION_NWK_SENC_KEY = RADIOLIB_LORAWAN_SESSION_START, // 16 bytes + RADIOLIB_LORAWAN_SESSION_APP_SKEY = RADIOLIB_LORAWAN_SESSION_NWK_SENC_KEY + RADIOLIB_AES128_KEY_SIZE, // 16 bytes + RADIOLIB_LORAWAN_SESSION_FNWK_SINT_KEY = RADIOLIB_LORAWAN_SESSION_APP_SKEY + RADIOLIB_AES128_KEY_SIZE, // 16 bytes + RADIOLIB_LORAWAN_SESSION_SNWK_SINT_KEY = RADIOLIB_LORAWAN_SESSION_FNWK_SINT_KEY + RADIOLIB_AES128_KEY_SIZE, // 16 bytes + RADIOLIB_LORAWAN_SESSION_DEV_ADDR = RADIOLIB_LORAWAN_SESSION_SNWK_SINT_KEY + RADIOLIB_AES128_KEY_SIZE, // 4 bytes + RADIOLIB_LORAWAN_SESSION_NONCES_SIGNATURE = RADIOLIB_LORAWAN_SESSION_DEV_ADDR + sizeof(uint32_t), // 2 bytes + RADIOLIB_LORAWAN_SESSION_FCNT_UP = RADIOLIB_LORAWAN_SESSION_NONCES_SIGNATURE + 2, // 4 bytes + RADIOLIB_LORAWAN_SESSION_N_FCNT_DOWN = RADIOLIB_LORAWAN_SESSION_FCNT_UP + sizeof(uint32_t), // 4 bytes + RADIOLIB_LORAWAN_SESSION_A_FCNT_DOWN = RADIOLIB_LORAWAN_SESSION_N_FCNT_DOWN + sizeof(uint32_t), // 4 bytes + RADIOLIB_LORAWAN_SESSION_ADR_FCNT = RADIOLIB_LORAWAN_SESSION_A_FCNT_DOWN + sizeof(uint32_t), // 4 bytes + RADIOLIB_LORAWAN_SESSION_CONF_FCNT_UP = RADIOLIB_LORAWAN_SESSION_ADR_FCNT + sizeof(uint32_t), // 4 bytes + RADIOLIB_LORAWAN_SESSION_CONF_FCNT_DOWN = RADIOLIB_LORAWAN_SESSION_CONF_FCNT_UP + sizeof(uint32_t), // 4 bytes + RADIOLIB_LORAWAN_SESSION_RJ_COUNT0 = RADIOLIB_LORAWAN_SESSION_CONF_FCNT_DOWN + sizeof(uint32_t), // 2 bytes + RADIOLIB_LORAWAN_SESSION_RJ_COUNT1 = RADIOLIB_LORAWAN_SESSION_RJ_COUNT0 + sizeof(uint16_t), // 2 bytes + RADIOLIB_LORAWAN_SESSION_HOMENET_ID = RADIOLIB_LORAWAN_SESSION_RJ_COUNT1 + sizeof(uint16_t), // 4 bytes + RADIOLIB_LORAWAN_SESSION_VERSION = RADIOLIB_LORAWAN_SESSION_HOMENET_ID + sizeof(uint32_t), // 1 byte + RADIOLIB_LORAWAN_SESSION_LINK_ADR = RADIOLIB_LORAWAN_SESSION_VERSION + sizeof(uint8_t), // 14 bytes + RADIOLIB_LORAWAN_SESSION_DUTY_CYCLE = RADIOLIB_LORAWAN_SESSION_LINK_ADR + 14, // 1 byte + RADIOLIB_LORAWAN_SESSION_RX_PARAM_SETUP = RADIOLIB_LORAWAN_SESSION_DUTY_CYCLE + 1, // 4 bytes + RADIOLIB_LORAWAN_SESSION_RX_TIMING_SETUP = RADIOLIB_LORAWAN_SESSION_RX_PARAM_SETUP + 4, // 1 byte + RADIOLIB_LORAWAN_SESSION_TX_PARAM_SETUP = RADIOLIB_LORAWAN_SESSION_RX_TIMING_SETUP + 1, // 1 byte + RADIOLIB_LORAWAN_SESSION_ADR_PARAM_SETUP = RADIOLIB_LORAWAN_SESSION_TX_PARAM_SETUP + 1, // 1 byte + RADIOLIB_LORAWAN_SESSION_REJOIN_PARAM_SETUP = RADIOLIB_LORAWAN_SESSION_ADR_PARAM_SETUP + 1, // 1 byte + RADIOLIB_LORAWAN_SESSION_BEACON_FREQ = RADIOLIB_LORAWAN_SESSION_REJOIN_PARAM_SETUP + 1, // 3 bytes + RADIOLIB_LORAWAN_SESSION_PING_SLOT_CHANNEL = RADIOLIB_LORAWAN_SESSION_BEACON_FREQ + 3, // 4 bytes + RADIOLIB_LORAWAN_SESSION_PERIODICITY = RADIOLIB_LORAWAN_SESSION_PING_SLOT_CHANNEL + 4, // 1 byte + RADIOLIB_LORAWAN_SESSION_LAST_TIME = RADIOLIB_LORAWAN_SESSION_PERIODICITY + 1, // 4 bytes + RADIOLIB_LORAWAN_SESSION_UL_CHANNELS = RADIOLIB_LORAWAN_SESSION_LAST_TIME + 4, // 16*5 bytes + RADIOLIB_LORAWAN_SESSION_DL_CHANNELS = RADIOLIB_LORAWAN_SESSION_UL_CHANNELS + RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS*5, // 16*4 bytes + RADIOLIB_LORAWAN_SESSION_AVAILABLE_CHANNELS = RADIOLIB_LORAWAN_SESSION_DL_CHANNELS + RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS*4, // 2 bytes + RADIOLIB_LORAWAN_SESSION_MAC_QUEUE = RADIOLIB_LORAWAN_SESSION_AVAILABLE_CHANNELS + sizeof(uint16_t), // 15 bytes + RADIOLIB_LORAWAN_SESSION_MAC_QUEUE_LEN = RADIOLIB_LORAWAN_SESSION_MAC_QUEUE + RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN, // 1 byte + RADIOLIB_LORAWAN_SESSION_SIGNATURE = RADIOLIB_LORAWAN_SESSION_MAC_QUEUE_LEN + sizeof(uint8_t), // 2 bytes + RADIOLIB_LORAWAN_SESSION_BUF_SIZE = RADIOLIB_LORAWAN_SESSION_SIGNATURE + sizeof(uint16_t) // Session buffer size +}; + +/*! + \struct LoRaWANChannel_t + \brief Structure to save information about LoRaWAN channels. + To save space, adjacent channels are saved in "spans". +*/ +struct LoRaWANChannel_t { + /*! \brief Whether this channel is enabled (can be used) or is disabled */ + bool enabled; + + /*! \brief The channel number, as specified by defaults or the network */ + uint8_t idx; + + /*! \brief The channel frequency (coded in 100 Hz steps) */ + uint32_t freq; + + /*! \brief Minimum allowed datarate for this channel */ + uint8_t drMin; + + /*! \brief Maximum allowed datarate for this channel (inclusive) */ + uint8_t drMax; + + /*! \brief Datarate currently in use on this channel */ + uint8_t dr; + + /*! \brief Whether this channel is available for channel selection */ + bool available; +}; + +// alias for unused channel +#define RADIOLIB_LORAWAN_CHANNEL_NONE { .enabled = false, .idx = RADIOLIB_LORAWAN_CHANNEL_INDEX_NONE, .freq = 0, \ + .drMin = 0, .drMax = 0, .dr = RADIOLIB_LORAWAN_DATA_RATE_UNUSED, .available = false } + +/*! + \struct LoRaWANChannelSpan_t + \brief Structure to save information about LoRaWAN channels. + To save space, adjacent channels are saved in "spans". +*/ +struct LoRaWANChannelSpan_t { + /*! \brief Total number of channels in the span */ + uint8_t numChannels; + + /*! \brief Center frequency of the first channel in span (coded in 100 Hz steps) */ + uint32_t freqStart; + + /*! \brief Frequency step between adjacent channels (coded in 100 Hz steps) */ + uint32_t freqStep; + + /*! \brief Minimum allowed datarate for all channels in this span */ + uint8_t drMin; + + /*! \brief Maximum allowed datarate for all channels in this span (inclusive) */ + uint8_t drMax; + + /*! \brief Allowed data rates for a join request message */ + uint8_t drJoinRequest; +}; + +// alias for unused channel span +#define RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE { .numChannels = 0, .freqStart = 0, .freqStep = 0, .drMin = 0, .drMax = 0, .drJoinRequest = RADIOLIB_LORAWAN_DATA_RATE_UNUSED } + +/*! + \struct LoRaWANBand_t + \brief Structure to save information about LoRaWAN band +*/ +struct LoRaWANBand_t { + /*! \brief Identier for this band */ + uint8_t bandNum; + + /*! \brief Whether the channels are fixed per specification, or dynamically allocated through the network (plus defaults) */ + uint8_t bandType; + + /*! \brief Minimum allowed frequency (coded in 100 Hz steps) */ + uint32_t freqMin; + + /*! \brief Maximum allowed frequency (coded in 100 Hz steps) */ + uint32_t freqMax; + + /*! \brief Array of allowed maximum application payload lengths for each data rate (N-value) */ + uint8_t payloadLenMax[RADIOLIB_LORAWAN_CHANNEL_NUM_DATARATES]; + + /*! \brief Maximum allowed output power in this band in dBm */ + int8_t powerMax; + + /*! \brief Number of power steps in this band */ + int8_t powerNumSteps; + + /*! \brief Number of milliseconds per hour of allowed Time-on-Air */ + RadioLibTime_t dutyCycle; + + /*! \brief Maximum dwell time per uplink message in milliseconds */ + RadioLibTime_t dwellTimeUp; + + /*! \brief Maximum dwell time per downlink message in milliseconds */ + RadioLibTime_t dwellTimeDn; + + /*! \brief Whether this band implements the MAC command TxParamSetupReq */ + bool txParamSupported; + + /*! \brief A set of default uplink (TX) channels for dynamic bands */ + LoRaWANChannel_t txFreqs[3]; + + /*! \brief A set of possible extra channels for the Join-Request message for dynamic bands */ + LoRaWANChannel_t txJoinReq[3]; + + /*! \brief The number of TX channel spans for fixed bands */ + uint8_t numTxSpans; + + /*! \brief Default uplink (TX) channel spans for fixed bands, including Join-Request parameters */ + LoRaWANChannelSpan_t txSpans[2]; + + /*! \brief Default downlink (RX1) channel span for fixed bands */ + LoRaWANChannelSpan_t rx1Span; + + uint8_t rx1DrTable[RADIOLIB_LORAWAN_CHANNEL_NUM_DATARATES][8]; + + /*! \brief Backup channel for downlink (RX2) window */ + LoRaWANChannel_t rx2; + + /*! \brief Relay channels for WoR uplink */ + LoRaWANChannel_t txWoR[2]; + + /*! \brief Relay channels for ACK downlink */ + LoRaWANChannel_t txAck[2]; + + /*! \brief The corresponding datarates, bandwidths and coding rates for DR index */ + uint8_t dataRates[RADIOLIB_LORAWAN_CHANNEL_NUM_DATARATES]; +}; + +// supported bands +extern const LoRaWANBand_t EU868; +extern const LoRaWANBand_t US915; +extern const LoRaWANBand_t EU433; +extern const LoRaWANBand_t AU915; +extern const LoRaWANBand_t CN500; +extern const LoRaWANBand_t AS923; +extern const LoRaWANBand_t AS923_2; +extern const LoRaWANBand_t AS923_3; +extern const LoRaWANBand_t AS923_4; +extern const LoRaWANBand_t KR920; +extern const LoRaWANBand_t IN865; + +/*! + \struct LoRaWANBandNum_t + \brief IDs of all currently supported bands +*/ +enum LoRaWANBandNum_t { + BandEU868, + BandUS915, + BandEU433, + BandAU915, + BandCN500, + BandAS923, + BandAS923_2, + BandAS923_3, + BandAS923_4, + BandKR920, + BandIN865, + BandLast +}; + +// provide easy access to the number of currently supported bands +#define RADIOLIB_LORAWAN_NUM_SUPPORTED_BANDS (BandLast - BandEU868) + +// array of currently supported bands +extern const LoRaWANBand_t* LoRaWANBands[]; + +/*! + \struct LoRaWANJoinEvent_t + \brief Structure to save extra information about activation event. +*/ +struct LoRaWANJoinEvent_t { + /*! \brief Whether a new session was started */ + bool newSession = false; + + /*! \brief The transmitted Join-Request DevNonce value */ + uint16_t devNonce = 0; + + /*! \brief The received Join-Request JoinNonce value */ + uint32_t joinNonce = 0; +}; + +/*! + \struct LoRaWANEvent_t + \brief Structure to save extra information about uplink/downlink event. +*/ +struct LoRaWANEvent_t { + /*! \brief Event direction, one of RADIOLIB_LORAWAN_CHANNEL_DIR_* */ + uint8_t dir; + + /*! \brief Whether the event is confirmed or not (e.g., confirmed uplink sent by user application) */ + bool confirmed; + + /*! \brief Whether the event is confirming a previous request + (e.g., server downlink reply to confirmed uplink sent by user application)*/ + bool confirming; + + /*! \brief Whether further downlink messages are pending on the server side. */ + bool frmPending; + + /*! \brief Datarate */ + uint8_t datarate; + + /*! \brief Frequency in MHz */ + float freq; + + /*! \brief Transmit power in dBm for uplink, or RSSI for downlink */ + int16_t power; + + /*! \brief The appropriate frame counter - for different events, different frame counters will be reported! */ + uint32_t fCnt; + + /*! \brief Port number */ + uint8_t fPort; + + /*! \brief Number of times this uplink was transmitted (ADR)*/ + uint8_t nbTrans; +}; + +/*! + \class LoRaWANNode + \brief LoRaWAN-compatible node (class A device). +*/ +class LoRaWANNode { + public: + + /*! + \brief Default constructor. + \param phy Pointer to the PhysicalLayer radio module. + \param band Pointer to the LoRaWAN band to use. + \param subBand The sub-band to be used (starting from 1!) + */ + LoRaWANNode(PhysicalLayer* phy, const LoRaWANBand_t* band, uint8_t subBand = 0); + + /*! + \brief Returns the pointer to the internal buffer that holds the LW base parameters + \returns Pointer to uint8_t array of size RADIOLIB_LORAWAN_NONCES_BUF_SIZE + */ + uint8_t* getBufferNonces(); + + /*! + \brief Fill the internal buffer that holds the LW base parameters with a supplied buffer + \param persistentBuffer Buffer that should match the internal format (previously extracted using getBufferNonces) + \returns \ref status_codes + */ + int16_t setBufferNonces(uint8_t* persistentBuffer); + + /*! + \brief Clear an active session, so that the device will have to rejoin the network. + */ + void clearSession(); + + /*! + \brief Returns the pointer to the internal buffer that holds the LW session parameters + \returns Pointer to uint8_t array of size RADIOLIB_LORAWAN_SESSION_BUF_SIZE + */ + uint8_t* getBufferSession(); + + /*! + \brief Fill the internal buffer that holds the LW session parameters with a supplied buffer + \param persistentBuffer Buffer that should match the internal format (previously extracted using getBufferSession) + \returns \ref status_codes + */ + int16_t setBufferSession(uint8_t* persistentBuffer); + + /*! + \brief Set the device credentials and activation configuration + \param joinEUI 8-byte application identifier. + \param devEUI 8-byte device identifier. + \param nwkKey Pointer to the network AES-128 key. + \param appKey Pointer to the application AES-128 key. + \returns \ref status_codes + */ + int16_t beginOTAA(uint64_t joinEUI, uint64_t devEUI, uint8_t* nwkKey, uint8_t* appKey); + + /*! + \brief Set the device credentials and activation configuration + \param addr Device address. + \param fNwkSIntKey Pointer to the Forwarding network session (LoRaWAN 1.1), NULL for LoRaWAN 1.0. + \param sNwkSIntKey Pointer to the Serving network session (LoRaWAN 1.1), NULL for LoRaWAN 1.0. + \param nwkSEncKey Pointer to the MAC command network session key [NwkSEncKey] (LoRaWAN 1.1) + or network session AES-128 key [NwkSKey] (LoRaWAN 1.0). + \param appSKey Pointer to the application session AES-128 key. + \returns \ref status_codes + */ + int16_t beginABP(uint32_t addr, uint8_t* fNwkSIntKey, uint8_t* sNwkSIntKey, uint8_t* nwkSEncKey, uint8_t* appSKey); + + /*! + \brief Join network by restoring OTAA session or performing over-the-air activation. By this procedure, + the device will perform an exchange with the network server and set all necessary configuration. + \param joinDr The datarate at which to send the join-request and any subsequent uplinks (unless ADR is enabled) + \returns \ref status_codes + */ + virtual int16_t activateOTAA(uint8_t initialDr = RADIOLIB_LORAWAN_DATA_RATE_UNUSED, LoRaWANJoinEvent_t *joinEvent = NULL); + + /*! + \brief Join network by restoring ABP session or performing over-the-air activation. + In this procedure, all necessary configuration must be provided by the user. + \param initialDr The datarate at which to send the first uplink and any subsequent uplinks (unless ADR is enabled). + \returns \ref status_codes + */ + virtual int16_t activateABP(uint8_t initialDr = RADIOLIB_LORAWAN_DATA_RATE_UNUSED); + + /*! \brief Whether there is an ongoing session active */ + bool isActivated(); + + #if defined(RADIOLIB_BUILD_ARDUINO) + /*! + \brief Send a message to the server and wait for a downlink during Rx1 and/or Rx2 window. + \param strUp Address of Arduino String that will be transmitted. + \param fPort Port number to send the message to. + \param strDown Address of Arduino String to save the received data. + \param isConfirmed Whether to send a confirmed uplink or not. + \param eventUp Pointer to a structure to store extra information about the uplink event + (fPort, frame counter, etc.). If set to NULL, no extra information will be passed to the user. + \param eventDown Pointer to a structure to store extra information about the downlink event + (fPort, frame counter, etc.). If set to NULL, no extra information will be passed to the user. + \returns Window number > 0 if downlink was received, 0 is no downlink was received, otherwise \ref status_codes + */ + virtual int16_t sendReceive(String& strUp, uint8_t fPort, String& strDown, bool isConfirmed = false, LoRaWANEvent_t* eventUp = NULL, LoRaWANEvent_t* eventDown = NULL); + #endif + + /*! + \brief Send a message to the server and wait for a downlink during Rx1 and/or Rx2 window. + \param strUp C-string that will be transmitted. + \param fPort Port number to send the message to. + \param isConfirmed Whether to send a confirmed uplink or not. + \param eventUp Pointer to a structure to store extra information about the uplink event + (fPort, frame counter, etc.). If set to NULL, no extra information will be passed to the user. + \param eventDown Pointer to a structure to store extra information about the downlink event + (fPort, frame counter, etc.). If set to NULL, no extra information will be passed to the user. + \returns Window number > 0 if downlink was received, 0 is no downlink was received, otherwise \ref status_codes + */ + virtual int16_t sendReceive(const char* strUp, uint8_t fPort, bool isConfirmed = false, LoRaWANEvent_t* eventUp = NULL, LoRaWANEvent_t* eventDown = NULL); + + /*! + \brief Send a message to the server and wait for a downlink during Rx1 and/or Rx2 window. + \param strUp C-string that will be transmitted. + \param fPort Port number to send the message to. + \param dataDown Buffer to save received data into. + \param lenDown Pointer to variable that will be used to save the number of received bytes. + \param isConfirmed Whether to send a confirmed uplink or not. + \param eventUp Pointer to a structure to store extra information about the uplink event + (fPort, frame counter, etc.). If set to NULL, no extra information will be passed to the user. + \param eventDown Pointer to a structure to store extra information about the downlink event + (fPort, frame counter, etc.). If set to NULL, no extra information will be passed to the user. + \returns Window number > 0 if downlink was received, 0 is no downlink was received, otherwise \ref status_codes + */ + virtual int16_t sendReceive(const char* strUp, uint8_t fPort, uint8_t* dataDown, size_t* lenDown, bool isConfirmed = false, LoRaWANEvent_t* eventUp = NULL, LoRaWANEvent_t* eventDown = NULL); + + /*! + \brief Send a message to the server and wait for a downlink but don't bother the user with downlink contents + \param dataUp Data to send. + \param lenUp Length of the data. + \param fPort Port number to send the message to. + \param isConfirmed Whether to send a confirmed uplink or not. + \param eventUp Pointer to a structure to store extra information about the uplink event + (fPort, frame counter, etc.). If set to NULL, no extra information will be passed to the user. + \param eventDown Pointer to a structure to store extra information about the downlink event + (fPort, frame counter, etc.). If set to NULL, no extra information will be passed to the user. + \returns Window number > 0 if downlink was received, 0 is no downlink was received, otherwise \ref status_codes + */ + virtual int16_t sendReceive(uint8_t* dataUp, size_t lenUp, uint8_t fPort = 1, bool isConfirmed = false, LoRaWANEvent_t* eventUp = NULL, LoRaWANEvent_t* eventDown = NULL); + + /*! + \brief Send a message to the server and wait for a downlink during Rx1 and/or Rx2 window. + \param dataUp Data to send. + \param lenUp Length of the data. + \param fPort Port number to send the message to. + \param dataDown Buffer to save received data into. + \param lenDown Pointer to variable that will be used to save the number of received bytes. + \param isConfirmed Whether to send a confirmed uplink or not. + \param eventUp Pointer to a structure to store extra information about the uplink event + (fPort, frame counter, etc.). If set to NULL, no extra information will be passed to the user. + \param eventDown Pointer to a structure to store extra information about the downlink event + (fPort, frame counter, etc.). If set to NULL, no extra information will be passed to the user. + \returns Window number > 0 if downlink was received, 0 is no downlink was received, otherwise \ref status_codes + */ + virtual int16_t sendReceive(uint8_t* dataUp, size_t lenUp, uint8_t fPort, uint8_t* dataDown, size_t* lenDown, bool isConfirmed = false, LoRaWANEvent_t* eventUp = NULL, LoRaWANEvent_t* eventDown = NULL); + + /*! + \brief Add a MAC command to the uplink queue. + Only LinkCheck and DeviceTime are available to the user. + Other commands are ignored; duplicate MAC commands are discarded. + \param cid ID of the MAC command + \returns \ref status_codes + */ + int16_t sendMacCommandReq(uint8_t cid); + + /*! + \brief Returns the quality of connectivity after requesting a LinkCheck MAC command. + Returns 'true' if a network response was successfully parsed. + Returns 'false' if there was no network response / parsing failed. + \param margin Link margin in dB of LinkCheckReq demodulation at gateway side. + \param gwCnt Number of gateways that received the LinkCheckReq. + \returns \ref status_codes + */ + int16_t getMacLinkCheckAns(uint8_t* margin, uint8_t* gwCnt); + + /*! + \brief Returns the network time after requesting a DeviceTime MAC command. + Returns 'true' if a network response was successfully parsed. + Returns 'false' if there was no network response / parsing failed. + \param gpsEpoch Number of seconds since GPS epoch (Jan. 6th 1980) + \param fraction Fractional-second, in 1/256-second steps + \param returnUnix If true, returns Unix timestamp instead of GPS (default true) + \returns \ref status_codes + */ + int16_t getMacDeviceTimeAns(uint32_t* gpsEpoch, uint8_t* fraction, bool returnUnix = true); + + /*! + \brief Set uplink datarate. This should not be used when ADR is enabled. + \param drUp Datarate to use for uplinks. + \returns \ref status_codes + */ + int16_t setDatarate(uint8_t drUp); + + /*! + \brief Configure TX power of the radio module. + \param txPower Output power during TX mode to be set in dBm. + \returns \ref status_codes + */ + int16_t setTxPower(int8_t txPower); + + /*! + \brief Configure the Rx2 datarate for ABP mode. + This should not be needed for LoRaWAN 1.1 as it is configured through the first downlink. + \param dr The datarate to be used for listening for downlinks in Rx2. + \returns \ref status_codes + */ + int16_t setRx2Dr(uint8_t dr); + + /*! + \brief Toggle ADR to on or off. + \param enable Whether to disable ADR or not. + */ + void setADR(bool enable = true); + + /*! + \brief Toggle adherence to dutyCycle limits to on or off. + \param enable Whether to adhere to dutyCycle limits or not (default true). + \param msPerHour The maximum allowed Time-on-Air per hour in milliseconds + (default 0 = maximum allowed for configured band). + */ + void setDutyCycle(bool enable = true, RadioLibTime_t msPerHour = 0); + + /*! + \brief Toggle adherence to dwellTime limits to on or off. + \param enable Whether to adhere to dwellTime limits or not (default true). + \param msPerUplink The maximum allowed Time-on-Air per uplink in milliseconds + (default 0 = maximum allowed for configured band). + */ + void setDwellTime(bool enable, RadioLibTime_t msPerUplink = 0); + + /*! + \brief Configures CSMA for LoRaWAN as per TR013, LoRa Alliance. + \param csmaEnabled Enable/disable CSMA for LoRaWAN. + \param maxChanges Maximum number of channel hops if channel is used (default 4). + \param backoffMax Num of BO slots to be decremented after DIFS phase. 0 to disable BO (default). + \param difsSlots Num of CADs to estimate a clear CH (default 2). + */ + void setCSMA(bool csmaEnabled, uint8_t maxChanges = 4, uint8_t backoffMax = 0, uint8_t difsSlots = 2); + + /*! + \brief Set device status. + \param battLevel Battery level to set. 0 for external power source, 1 for lowest battery, + 254 for highest battery, 255 for unable to measure. + */ + void setDeviceStatus(uint8_t battLevel); + + /*! + \brief Set the exact time a transmission should occur. Note: this is the internal clock time. + On Arduino platforms, this is the usual time supplied by millis(). + If the supplied time is larger than the current time, sendReceive() or uplink() will delay + until the scheduled time. + \param tUplink Transmission time in milliseconds, based on internal clock. + */ + void scheduleTransmission(RadioLibTime_t tUplink); + + /*! + \brief Returns the last uplink's frame counter; + also 0 if no uplink occured yet. + */ + uint32_t getFCntUp(); + + /*! + \brief Returns the last network downlink's frame counter; + also 0 if no network downlink occured yet. + */ + uint32_t getNFCntDown(); + + /*! + \brief Returns the last application downlink's frame counter; + also 0 if no application downlink occured yet. + */ + uint32_t getAFCntDown(); + + /*! + \brief Reset the downlink frame counters (application and network) + This is unsafe and can possibly allow replay attacks using downlinks. + It mainly exists as part of the TS009 Specification Verification protocol. + */ + void resetFCntDown(); + + /*! + \brief Returns the DevAddr of the device, regardless of OTAA or ABP mode + \returns 4-byte DevAddr + */ + uint32_t getDevAddr(); + + /*! + \brief Get the Time-on-air of the last uplink message (in milliseconds). + \returns (RadioLibTime_t) time-on-air (ToA) of last uplink message (in milliseconds). + */ + RadioLibTime_t getLastToA(); + + /*! + \brief Calculate the minimum interval to adhere to a certain dutyCycle. + This interval is based on the ToA of one uplink and does not actually keep track of total airtime. + \param msPerHour The maximum allowed duty cycle (in milliseconds per hour). + \param airtime The airtime of the uplink. + \returns Required interval (delay) in milliseconds between consecutive uplinks. + */ + RadioLibTime_t dutyCycleInterval(RadioLibTime_t msPerHour, RadioLibTime_t airtime); + + /*! \brief Returns time in milliseconds until next uplink is available under dutyCycle limits */ + RadioLibTime_t timeUntilUplink(); + + /*! + \brief Returns the maximum allowed uplink payload size given the current MAC state. + Most importantly, this includes dwell time limitations and ADR. + */ + uint8_t getMaxPayloadLen(); + + /*! + \brief TS009 Protocol Specification Verification switch + (allows FPort 224 and cuts off uplink payload instead of rejecting if maximum length exceeded). + */ + bool TS009 = false; + + /*! + \brief Rx window padding in milliseconds + according to the spec, the Rx window must be at least enough time to effectively detect a preamble + but we pad it a bit on both sides (start and end) to make sure it is wide enough + The larger this number the more power will be consumed! So be careful about changing it. + For debugging purposes 50 is a reasonable start, but for production devices it should + be as low as possible. + 0 is a valid time. + + 500 is the **maximum** value, but it is not a good idea to go anywhere near that. + If you have to go above 50 you probably have a bug somewhere. Check your device timing. + */ + RadioLibTime_t scanGuard = 10; + +#if !RADIOLIB_GODMODE + protected: +#endif + PhysicalLayer* phyLayer = NULL; + const LoRaWANBand_t* band = NULL; + + // a buffer that holds all LW base parameters that should persist at all times! + uint8_t bufferNonces[RADIOLIB_LORAWAN_NONCES_BUF_SIZE] = { 0 }; + + // a buffer that holds all LW session parameters that preferably persist, but can be afforded to get lost + uint8_t bufferSession[RADIOLIB_LORAWAN_SESSION_BUF_SIZE] = { 0 }; + + uint8_t fOptsUp[RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN] = { 0 }; + uint8_t fOptsDown[RADIOLIB_LORAWAN_FHDR_FOPTS_MAX_LEN] = { 0 }; + uint8_t fOptsUpLen = 0; + uint8_t fOptsDownLen = 0; + + uint16_t lwMode = RADIOLIB_LORAWAN_MODE_NONE; + uint8_t lwClass = RADIOLIB_LORAWAN_CLASS_A; + bool isActive = false; + + uint64_t joinEUI = 0; + uint64_t devEUI = 0; + uint8_t nwkKey[RADIOLIB_AES128_KEY_SIZE] = { 0 }; + uint8_t appKey[RADIOLIB_AES128_KEY_SIZE] = { 0 }; + + // the following is either provided by the network server (OTAA) + // or directly entered by the user (ABP) + uint32_t devAddr = 0; + uint8_t appSKey[RADIOLIB_AES128_KEY_SIZE] = { 0 }; + uint8_t fNwkSIntKey[RADIOLIB_AES128_KEY_SIZE] = { 0 }; + uint8_t sNwkSIntKey[RADIOLIB_AES128_KEY_SIZE] = { 0 }; + uint8_t nwkSEncKey[RADIOLIB_AES128_KEY_SIZE] = { 0 }; + uint8_t jSIntKey[RADIOLIB_AES128_KEY_SIZE] = { 0 }; + + uint16_t keyCheckSum = 0; + + // device-specific parameters, persistent through sessions + uint16_t devNonce = 0; + uint32_t joinNonce = 0; + + // session-specific parameters + uint32_t homeNetId = 0; + uint8_t adrLimitExp = RADIOLIB_LORAWAN_ADR_ACK_LIMIT_EXP; + uint8_t adrDelayExp = RADIOLIB_LORAWAN_ADR_ACK_DELAY_EXP; + uint8_t nbTrans = 1; // Number of allowed frame retransmissions + uint8_t txPowerSteps = 0; + uint8_t txPowerMax = 0; + uint32_t fCntUp = 0; + uint32_t aFCntDown = 0; + uint32_t nFCntDown = 0; + uint32_t confFCntUp = RADIOLIB_LORAWAN_FCNT_NONE; + uint32_t confFCntDown = RADIOLIB_LORAWAN_FCNT_NONE; + uint32_t adrFCnt = 0; + + // ADR is enabled by default + bool adrEnabled = true; + + // duty cycle is set upon initialization and activated in regions that impose this + bool dutyCycleEnabled = false; + uint32_t dutyCycle = 0; + + // dwell time is set upon initialization and activated in regions that impose this + bool dwellTimeEnabledUp = false; + uint16_t dwellTimeUp = 0; + bool dwellTimeEnabledDn = false; + uint16_t dwellTimeDn = 0; + + RadioLibTime_t tUplink = 0; // scheduled uplink transmission time (internal clock) + RadioLibTime_t tDownlink = 0; // time at end of downlink reception + + // enable/disable CSMA for LoRaWAN + bool csmaEnabled = false; + + // maximum number of channel hops during CSMA + uint8_t maxChanges = RADIOLIB_LORAWAN_MAX_CHANGES_DEFAULT; + + // number of backoff slots to be checked after DIFS phase. + // A random BO avoids collisions in the case where two or more nodes start the CSMA + // process at the same time. + uint8_t backoffMax = RADIOLIB_LORAWAN_BACKOFF_MAX_DEFAULT; + + // number of CADs to estimate a clear CH + uint8_t difsSlots = RADIOLIB_LORAWAN_DIFS_DEFAULT; + + // available channel frequencies from list passed during OTA activation + LoRaWANChannel_t channelPlan[2][RADIOLIB_LORAWAN_NUM_AVAILABLE_CHANNELS]; + + // currently configured channels for TX, RX1, RX2 + LoRaWANChannel_t channels[3] = { RADIOLIB_LORAWAN_CHANNEL_NONE, RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE }; + + // delays between the uplink and RX1/2 windows + // the first field is meaningless, but is used for offsetting for Rx windows 1 and 2 + RadioLibTime_t rxDelays[3] = { 0, RADIOLIB_LORAWAN_RECEIVE_DELAY_1_MS, RADIOLIB_LORAWAN_RECEIVE_DELAY_2_MS }; + + // offset between TX and RX1 (such that RX1 has equal or lower DR) + uint8_t rx1DrOffset = 0; + + // LoRaWAN revision (1.0 vs 1.1) + uint8_t rev = 0; + + // Time on Air of last uplink + RadioLibTime_t lastToA = 0; + + // timestamp to measure the RX1/2 delay (from uplink end) + RadioLibTime_t rxDelayStart = 0; + + // timestamp when the Rx1/2 windows were closed (timeout or uplink received) + RadioLibTime_t rxDelayEnd = 0; + + // device status - battery level + uint8_t battLevel = 0xFF; + + // indicates whether an uplink has MAC commands as payload + bool isMACPayload = false; + + // save the selected sub-band in case this must be restored in ADR control + uint8_t subBand = 0; + + // allow port 226 for devices implementing TS011 + bool TS011 = false; + + // this will reset the device credentials, so the device starts completely new + void clearNonces(); + + // start a fresh session using default parameters + void createSession(uint16_t lwMode, uint8_t initialDr); + + // setup Join-Request payload + void composeJoinRequest(uint8_t* joinRequestMsg); + + // extract Join-Accept payload and start a new session + int16_t processJoinAccept(LoRaWANJoinEvent_t *joinEvent); + + // a join-accept can piggy-back a set of channels or channel masks + void processCFList(uint8_t* cfList); + + // check whether payload length and fport are allowed + int16_t isValidUplink(uint8_t* len, uint8_t fPort); + + // perform ADR backoff + void adrBackoff(); + + // create an encrypted uplink buffer, composing metadata, user data and MAC data + void composeUplink(uint8_t* in, uint8_t lenIn, uint8_t* out, uint8_t fPort, bool isConfirmed); + + // generate and set the MIC of an uplink buffer (depends on selected channels) + void micUplink(uint8_t* inOut, uint8_t lenInOut); + + // transmit uplink buffer on a specified channel + int16_t transmitUplink(LoRaWANChannel_t* chnl, uint8_t* in, uint8_t len, bool retrans); + + // wait for, open and listen during receive windows; only performs listening + int16_t receiveCommon(uint8_t dir, const LoRaWANChannel_t* dlChannels, const RadioLibTime_t* dlDelays, uint8_t numWindows, RadioLibTime_t tReference); + + // extract downlink payload and process MAC commands + int16_t parseDownlink(uint8_t* data, size_t* len, LoRaWANEvent_t* event = NULL); + + // execute mac command, return the number of processed bytes for sequential processing + bool execMacCommand(uint8_t cid, uint8_t* optIn, uint8_t lenIn); + bool execMacCommand(uint8_t cid, uint8_t* optIn, uint8_t lenIn, uint8_t* optOut); + + // possible override for additional MAC commands that are not in the base specification + virtual bool derivedMacHandler(uint8_t cid, uint8_t* optIn, uint8_t lenIn, uint8_t* optOut); + + // pre-process a (set of) LinkAdrReq commands into one super-channel-mask + Tx/Dr/NbTrans fields + void preprocessMacLinkAdr(uint8_t* mPtr, uint8_t cLen, uint8_t* mAdrOpt); + + // post-process a (set of) LinkAdrAns commands depending on LoRaWAN version + void postprocessMacLinkAdr(uint8_t* ack, uint8_t cLen); + + // get the properties of a MAC command given a certain command ID + int16_t getMacCommand(uint8_t cid, LoRaWANMacCommand_t* cmd); + + // possible override for additional MAC commands that are not in the base specification + virtual int16_t derivedMacFinder(uint8_t cid, LoRaWANMacCommand_t* cmd); + + // get the length of a certain MAC command in a specific direction (up/down) + // if inclusive is true, add one for the CID byte + int16_t getMacLen(uint8_t cid, uint8_t* len, uint8_t dir, bool inclusive = false); + + // find out of a MAC command should persist destruction + // in uplink direction, some commands must persist if no downlink is received + // in downlink direction, the user-accessible MAC commands remain available for retrieval + bool isPersistentMacCommand(uint8_t cid, uint8_t dir); + + // push MAC command to queue, done by copy + int16_t pushMacCommand(uint8_t cid, uint8_t* cOcts, uint8_t* out, uint8_t* lenOut, uint8_t dir); + + // retrieve the payload of a certain MAC command, if present in the buffer + int16_t getMacPayload(uint8_t cid, uint8_t* in, uint8_t lenIn, uint8_t* out, uint8_t dir); + + // delete a specific MAC command from queue, indicated by the command ID + int16_t deleteMacCommand(uint8_t cid, uint8_t* inOut, uint8_t* lenInOut, uint8_t dir); + + // clear a MAC buffer, possible retaining persistent MAC commands + void clearMacCommands(uint8_t* inOut, uint8_t* lenInOut, uint8_t dir); + + // configure the common physical layer properties (frequency, sync word etc.) + int16_t setPhyProperties(const LoRaWANChannel_t* chnl, uint8_t dir, int8_t pwr, size_t pre = 0); + + // Performs CSMA as per LoRa Alliance Technical Recommendation 13 (TR-013). + bool csmaChannelClear(uint8_t difs, uint8_t numBackoff); + + // perform a single CAD operation for the under SF/CH combination. Returns either busy or otherwise. + bool cadChannelClear(); + + // (dynamic bands:) get or (fixed bands:) create a complete 80-bit channel mask for current configuration + void getChannelPlanMask(uint64_t* chMaskGrp0123, uint32_t* chMaskGrp45); + + // setup uplink/downlink channel data rates and frequencies + // for dynamic channels, there is a small set of predefined channels + // in case of JoinRequest, add some optional extra frequencies + void selectChannelPlanDyn(bool joinRequest = false); + + // setup uplink/downlink channel data rates and frequencies + // for fixed bands, we only allow one sub-band at a time to be selected + void selectChannelPlanFix(); + + // get the number of available channels, + // along with a 16-bit mask indicating which channels can be used next for uplink/downlink + uint8_t getAvailableChannels(uint16_t* mask); + + // (re)set/restore which channels can be used next for uplink/downlink + void setAvailableChannels(uint16_t mask); + + // select a set of random TX/RX channels for up- and downlink + int16_t selectChannels(); + + // apply a 96-bit channel mask + bool applyChannelMask(uint64_t chMaskGrp0123, uint32_t chMaskGrp45); + +#if RADIOLIB_DEBUG_PROTOCOL + // print the available channels through debug + void printChannels(); +#endif + + // method to generate message integrity code + uint32_t generateMIC(uint8_t* msg, size_t len, uint8_t* key); + + // method to verify message integrity code + // it assumes that the MIC is the last 4 bytes of the message + bool verifyMIC(uint8_t* msg, size_t len, uint8_t* key); + + // find the first usable data rate for the given band + int16_t findDataRate(uint8_t dr, DataRate_t* dataRate); + + // function to encrypt and decrypt payloads (regular uplink/downlink) + void processAES(const uint8_t* in, size_t len, uint8_t* key, uint8_t* out, uint32_t fCnt, uint8_t dir, uint8_t ctrId, bool counter); + + // 16-bit checksum method that takes a uint8_t array of even length and calculates the checksum + static uint16_t checkSum16(const uint8_t *key, uint16_t keyLen); + + // check the integrity of a buffer using a 16-bit checksum located in the last two bytes of the buffer + static int16_t checkBufferCommon(uint8_t *buffer, uint16_t size); + + // network-to-host conversion method - takes data from network packet and converts it to the host endians + template + static T ntoh(uint8_t* buff, size_t size = 0); + + // host-to-network conversion method - takes data from host variable and and converts it to network packet endians + template + static void hton(uint8_t* buff, T val, size_t size = 0); +}; + +template +T LoRaWANNode::ntoh(uint8_t* buff, size_t size) { + uint8_t* buffPtr = buff; + size_t targetSize = sizeof(T); + if(size != 0) { + targetSize = size; + } + T res = 0; + for(size_t i = 0; i < targetSize; i++) { + res |= (uint32_t)(*(buffPtr++)) << 8*i; + } + return(res); +} + +template +void LoRaWANNode::hton(uint8_t* buff, T val, size_t size) { + uint8_t* buffPtr = buff; + size_t targetSize = sizeof(T); + if(size != 0) { + targetSize = size; + } + for(size_t i = 0; i < targetSize; i++) { + *(buffPtr++) = val >> 8*i; + } +} + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/protocols/LoRaWAN/LoRaWANBands.cpp b/software/firmware/source/libraries/RadioLib/src/protocols/LoRaWAN/LoRaWANBands.cpp new file mode 100644 index 000000000..d29126aa0 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/protocols/LoRaWAN/LoRaWANBands.cpp @@ -0,0 +1,879 @@ +#include "LoRaWAN.h" + +#if !RADIOLIB_EXCLUDE_LORAWAN + +// array of pointers to currently supported LoRaWAN bands +const LoRaWANBand_t* LoRaWANBands[RADIOLIB_LORAWAN_NUM_SUPPORTED_BANDS] = { + &EU868, + &US915, + &EU433, + &AU915, + &CN500, + &AS923, + &AS923_2, + &AS923_3, + &AS923_4, + &KR920, + &IN865, +}; + +const LoRaWANBand_t EU868 = { + .bandNum = BandEU868, + .bandType = RADIOLIB_LORAWAN_BAND_DYNAMIC, + .freqMin = 8630000, + .freqMax = 8700000, + .payloadLenMax = { 51, 51, 51, 115, 242, 242, 242, 242, 50, 115, 50, 115, 0, 0, 0 }, + .powerMax = 16, + .powerNumSteps = 7, + .dutyCycle = 36000, + .dwellTimeUp = 0, + .dwellTimeDn = 0, + .txParamSupported = false, + .txFreqs = { + { .enabled = true, .idx = 0, .freq = 8681000, .drMin = 0, .drMax = 5, .dr = 5, .available = true }, + { .enabled = true, .idx = 1, .freq = 8683000, .drMin = 0, .drMax = 5, .dr = 5, .available = true }, + { .enabled = true, .idx = 2, .freq = 8685000, .drMin = 0, .drMax = 5, .dr = 5, .available = true }, + }, + .txJoinReq = { + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, + .numTxSpans = 0, + .txSpans = { + RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, + RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE + }, + .rx1Span = RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, + .rx1DrTable = { + { 0, 0, 0, 0, 0, 0, 0xFF, 0xFF }, + { 1, 0, 0, 0, 0, 0, 0xFF, 0xFF }, + { 2, 1, 0, 0, 0, 0, 0xFF, 0xFF }, + { 3, 2, 1, 0, 0, 0, 0xFF, 0xFF }, + { 4, 3, 2, 1, 0, 0, 0xFF, 0xFF }, + { 5, 4, 3, 2, 1, 0, 0xFF, 0xFF }, + { 6, 5, 4, 3, 2, 1, 0xFF, 0xFF }, + { 7, 6, 5, 4, 3, 2, 0xFF, 0xFF }, + { 1, 0, 0, 0, 0, 0, 0xFF, 0xFF }, + { 2, 1, 0, 0, 0, 0, 0xFF, 0xFF }, + { 1, 0, 0, 0, 0, 0, 0xFF, 0xFF }, + { 2, 1, 0, 0, 0, 0, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } + }, + .rx2 = { .enabled = true, .idx = 0, .freq = 8695250, .drMin = 0, .drMax = 7, .dr = 0, .available = true }, + .txWoR = { + { .enabled = true, .idx = 0, .freq = 8651000, .drMin = 3, .drMax = 3, .dr = 3, .available = true }, + { .enabled = true, .idx = 1, .freq = 8655000, .drMin = 3, .drMax = 3, .dr = 3, .available = true } + }, + .txAck = { + { .enabled = true, .idx = 0, .freq = 8653000, .drMin = 3, .drMax = 3, .dr = 3, .available = true }, + { .enabled = true, .idx = 1, .freq = 8659000, .drMin = 3, .drMax = 3, .dr = 3, .available = true } + }, + .dataRates = { + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_FSK, + RADIOLIB_LORAWAN_DATA_RATE_LR_FHSS | RADIOLIB_LORAWAN_DATA_RATE_CR_1_3 | RADIOLIB_LORAWAN_DATA_RATE_BW_137_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LR_FHSS | RADIOLIB_LORAWAN_DATA_RATE_CR_2_3 | RADIOLIB_LORAWAN_DATA_RATE_BW_137_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LR_FHSS | RADIOLIB_LORAWAN_DATA_RATE_CR_1_3 | RADIOLIB_LORAWAN_DATA_RATE_BW_336_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LR_FHSS | RADIOLIB_LORAWAN_DATA_RATE_CR_2_3 | RADIOLIB_LORAWAN_DATA_RATE_BW_336_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED + } +}; + +const LoRaWANBand_t US915 = { + .bandNum = BandUS915, + .bandType = RADIOLIB_LORAWAN_BAND_FIXED, + .freqMin = 9020000, + .freqMax = 9280000, + .payloadLenMax = { 11, 53, 125, 242, 242, 50, 125, 0, 53, 129, 242, 242, 242, 242, 0 }, + .powerMax = 30, + .powerNumSteps = 10, + .dutyCycle = 0, + .dwellTimeUp = RADIOLIB_LORAWAN_DWELL_TIME, + .dwellTimeDn = 0, + .txParamSupported = false, + .txFreqs = { + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, + .txJoinReq = { + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, + .numTxSpans = 2, + .txSpans = { + { + .numChannels = 64, + .freqStart = 9023000, + .freqStep = 2000, + .drMin = 0, + .drMax = 3, + .drJoinRequest = 0 + }, + { + .numChannels = 8, + .freqStart = 9030000, + .freqStep = 16000, + .drMin = 4, + .drMax = 4, + .drJoinRequest = 4 + } + }, + .rx1Span = { + .numChannels = 8, + .freqStart = 9233000, + .freqStep = 6000, + .drMin = 8, + .drMax = 13, + .drJoinRequest = RADIOLIB_LORAWAN_DATA_RATE_UNUSED + }, + .rx1DrTable = { + { 10, 9, 8, 8, 0xFF, 0xFF, 0xFF, 0xFF }, + { 11, 10, 9, 8, 0xFF, 0xFF, 0xFF, 0xFF }, + { 12, 11, 10, 9, 0xFF, 0xFF, 0xFF, 0xFF }, + { 13, 12, 11, 10, 0xFF, 0xFF, 0xFF, 0xFF }, + { 13, 13, 12, 11, 0xFF, 0xFF, 0xFF, 0xFF }, + { 10, 9, 8, 8, 0xFF, 0xFF, 0xFF, 0xFF }, + { 11, 10, 9, 8, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } + }, + .rx2 = { .enabled = true, .idx = 0, .freq = 9233000, .drMin = 8, .drMax = 13, .dr = 8, .available = true }, + .txWoR = { + { .enabled = true, .idx = 0, .freq = 9167000, .drMin = 10, .drMax = 10, .dr = 10, .available = true }, + { .enabled = true, .idx = 1, .freq = 9199000, .drMin = 10, .drMax = 10, .dr = 10, .available = true } + }, + .txAck = { + { .enabled = true, .idx = 0, .freq = 9183000, .drMin = 10, .drMax = 10, .dr = 10, .available = true }, + { .enabled = true, .idx = 1, .freq = 9215000, .drMin = 10, .drMax = 10, .dr = 10, .available = true } + }, + .dataRates = { + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LR_FHSS | RADIOLIB_LORAWAN_DATA_RATE_CR_1_3 | RADIOLIB_LORAWAN_DATA_RATE_BW_1523_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LR_FHSS | RADIOLIB_LORAWAN_DATA_RATE_CR_2_3 | RADIOLIB_LORAWAN_DATA_RATE_BW_1523_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED + } +}; + +const LoRaWANBand_t EU433 = { + .bandNum = BandEU433, + .bandType = RADIOLIB_LORAWAN_BAND_DYNAMIC, + .freqMin = 4330000, + .freqMax = 4340000, + .payloadLenMax = { 51, 51, 51, 115, 242, 242, 242, 242, 0, 0, 0, 0, 0, 0, 0 }, + .powerMax = 12, + .powerNumSteps = 5, + .dutyCycle = 36000, + .dwellTimeUp = 0, + .dwellTimeDn = 0, + .txParamSupported = false, + .txFreqs = { + { .enabled = true, .idx = 0, .freq = 4331750, .drMin = 0, .drMax = 5, .dr = 5, .available = true }, + { .enabled = true, .idx = 1, .freq = 4333750, .drMin = 0, .drMax = 5, .dr = 5, .available = true }, + { .enabled = true, .idx = 2, .freq = 4335750, .drMin = 0, .drMax = 5, .dr = 5, .available = true }, + }, + .txJoinReq = { + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, + .numTxSpans = 0, + .txSpans = { + RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, + RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE + }, + .rx1Span = RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, + .rx1DrTable = { + { 0, 0, 0, 0, 0, 0, 0xFF, 0xFF }, + { 1, 0, 0, 0, 0, 0, 0xFF, 0xFF }, + { 2, 1, 0, 0, 0, 0, 0xFF, 0xFF }, + { 3, 2, 1, 0, 0, 0, 0xFF, 0xFF }, + { 4, 3, 2, 1, 0, 0, 0xFF, 0xFF }, + { 5, 4, 3, 2, 1, 0, 0xFF, 0xFF }, + { 6, 5, 4, 3, 2, 1, 0xFF, 0xFF }, + { 7, 6, 5, 4, 3, 2, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } + }, + .rx2 = { .enabled = true, .idx = 0, .freq = 4346650, .drMin = 0, .drMax = 7, .dr = 0, .available = true }, + .txWoR = { + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, + .txAck = { + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, + .dataRates = { + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_FSK, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED + } +}; + +const LoRaWANBand_t AU915 = { + .bandNum = BandAU915, + .bandType = RADIOLIB_LORAWAN_BAND_FIXED, + .freqMin = 9150000, + .freqMax = 9280000, + .payloadLenMax = { 51, 51, 51, 115, 242, 242, 242, 50, 53, 129, 242, 242, 242, 242, 0 }, + .powerMax = 30, + .powerNumSteps = 10, + .dutyCycle = 0, + .dwellTimeUp = RADIOLIB_LORAWAN_DWELL_TIME, + .dwellTimeDn = 0, + .txParamSupported = true, // conflict: not implemented according to RP v1.1 + .txFreqs = { + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, + .txJoinReq = { + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, + .numTxSpans = 2, + .txSpans = { + { + .numChannels = 64, + .freqStart = 9152000, + .freqStep = 2000, + .drMin = 0, + .drMax = 5, + .drJoinRequest = 2 + }, + { + .numChannels = 8, + .freqStart = 9159000, + .freqStep = 16000, + .drMin = 6, + .drMax = 6, + .drJoinRequest = 6 + } + }, + .rx1Span = { + .numChannels = 8, + .freqStart = 9233000, + .freqStep = 6000, + .drMin = 8, + .drMax = 13, + .drJoinRequest = RADIOLIB_LORAWAN_DATA_RATE_UNUSED + }, + .rx1DrTable = { + { 8, 8, 8, 8, 8, 8, 0xFF, 0xFF }, + { 9, 8, 8, 8, 8, 8, 0xFF, 0xFF }, + { 10, 9, 8, 8, 8, 8, 0xFF, 0xFF }, + { 11, 10, 9, 8, 8, 8, 0xFF, 0xFF }, + { 12, 11, 10, 9, 8, 8, 0xFF, 0xFF }, + { 13, 12, 11, 10, 9, 8, 0xFF, 0xFF }, + { 13, 13, 12, 11, 10, 9, 0xFF, 0xFF }, + { 9, 8, 8, 8, 8, 8, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } + }, + .rx2 = { .enabled = true, .idx = 0, .freq = 9233000, .drMin = 8, .drMax = 13, .dr = 8, .available = true }, + .txWoR = { + { .enabled = true, .idx = 0, .freq = 9167000, .drMin = 10, .drMax = 10, .dr = 10, .available = true }, + { .enabled = true, .idx = 1, .freq = 9199000, .drMin = 10, .drMax = 10, .dr = 10, .available = true } + }, + .txAck = { + { .enabled = true, .idx = 0, .freq = 9183000, .drMin = 10, .drMax = 10, .dr = 10, .available = true }, + { .enabled = true, .idx = 1, .freq = 9215000, .drMin = 10, .drMax = 10, .dr = 10, .available = true } + }, + .dataRates = { + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LR_FHSS | RADIOLIB_LORAWAN_DATA_RATE_CR_1_3 | RADIOLIB_LORAWAN_DATA_RATE_BW_1523_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_500_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED + } +}; + +const LoRaWANBand_t CN500 = { + .bandNum = BandCN500, + .bandType = RADIOLIB_LORAWAN_BAND_FIXED, + .freqMin = 4700000, + .freqMax = 5100000, + .payloadLenMax = { 51, 51, 51, 115, 242, 242, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + .powerMax = 19, + .powerNumSteps = 7, + .dutyCycle = 0, + .dwellTimeUp = 0, + .dwellTimeDn = 0, + .txParamSupported = false, + .txFreqs = { + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, + .txJoinReq = { + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, + .numTxSpans = 1, + .txSpans = { + { + .numChannels = 96, + .freqStart = 4703000, + .freqStep = 2000, + .drMin = 0, + .drMax = 5, + .drJoinRequest = 0 + }, + RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE + }, + .rx1Span = { + .numChannels = 48, + .freqStart = 5003000, + .freqStep = 2000, + .drMin = 0, + .drMax = 5, + .drJoinRequest = RADIOLIB_LORAWAN_DATA_RATE_UNUSED + }, + .rx1DrTable = { + { 0, 0, 0, 0, 0, 0, 0xFF, 0xFF }, + { 1, 1, 1, 1, 1, 1, 0xFF, 0xFF }, + { 2, 1, 1, 1, 1, 1, 0xFF, 0xFF }, + { 3, 2, 1, 1, 1, 1, 0xFF, 0xFF }, + { 4, 3, 2, 1, 1, 1, 0xFF, 0xFF }, + { 5, 4, 3, 2, 1, 1, 0xFF, 0xFF }, + { 6, 5, 4, 3, 2, 1, 0xFF, 0xFF }, + { 7, 6, 5, 4, 3, 2, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } + }, + .rx2 = { .enabled = true, .idx = 0, .freq = 5053000, .drMin = 0, .drMax = 5, .dr = 0, .available = true }, + .txWoR = { + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, + .txAck = { + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, + .dataRates = { + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED + } +}; + +const LoRaWANBand_t AS923 = { + .bandNum = BandAS923, + .bandType = RADIOLIB_LORAWAN_BAND_DYNAMIC, + .freqMin = 9150000, + .freqMax = 9280000, + .payloadLenMax = { 51, 51, 115, 115, 242, 242, 242, 242, 0, 0, 0, 0, 0, 0, 0 }, + .powerMax = 16, + .powerNumSteps = 7, + .dutyCycle = 36000, + .dwellTimeUp = RADIOLIB_LORAWAN_DWELL_TIME, + .dwellTimeDn = RADIOLIB_LORAWAN_DWELL_TIME, + .txParamSupported = true, + .txFreqs = { + { .enabled = true, .idx = 0, .freq = 9232000, .drMin = 0, .drMax = 5, .dr = 5, .available = true }, + { .enabled = true, .idx = 1, .freq = 9234000, .drMin = 0, .drMax = 5, .dr = 5, .available = true }, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, + .txJoinReq = { + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, + .numTxSpans = 0, + .txSpans = { + RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, + RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE + }, + .rx1Span = RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, + .rx1DrTable = { + { 0, 0, 0, 0, 0, 0, 1, 2 }, // note: + { 1, 0, 0, 0, 0, 0, 2, 3 }, // when downlinkDwellTime is one + { 2, 1, 0, 0, 0, 0, 3, 4 }, // we should clip any value <2 to 2 + { 3, 2, 1, 0, 0, 0, 4, 5 }, + { 4, 3, 2, 1, 0, 0, 5, 6 }, + { 5, 4, 3, 2, 1, 0, 6, 7 }, + { 6, 5, 4, 3, 2, 1, 7, 7 }, + { 7, 6, 5, 4, 3, 2, 7, 7 }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } + }, + .rx2 = { .enabled = true, .idx = 0, .freq = 9232000, .drMin = 0, .drMax = 7, .dr = 2, .available = true }, + .txWoR = { + { .enabled = true, .idx = 0, .freq = 9236000, .drMin = 3, .drMax = 3, .dr = 3, .available = true }, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, + .txAck = { + { .enabled = true, .idx = 0, .freq = 9238000, .drMin = 3, .drMax = 3, .dr = 3, .available = true }, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, + .dataRates = { + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_FSK, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED + } +}; + +const LoRaWANBand_t AS923_2 = { + .bandNum = BandAS923_2, + .bandType = RADIOLIB_LORAWAN_BAND_DYNAMIC, + .freqMin = 9150000, + .freqMax = 9280000, + .payloadLenMax = { 51, 51, 115, 115, 242, 242, 242, 242, 0, 0, 0, 0, 0, 0, 0 }, + .powerMax = 16, + .powerNumSteps = 7, + .dutyCycle = 36000, + .dwellTimeUp = RADIOLIB_LORAWAN_DWELL_TIME, + .dwellTimeDn = RADIOLIB_LORAWAN_DWELL_TIME, + .txParamSupported = true, + .txFreqs = { + { .enabled = true, .idx = 0, .freq = 9214000, .drMin = 0, .drMax = 5, .dr = 5, .available = true }, + { .enabled = true, .idx = 1, .freq = 9216000, .drMin = 0, .drMax = 5, .dr = 5, .available = true }, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, + .txJoinReq = { + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, + .numTxSpans = 0, + .txSpans = { + RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, + RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE + }, + .rx1Span = RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, + .rx1DrTable = { + { 0, 0, 0, 0, 0, 0, 1, 2 }, // note: + { 1, 0, 0, 0, 0, 0, 2, 3 }, // when downlinkDwellTime is one + { 2, 1, 0, 0, 0, 0, 3, 4 }, // we should clip any value <2 to 2 + { 3, 2, 1, 0, 0, 0, 4, 5 }, + { 4, 3, 2, 1, 0, 0, 5, 6 }, + { 5, 4, 3, 2, 1, 0, 6, 7 }, + { 6, 5, 4, 3, 2, 1, 7, 7 }, + { 7, 6, 5, 4, 3, 2, 7, 7 }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } + }, + .rx2 = { .enabled = true, .idx = 0, .freq = 9214000, .drMin = 0, .drMax = 7, .dr = 2, .available = true }, + .txWoR = { + { .enabled = true, .idx = 0, .freq = 9218000, .drMin = 3, .drMax = 3, .dr = 3, .available = true }, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, + .txAck = { + { .enabled = true, .idx = 0, .freq = 9220000, .drMin = 3, .drMax = 3, .dr = 3, .available = true }, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, + .dataRates = { + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_FSK, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED + } +}; + +const LoRaWANBand_t AS923_3 = { + .bandNum = BandAS923_3, + .bandType = RADIOLIB_LORAWAN_BAND_DYNAMIC, + .freqMin = 9150000, + .freqMax = 9280000, + .payloadLenMax = { 51, 51, 115, 115, 242, 242, 242, 242, 0, 0, 0, 0, 0, 0, 0 }, + .powerMax = 16, + .powerNumSteps = 7, + .dutyCycle = 36000, + .dwellTimeUp = RADIOLIB_LORAWAN_DWELL_TIME, + .dwellTimeDn = RADIOLIB_LORAWAN_DWELL_TIME, + .txParamSupported = true, + .txFreqs = { + { .enabled = true, .idx = 0, .freq = 9166000, .drMin = 0, .drMax = 5, .dr = 5, .available = true }, + { .enabled = true, .idx = 1, .freq = 9168000, .drMin = 0, .drMax = 5, .dr = 5, .available = true }, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, + .txJoinReq = { + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, + .numTxSpans = 0, + .txSpans = { + RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, + RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE + }, + .rx1Span = RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, + .rx1DrTable = { + { 0, 0, 0, 0, 0, 0, 1, 2 }, // note: + { 1, 0, 0, 0, 0, 0, 2, 3 }, // when downlinkDwellTime is one + { 2, 1, 0, 0, 0, 0, 3, 4 }, // we should clip any value <2 to 2 + { 3, 2, 1, 0, 0, 0, 4, 5 }, + { 4, 3, 2, 1, 0, 0, 5, 6 }, + { 5, 4, 3, 2, 1, 0, 6, 7 }, + { 6, 5, 4, 3, 2, 1, 7, 7 }, + { 7, 6, 5, 4, 3, 2, 7, 7 }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } + }, + .rx2 = { .enabled = true, .idx = 0, .freq = 9166000, .drMin = 0, .drMax = 7, .dr = 2, .available = true }, + .txWoR = { + { .enabled = true, .idx = 0, .freq = 9170000, .drMin = 3, .drMax = 3, .dr = 3, .available = true }, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, + .txAck = { + { .enabled = true, .idx = 0, .freq = 9172000, .drMin = 3, .drMax = 3, .dr = 3, .available = true }, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, + .dataRates = { + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_FSK, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED + } +}; + +const LoRaWANBand_t AS923_4 = { + .bandNum = BandAS923_4, + .bandType = RADIOLIB_LORAWAN_BAND_DYNAMIC, + .freqMin = 9170000, + .freqMax = 9200000, + .payloadLenMax = { 51, 51, 115, 115, 242, 242, 242, 242, 0, 0, 0, 0, 0, 0, 0 }, + .powerMax = 16, + .powerNumSteps = 7, + .dutyCycle = 36000, + .dwellTimeUp = RADIOLIB_LORAWAN_DWELL_TIME, + .dwellTimeDn = RADIOLIB_LORAWAN_DWELL_TIME, + .txParamSupported = true, + .txFreqs = { + { .enabled = true, .idx = 0, .freq = 9173000, .drMin = 0, .drMax = 5, .dr = 5, .available = true }, + { .enabled = true, .idx = 1, .freq = 9175000, .drMin = 0, .drMax = 5, .dr = 5, .available = true }, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, + .txJoinReq = { + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, + .numTxSpans = 0, + .txSpans = { + RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, + RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE + }, + .rx1Span = RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, + .rx1DrTable = { + { 0, 0, 0, 0, 0, 0, 1, 2 }, // note: + { 1, 0, 0, 0, 0, 0, 2, 3 }, // when downlinkDwellTime is one + { 2, 1, 0, 0, 0, 0, 3, 4 }, // we should clip any value <2 to 2 + { 3, 2, 1, 0, 0, 0, 4, 5 }, + { 4, 3, 2, 1, 0, 0, 5, 6 }, + { 5, 4, 3, 2, 1, 0, 6, 7 }, + { 6, 5, 4, 3, 2, 1, 7, 7 }, + { 7, 6, 5, 4, 3, 2, 7, 7 }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } + }, + .rx2 = { .enabled = true, .idx = 0, .freq = 9173000, .drMin = 0, .drMax = 7, .dr = 2, .available = true }, + .txWoR = { + { .enabled = true, .idx = 0, .freq = 9177000, .drMin = 3, .drMax = 3, .dr = 3, .available = true }, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, + .txAck = { + { .enabled = true, .idx = 0, .freq = 9179000, .drMin = 3, .drMax = 3, .dr = 3, .available = true }, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, + .dataRates = { + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_250_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_FSK, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED + } +}; + +const LoRaWANBand_t KR920 = { + .bandNum = BandKR920, + .bandType = RADIOLIB_LORAWAN_BAND_DYNAMIC, + .freqMin = 9209000, + .freqMax = 9233000, + .payloadLenMax = { 51, 51, 51, 115, 242, 242, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, + .powerMax = 14, + .powerNumSteps = 7, + .dutyCycle = 0, + .dwellTimeUp = 0, + .dwellTimeDn = 0, + .txParamSupported = false, + .txFreqs = { + { .enabled = true, .idx = 0, .freq = 9221000, .drMin = 0, .drMax = 5, .dr = 5, .available = true }, + { .enabled = true, .idx = 1, .freq = 9223000, .drMin = 0, .drMax = 5, .dr = 5, .available = true }, + { .enabled = true, .idx = 2, .freq = 9225000, .drMin = 0, .drMax = 5, .dr = 5, .available = true } + }, + .txJoinReq = { + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, + .numTxSpans = 0, + .txSpans = { + RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, + RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE + }, + .rx1Span = RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, + .rx1DrTable = { + { 0, 0, 0, 0, 0, 0, 0xFF, 0xFF }, + { 1, 0, 0, 0, 0, 0, 0xFF, 0xFF }, + { 2, 1, 0, 0, 0, 0, 0xFF, 0xFF }, + { 3, 2, 1, 0, 0, 0, 0xFF, 0xFF }, + { 4, 3, 2, 1, 0, 0, 0xFF, 0xFF }, + { 5, 4, 3, 2, 1, 0, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } + }, + .rx2 = { .enabled = true, .idx = 0, .freq = 9219000, .drMin = 0, .drMax = 5, .dr = 0, .available = true }, + .txWoR = { + { .enabled = true, .idx = 0, .freq = 9227000, .drMin = 3, .drMax = 3, .dr = 3, .available = true }, + { .enabled = true, .idx = 1, .freq = 9231000, .drMin = 3, .drMax = 3, .dr = 3, .available = true } + }, + .txAck = { + { .enabled = true, .idx = 0, .freq = 9229000, .drMin = 3, .drMax = 3, .dr = 3, .available = true }, + { .enabled = true, .idx = 1, .freq = 9231000, .drMin = 3, .drMax = 3, .dr = 3, .available = true } + }, + .dataRates = { + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED + } +}; + +const LoRaWANBand_t IN865 = { + .bandNum = BandIN865, + .bandType = RADIOLIB_LORAWAN_BAND_DYNAMIC, + .freqMin = 8650000, + .freqMax = 8670000, + .payloadLenMax = { 51, 51, 51, 115, 242, 242, 0, 242, 0, 0, 0, 0, 0, 0, 0 }, + .powerMax = 30, + .powerNumSteps = 10, + .dutyCycle = 0, + .dwellTimeUp = 0, + .dwellTimeDn = 0, + .txParamSupported = false, + .txFreqs = { + { .enabled = true, .idx = 0, .freq = 8650625, .drMin = 0, .drMax = 5, .dr = 5, .available = true }, + { .enabled = true, .idx = 1, .freq = 8654025, .drMin = 0, .drMax = 5, .dr = 5, .available = true }, + { .enabled = true, .idx = 2, .freq = 8659850, .drMin = 0, .drMax = 5, .dr = 5, .available = true } + }, + .txJoinReq = { + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE, + RADIOLIB_LORAWAN_CHANNEL_NONE + }, + .numTxSpans = 0, + .txSpans = { + RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, + RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE + }, + .rx1Span = RADIOLIB_LORAWAN_CHANNEL_SPAN_NONE, + .rx1DrTable = { + { 0, 0, 0, 0, 0, 0, 1, 2 }, + { 1, 0, 0, 0, 0, 0, 2, 3 }, + { 2, 1, 0, 0, 0, 0, 3, 4 }, + { 3, 2, 1, 0, 0, 0, 4, 5 }, + { 4, 3, 2, 1, 0, 0, 5, 5 }, + { 5, 4, 3, 2, 1, 0, 5, 7 }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 7, 6, 5, 4, 3, 2, 7, 7 }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } + }, + .rx2 = { .enabled = true, .idx = 0, .freq = 8665500, .drMin = 0, .drMax = 7, .dr = 2, .available = true }, + .txWoR = { + { .enabled = true, .idx = 0, .freq = 8660000, .drMin = 3, .drMax = 3, .dr = 3, .available = true }, + { .enabled = true, .idx = 1, .freq = 8667000, .drMin = 3, .drMax = 3, .dr = 3, .available = true } + }, + .txAck = { + { .enabled = true, .idx = 0, .freq = 8662000, .drMin = 3, .drMax = 3, .dr = 3, .available = true }, + { .enabled = true, .idx = 1, .freq = 8669000, .drMin = 3, .drMax = 3, .dr = 3, .available = true } + }, + .dataRates = { + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_12 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_11 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_10 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_9 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_8 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_LORA | RADIOLIB_LORAWAN_DATA_RATE_SF_7 | RADIOLIB_LORAWAN_DATA_RATE_BW_125_KHZ, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_FSK, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED, + RADIOLIB_LORAWAN_DATA_RATE_UNUSED + } +}; + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/protocols/Morse/Morse.cpp b/software/firmware/source/libraries/RadioLib/src/protocols/Morse/Morse.cpp new file mode 100644 index 000000000..b41dae768 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/protocols/Morse/Morse.cpp @@ -0,0 +1,187 @@ +#include "Morse.h" + +#include + +#if !RADIOLIB_EXCLUDE_MORSE + +MorseClient::MorseClient(PhysicalLayer* phy) { + phyLayer = phy; + lineFeed = "^"; + #if !RADIOLIB_EXCLUDE_AFSK + audioClient = nullptr; + #endif +} + +#if !RADIOLIB_EXCLUDE_AFSK +MorseClient::MorseClient(AFSKClient* audio) { + phyLayer = audio->phyLayer; + lineFeed = "^"; + audioClient = audio; +} +#endif + +int16_t MorseClient::begin(float base, uint8_t speed) { + // calculate 24-bit frequency + baseFreqHz = base; + baseFreq = (base * 1000000.0) / phyLayer->getFreqStep(); + + // calculate tone period for decoding + basePeriod = (1000000.0f/base)/2.0f; + + // calculate symbol lengths (assumes PARIS as typical word) + dotLength = 1200 / speed; + dashLength = 3*dotLength; + letterSpace = 3*dotLength; + wordSpace = 4*dotLength; + + // configure for direct mode + return(phyLayer->startDirect()); +} + +size_t MorseClient::startSignal() { + return(MorseClient::write('_')); +} + +char MorseClient::decode(uint8_t symbol, uint8_t len) { + // add the guard bit + symbol |= (RADIOLIB_MORSE_DASH << len); + + // iterate over the table + for(uint8_t i = 0; i < sizeof(MorseTable); i++) { + uint8_t code = RADIOLIB_NONVOLATILE_READ_BYTE(&MorseTable[i]); + if(code == symbol) { + // match, return the index + ASCII offset + return((char)(i + RADIOLIB_MORSE_ASCII_OFFSET)); + } + } + + // nothing found + return(RADIOLIB_MORSE_UNSUPPORTED); +} + +#if !RADIOLIB_EXCLUDE_AFSK +int MorseClient::read(uint8_t* symbol, uint8_t* len, float low, float high) { + Module* mod = phyLayer->getMod(); + + // measure pulse duration in us + uint32_t duration = mod->hal->pulseIn(audioClient->outPin, mod->hal->GpioLevelLow, 4*basePeriod); + + // decide if this is a signal, or pause + if((duration > low*basePeriod) && (duration < high*basePeriod)) { + // this is a signal + signalCounter++; + } else if(duration == 0) { + // this is a pause + pauseCounter++; + } + + // update everything + if((pauseCounter > 0) && (signalCounter == 1)) { + // start of dot or dash + pauseCounter = 0; + signalStart = mod->hal->millis(); + uint32_t pauseLen = mod->hal->millis() - pauseStart; + + if((pauseLen >= low*(float)letterSpace) && (pauseLen <= high*(float)letterSpace)) { + return(RADIOLIB_MORSE_CHAR_COMPLETE); + } else if(pauseLen > wordSpace) { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("\n"); + return(RADIOLIB_MORSE_WORD_COMPLETE); + } + + } else if((signalCounter > 0) && (pauseCounter == 1)) { + // end of dot or dash + signalCounter = 0; + pauseStart = mod->hal->millis(); + uint32_t signalLen = mod->hal->millis() - signalStart; + + if((signalLen >= low*(float)dotLength) && (signalLen <= high*(float)dotLength)) { + RADIOLIB_DEBUG_PROTOCOL_PRINT("."); + (*symbol) |= (RADIOLIB_MORSE_DOT << (*len)); + (*len)++; + } else if((signalLen >= low*(float)dashLength) && (signalLen <= high*(float)dashLength)) { + RADIOLIB_DEBUG_PROTOCOL_PRINT("-"); + (*symbol) |= (RADIOLIB_MORSE_DASH << (*len)); + (*len)++; + } else { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("", (long unsigned int)signalLen); + } + } + + return(RADIOLIB_MORSE_INTER_SYMBOL); +} +#endif + +size_t MorseClient::write(uint8_t b) { + Module* mod = phyLayer->getMod(); + + // check unprintable ASCII characters and boundaries + if((b < ' ') || (b == 0x60) || (b > 'z')) { + return(0); + } + + // inter-word pause (space) + if(b == ' ') { + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("space"); + standby(); + mod->waitForMicroseconds(mod->hal->micros(), wordSpace*1000); + return(1); + } + + // get morse code from lookup table + uint8_t code = RADIOLIB_NONVOLATILE_READ_BYTE(&MorseTable[(uint8_t)(toupper(b) - RADIOLIB_MORSE_ASCII_OFFSET)]); + + // check unsupported characters + if(code == RADIOLIB_MORSE_UNSUPPORTED) { + return(0); + } + + // iterate through codeword until guard bit is reached + while(code > RADIOLIB_MORSE_GUARDBIT) { + + // send dot or dash + if (code & RADIOLIB_MORSE_DASH) { + RADIOLIB_DEBUG_PROTOCOL_PRINT("-"); + transmitDirect(baseFreq, baseFreqHz); + mod->waitForMicroseconds(mod->hal->micros(), dashLength*1000); + } else { + RADIOLIB_DEBUG_PROTOCOL_PRINT("."); + transmitDirect(baseFreq, baseFreqHz); + mod->waitForMicroseconds(mod->hal->micros(), dotLength*1000); + } + + // symbol space + standby(); + mod->waitForMicroseconds(mod->hal->micros(), dotLength*1000); + + // move onto the next bit + code >>= 1; + } + + // letter space + standby(); + mod->waitForMicroseconds(mod->hal->micros(), letterSpace*1000 - dotLength*1000); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN(); + + return(1); +} + +int16_t MorseClient::transmitDirect(uint32_t freq, uint32_t freqHz) { + #if !RADIOLIB_EXCLUDE_AFSK + if(audioClient != nullptr) { + return(audioClient->tone(freqHz)); + } + #endif + return(phyLayer->transmitDirect(freq)); +} + +int16_t MorseClient::standby() { + #if !RADIOLIB_EXCLUDE_AFSK + if(audioClient != nullptr) { + return(audioClient->noTone(true)); + } + #endif + return(phyLayer->standby()); +} + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/protocols/Morse/Morse.h b/software/firmware/source/libraries/RadioLib/src/protocols/Morse/Morse.h new file mode 100644 index 000000000..78d6a9107 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/protocols/Morse/Morse.h @@ -0,0 +1,181 @@ +#if !defined(_RADIOLIB_MORSE_H) && !RADIOLIB_EXCLUDE_MORSE +#define _RADIOLIB_MORSE_H + +#include "../../TypeDef.h" +#include "../PhysicalLayer/PhysicalLayer.h" +#include "../AFSK/AFSK.h" +#include "../Print/Print.h" + +#define RADIOLIB_MORSE_DOT 0b0 +#define RADIOLIB_MORSE_DASH 0b1 +#define RADIOLIB_MORSE_GUARDBIT 0b1 +#define RADIOLIB_MORSE_UNSUPPORTED 0xFF +#define RADIOLIB_MORSE_ASCII_OFFSET 32 +#define RADIOLIB_MORSE_INTER_SYMBOL 0x00 +#define RADIOLIB_MORSE_CHAR_COMPLETE 0x01 +#define RADIOLIB_MORSE_WORD_COMPLETE 0x02 + +// Morse character table: - using codes defined in ITU-R M.1677-1 +// - Morse code representation is saved LSb first, using additional bit as guard +// - position in array corresponds ASCII code minus RADIOLIB_MORSE_ASCII_OFFSET +// - ASCII characters marked RADIOLIB_MORSE_UNSUPPORTED do not have ITU-R M.1677-1 equivalent +static const uint8_t MorseTable[] RADIOLIB_NONVOLATILE = { + 0b00, // space + 0b110101, // ! (unsupported) + 0b1010010, // " + RADIOLIB_MORSE_UNSUPPORTED, // # (unsupported) + RADIOLIB_MORSE_UNSUPPORTED, // $ (unsupported) + RADIOLIB_MORSE_UNSUPPORTED, // % (unsupported) + RADIOLIB_MORSE_UNSUPPORTED, // & (unsupported) + 0b1011110, // ' + 0b101101, // ( + 0b1101101, // ) + RADIOLIB_MORSE_UNSUPPORTED, // * (unsupported) + 0b101010, // + + 0b1110011, // , + 0b1100001, // - + 0b1101010, // . + 0b101001, // / + 0b111111, // 0 + 0b111110, // 1 + 0b111100, // 2 + 0b111000, // 3 + 0b110000, // 4 + 0b100000, // 5 + 0b100001, // 6 + 0b100011, // 7 + 0b100111, // 8 + 0b101111, // 9 + 0b1000111, // : + RADIOLIB_MORSE_UNSUPPORTED, // ; (unsupported) + RADIOLIB_MORSE_UNSUPPORTED, // < (unsupported) + 0b110001, // = + RADIOLIB_MORSE_UNSUPPORTED, // > (unsupported) + 0b1001100, // ? + 0b1010110, // @ + 0b110, // A + 0b10001, // B + 0b10101, // C + 0b1001, // D + 0b10, // E + 0b10100, // F + 0b1011, // G + 0b10000, // H + 0b100, // I + 0b11110, // J + 0b1101, // K + 0b10010, // L + 0b111, // M + 0b101, // N + 0b1111, // O + 0b10110, // P + 0b11011, // Q + 0b1010, // R + 0b1000, // S + 0b11, // T + 0b1100, // U + 0b11000, // V + 0b1110, // W + 0b11001, // X + 0b11101, // Y + 0b10011, // Z + RADIOLIB_MORSE_UNSUPPORTED, // [ (unsupported) + RADIOLIB_MORSE_UNSUPPORTED, // \ (unsupported) + RADIOLIB_MORSE_UNSUPPORTED, // ] (unsupported) + 0b1101000, // ^ (unsupported, used as alias for end of work) + 0b110101 // _ (unsupported, used as alias for starting signal) +}; + +/*! + \class MorseClient + \brief Client for Morse Code communication. The public interface is the same as Arduino Serial. +*/ +class MorseClient: public RadioLibPrint { + public: + /*! + \brief Constructor for 2-FSK mode. + \param phy Pointer to the wireless module providing PhysicalLayer communication. + */ + explicit MorseClient(PhysicalLayer* phy); + + #if !RADIOLIB_EXCLUDE_AFSK + /*! + \brief Constructor for AFSK mode. + \param audio Pointer to the AFSK instance providing audio. + */ + explicit MorseClient(AFSKClient* audio); + #endif + + // basic methods + + /*! + \brief Initialization method. + \param base Base RF frequency to be used in MHz (in 2-FSK mode), or the tone frequency in Hz (in AFSK mode) + \param speed Coding speed in words per minute. + \returns \ref status_codes + */ + int16_t begin(float base, uint8_t speed = 20); + + /*! + \brief Send start signal. + \returns Number of bytes sent (always 0). + */ + size_t startSignal(); + + /*! + \brief Decode Morse symbol to ASCII. + \param symbol Morse code symbol, represented as outlined in MorseTable. + \param len Symbol length (number of dots and dashes). + \returns ASCII character matching the symbol, or 0xFF if no match is found. + */ + static char decode(uint8_t symbol, uint8_t len); + + /*! + \brief Read Morse tone on input pin. + \param symbol Pointer to the symbol buffer. + \param len Pointer to the length counter. + \param low Low threshold for decision limit (dot length, pause length etc.), defaults to 0.75. + \param high High threshold for decision limit (dot length, pause length etc.), defaults to 1.25. + \returns 0 if not enough symbols were decoded, 1 if inter-character space was detected, + 2 if inter-word space was detected. + */ + #if !RADIOLIB_EXCLUDE_AFSK + int read(uint8_t* symbol, uint8_t* len, float low = 0.75f, float high = 1.25f); + #endif + + /*! + \brief Write one byte. Implementation of interface of the RadioLibPrint/Print class. + \param b Byte to write. + \returns 1 if the byte was written, 0 otherwise. + */ + size_t write(uint8_t b) override; + +#if !RADIOLIB_GODMODE + private: +#endif + PhysicalLayer* phyLayer; + #if !RADIOLIB_EXCLUDE_AFSK + AFSKClient* audioClient; + #endif + + uint32_t baseFreq = 0, baseFreqHz = 0; + float basePeriod = 0.0f; + uint32_t dotLength = 0; + uint32_t dashLength = 0; + uint32_t letterSpace = 0; + uint16_t wordSpace = 0; + + // variables to keep decoding state + uint32_t signalCounter = 0; + RadioLibTime_t signalStart = 0; + uint32_t pauseCounter = 0; + RadioLibTime_t pauseStart = 0; + + size_t printNumber(unsigned long, uint8_t); + size_t printFloat(double, uint8_t); + + int16_t transmitDirect(uint32_t freq = 0, uint32_t freqHz = 0); + int16_t standby(); +}; + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/protocols/Pager/Pager.cpp b/software/firmware/source/libraries/RadioLib/src/protocols/Pager/Pager.cpp new file mode 100644 index 000000000..9d56ea40d --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/protocols/Pager/Pager.cpp @@ -0,0 +1,594 @@ +#include "Pager.h" + +#include +#include + +#if defined(ESP_PLATFORM) +#include "esp_attr.h" +#endif + +#if !RADIOLIB_EXCLUDE_PAGER + +#if !RADIOLIB_EXCLUDE_DIRECT_RECEIVE +// this is a massive hack, but we need a global-scope ISR to manage the bit reading +// let's hope nobody ever tries running two POCSAG receivers at the same time +static PhysicalLayer* readBitInstance = NULL; +static uint32_t readBitPin = RADIOLIB_NC; + +#if defined(ESP8266) || defined(ESP32) + IRAM_ATTR +#endif +static void PagerClientReadBit(void) { + if(readBitInstance) { + readBitInstance->readBit(readBitPin); + } +} +#endif + +PagerClient::PagerClient(PhysicalLayer* phy) { + phyLayer = phy; + #if !RADIOLIB_EXCLUDE_DIRECT_RECEIVE + readBitInstance = phyLayer; + #endif +} + +int16_t PagerClient::begin(float base, uint16_t speed, bool invert, uint16_t shift) { + // calculate duration of 1 bit in us + dataRate = (float)speed/1000.0f; + bitDuration = (RadioLibTime_t)1000000/speed; + + // calculate 24-bit frequency + baseFreq = base; + baseFreqRaw = (baseFreq * 1000000.0) / phyLayer->getFreqStep(); + + // calculate module carrier frequency resolution + uint16_t step = round(phyLayer->getFreqStep()); + + // calculate raw frequency shift + shiftFreqHz = shift; + shiftFreq = shiftFreqHz/step; + inv = invert; + + // initialize BCH encoder + RadioLibBCHInstance.begin(RADIOLIB_PAGER_BCH_N, RADIOLIB_PAGER_BCH_K, RADIOLIB_PAGER_BCH_PRIMITIVE_POLY); + + // configure for direct mode + return(phyLayer->startDirect()); +} + +int16_t PagerClient::sendTone(uint32_t addr) { + return(PagerClient::transmit(NULL, 0, addr)); +} + +#if defined(RADIOLIB_BUILD_ARDUINO) +int16_t PagerClient::transmit(String& str, uint32_t addr, uint8_t encoding, uint8_t function) { + return(PagerClient::transmit(str.c_str(), addr, encoding, function)); +} +#endif + +int16_t PagerClient::transmit(const char* str, uint32_t addr, uint8_t encoding, uint8_t function) { + return(PagerClient::transmit((uint8_t*)str, strlen(str), addr, encoding, function)); +} + +int16_t PagerClient::transmit(uint8_t* data, size_t len, uint32_t addr, uint8_t encoding, uint8_t function) { + if(addr > RADIOLIB_PAGER_ADDRESS_MAX) { + return(RADIOLIB_ERR_INVALID_ADDRESS_WIDTH); + } + + if(((data == NULL) && (len > 0)) || ((data != NULL) && (len == 0))) { + return(RADIOLIB_ERR_INVALID_PAYLOAD); + } + + // get symbol bit length based on encoding + uint8_t symbolLength = 0; + if(encoding == RADIOLIB_PAGER_BCD) { + symbolLength = 4; + + } else if(encoding == RADIOLIB_PAGER_ASCII) { + symbolLength = 7; + + } else { + return(RADIOLIB_ERR_INVALID_ENCODING); + + } + + // Automatically set function bits based on given encoding + if (function == RADIOLIB_PAGER_FUNC_AUTO) { + if(encoding == RADIOLIB_PAGER_BCD) { + function = RADIOLIB_PAGER_FUNC_BITS_NUMERIC; + + } else if(encoding == RADIOLIB_PAGER_ASCII) { + function = RADIOLIB_PAGER_FUNC_BITS_ALPHA; + + } else { + return(RADIOLIB_ERR_INVALID_ENCODING); + + } + if(len == 0) { + function = RADIOLIB_PAGER_FUNC_BITS_TONE; + } + } + if (function > RADIOLIB_PAGER_FUNC_BITS_ALPHA) { + return(RADIOLIB_ERR_INVALID_FUNCTION); + } + + // get target position in batch (3 LSB from address determine frame position in batch) + uint8_t framePos = 2*(addr & 0x07); + + // get address that will be written into address frame + uint32_t frameAddr = ((addr >> 3) << RADIOLIB_PAGER_ADDRESS_POS) | (function << RADIOLIB_PAGER_FUNC_BITS_POS); + + // calculate the number of 20-bit data blocks + size_t numDataBlocks = (len * symbolLength) / RADIOLIB_PAGER_MESSAGE_BITS_LENGTH; + if((len * symbolLength) % RADIOLIB_PAGER_MESSAGE_BITS_LENGTH > 0) { + numDataBlocks += 1; + } + + // calculate number of batches + size_t numBatches = (1 + framePos + numDataBlocks) / RADIOLIB_PAGER_BATCH_LEN + 1; + if((1 + numDataBlocks) % RADIOLIB_PAGER_BATCH_LEN == 0) { + numBatches -= 1; + } + + // calculate message length in 32-bit code words + size_t msgLen = RADIOLIB_PAGER_PREAMBLE_LENGTH + (1 + RADIOLIB_PAGER_BATCH_LEN) * numBatches; + + #if RADIOLIB_STATIC_ONLY + uint32_t msg[RADIOLIB_STATIC_ARRAY_SIZE]; + #else + uint32_t* msg = new uint32_t[msgLen]; + #endif + + // build the message + memset(msg, 0x00, msgLen*sizeof(uint32_t)); + + // set preamble + for(size_t i = 0; i < RADIOLIB_PAGER_PREAMBLE_LENGTH; i++) { + msg[i] = RADIOLIB_PAGER_PREAMBLE_CODE_WORD; + } + + // start by setting everything after preamble to idle + for(size_t i = RADIOLIB_PAGER_PREAMBLE_LENGTH; i < msgLen; i++) { + msg[i] = RADIOLIB_PAGER_IDLE_CODE_WORD; + } + + // set frame synchronization code words + for(size_t i = 0; i < numBatches; i++) { + msg[RADIOLIB_PAGER_PREAMBLE_LENGTH + i*(1 + RADIOLIB_PAGER_BATCH_LEN)] = RADIOLIB_PAGER_FRAME_SYNC_CODE_WORD; + } + + // write address code word + msg[RADIOLIB_PAGER_PREAMBLE_LENGTH + 1 + framePos] = RadioLibBCHInstance.encode(frameAddr); + + // write the data as 20-bit code blocks + if(len > 0) { + int8_t remBits = 0; + uint8_t dataPos = 0; + for(size_t i = 0; i < numDataBlocks + numBatches - 1; i++) { + uint8_t blockPos = RADIOLIB_PAGER_PREAMBLE_LENGTH + 1 + framePos + 1 + i; + + // check if we need to skip a frame sync marker + if(((blockPos - (RADIOLIB_PAGER_PREAMBLE_LENGTH + 1)) % RADIOLIB_PAGER_BATCH_LEN) == 0) { + blockPos++; + i++; + } + + // mark this as a message code word + msg[blockPos] = RADIOLIB_PAGER_MESSAGE_CODE_WORD << (RADIOLIB_PAGER_CODE_WORD_LEN - 1); + + // first insert the remainder from previous code word (if any) + if(remBits > 0) { + // this doesn't apply to BCD messages, so no need to check that here + uint8_t prev = rlb_reflect(data[dataPos - 1], 8); + prev >>= 1; + msg[blockPos] |= (uint32_t)prev << (RADIOLIB_PAGER_CODE_WORD_LEN - 1 - remBits); + } + + // set all message symbols until we overflow to the next code word or run out of message symbols + int8_t symbolPos = RADIOLIB_PAGER_CODE_WORD_LEN - 1 - symbolLength - remBits; + while(symbolPos > (RADIOLIB_PAGER_FUNC_BITS_POS - symbolLength)) { + + // for BCD, encode the symbol + uint8_t symbol = data[dataPos++]; + if(encoding == RADIOLIB_PAGER_BCD) { + symbol = encodeBCD(symbol); + } + symbol = rlb_reflect(symbol, 8); + symbol >>= (8 - symbolLength); + + // insert the next message symbol + msg[blockPos] |= (uint32_t)symbol << symbolPos; + symbolPos -= symbolLength; + + // check if we ran out of message symbols + if(dataPos >= len) { + // in BCD mode, pad the rest of the code word with spaces (0xC) + if(encoding == RADIOLIB_PAGER_BCD) { + uint8_t numSteps = (symbolPos - RADIOLIB_PAGER_FUNC_BITS_POS + symbolLength)/symbolLength; + for(uint8_t j = 0; j < numSteps; j++) { + symbol = encodeBCD(' '); + symbol = rlb_reflect(symbol, 8); + symbol >>= (8 - symbolLength); + msg[blockPos] |= (uint32_t)symbol << symbolPos; + symbolPos -= symbolLength; + } + } + break; + } + } + + // ensure the parity bits are not set due to overflow + msg[blockPos] &= ~(RADIOLIB_PAGER_BCH_BITS_MASK); + + // save the number of overflown bits + remBits = RADIOLIB_PAGER_FUNC_BITS_POS - symbolPos - symbolLength; + + // do the FEC + msg[blockPos] = RadioLibBCHInstance.encode(msg[blockPos]); + } + } + + // transmit the message + PagerClient::write(msg, msgLen); + + #if !RADIOLIB_STATIC_ONLY + delete[] msg; + #endif + + // turn transmitter off + phyLayer->standby(); + + return(RADIOLIB_ERR_NONE); +} + +#if !RADIOLIB_EXCLUDE_DIRECT_RECEIVE +int16_t PagerClient::startReceive(uint32_t pin, uint32_t addr, uint32_t mask) { + // save the variables + readBitPin = pin; + filterAddr = addr; + filterMask = mask; + filterAddresses = NULL; + filterMasks = NULL; + filterNumAddresses = 0; + return(startReceiveCommon()); +} + +int16_t PagerClient::startReceive(uint32_t pin, uint32_t *addrs, uint32_t *masks, size_t numAddresses) { + // save the variables + readBitPin = pin; + filterAddr = 0; + filterMask = 0; + filterAddresses = addrs; + filterMasks = masks; + filterNumAddresses = numAddresses; + return(startReceiveCommon()); +} + +int16_t PagerClient::startReceiveCommon() { + // set the carrier frequency + int16_t state = phyLayer->setFrequency(baseFreq); + RADIOLIB_ASSERT(state); + + // set bitrate + state = phyLayer->setBitRate(dataRate); + RADIOLIB_ASSERT(state); + + // set frequency deviation to 4.5 khz + state = phyLayer->setFrequencyDeviation((float)shiftFreqHz / 1000.0f); + RADIOLIB_ASSERT(state); + + // now set up the direct mode reception + Module* mod = phyLayer->getMod(); + mod->hal->pinMode(readBitPin, mod->hal->GpioModeInput); + + // set direct sync word to the frame sync word + // the logic here is inverted, because modules like SX1278 + // assume high frequency to be logic 1, which is opposite to POCSAG + if(!inv) { + phyLayer->setDirectSyncWord(~RADIOLIB_PAGER_FRAME_SYNC_CODE_WORD, 32); + } else { + phyLayer->setDirectSyncWord(RADIOLIB_PAGER_FRAME_SYNC_CODE_WORD, 32); + } + + phyLayer->setDirectAction(PagerClientReadBit); + phyLayer->receiveDirect(); + + return(state); +} + +size_t PagerClient::available() { + return(phyLayer->available() + sizeof(uint32_t))/(sizeof(uint32_t) * (RADIOLIB_PAGER_BATCH_LEN + 1)); +} + +#if defined(RADIOLIB_BUILD_ARDUINO) +int16_t PagerClient::readData(String& str, size_t len, uint32_t* addr) { + int16_t state = RADIOLIB_ERR_NONE; + + // determine the message length, based on user input or the amount of received data + size_t length = len; + if(length == 0) { + // one batch can contain at most 80 message symbols + length = available()*80; + } + + // build a temporary buffer + #if RADIOLIB_STATIC_ONLY + uint8_t data[RADIOLIB_STATIC_ARRAY_SIZE + 1]; + #else + uint8_t* data = new uint8_t[length + 1]; + RADIOLIB_ASSERT_PTR(data); + #endif + + // read the received data + state = readData(data, &length, addr); + + if(state == RADIOLIB_ERR_NONE) { + // check tone-only tramsissions + if(length == 0) { + length = 6; + strncpy((char*)data, "", length + 1); + } + + // add null terminator + data[length] = 0; + + // initialize Arduino String class + str = String((char*)data); + } + + // deallocate temporary buffer + #if !RADIOLIB_STATIC_ONLY + delete[] data; + #endif + + return(state); +} +#endif + +int16_t PagerClient::readData(uint8_t* data, size_t* len, uint32_t* addr) { + // find the correct address + bool match = false; + uint8_t framePos = 0; + uint8_t symbolLength = 0; + while(!match && phyLayer->available()) { + uint32_t cw = read(); + framePos++; + + // check if it's the idle code word + if(cw == RADIOLIB_PAGER_IDLE_CODE_WORD) { + continue; + } + + // check if it's the sync word + if(cw == RADIOLIB_PAGER_FRAME_SYNC_CODE_WORD) { + framePos = 0; + continue; + } + + // not an idle code word, check if it's an address word + if(cw & (RADIOLIB_PAGER_MESSAGE_CODE_WORD << (RADIOLIB_PAGER_CODE_WORD_LEN - 1))) { + // this is pretty weird, it seems to be a message code word without address + continue; + } + + // should be an address code word, extract the address + uint32_t addr_found = ((cw & RADIOLIB_PAGER_ADDRESS_BITS_MASK) >> (RADIOLIB_PAGER_ADDRESS_POS - 3)) | (framePos/2); + if (addressMatched(addr_found)) { + match = true; + if(addr) { + *addr = addr_found; + } + + // determine the encoding from the function bits + if((cw & RADIOLIB_PAGER_FUNCTION_BITS_MASK) >> RADIOLIB_PAGER_FUNC_BITS_POS == RADIOLIB_PAGER_FUNC_BITS_NUMERIC) { + symbolLength = 4; + } else { + symbolLength = 7; + } + } + } + + if(!match) { + // address not found + return(RADIOLIB_ERR_ADDRESS_NOT_FOUND); + } + + // we have the address, start pulling out the message + size_t decodedBytes = 0; + uint32_t prevCw = 0; + bool overflow = false; + int8_t ovfBits = 0; + while(phyLayer->available()) { + uint32_t cw = read(); + + // check if it's the idle code word + if(cw == RADIOLIB_PAGER_IDLE_CODE_WORD) { + break; + } + + // skip the sync words + if(cw == RADIOLIB_PAGER_FRAME_SYNC_CODE_WORD) { + continue; + } + + // check overflow from previous code word + uint8_t bitPos = RADIOLIB_PAGER_CODE_WORD_LEN - 1 - symbolLength; + if(overflow) { + overflow = false; + + // this is a bit convoluted - first, build masks for both previous and current code word + uint8_t currPos = RADIOLIB_PAGER_CODE_WORD_LEN - 1 - symbolLength + ovfBits; + uint8_t prevPos = RADIOLIB_PAGER_MESSAGE_END_POS; + uint32_t prevMask = (0x7FUL << prevPos) & ~((uint32_t)0x7FUL << (RADIOLIB_PAGER_MESSAGE_END_POS + ovfBits)); + uint32_t currMask = (0x7FUL << currPos) & ~((uint32_t)1 << (RADIOLIB_PAGER_CODE_WORD_LEN - 1)); + + // next, get the two parts of the message symbol and stick them together + uint8_t prevSymbol = (prevCw & prevMask) >> prevPos; + uint8_t currSymbol = (cw & currMask) >> currPos; + uint32_t symbol = prevSymbol << (symbolLength - ovfBits) | currSymbol; + + // finally, we can flip the bits + symbol = rlb_reflect((uint8_t)symbol, 8); + symbol >>= (8 - symbolLength); + + // decode BCD and we're done + if(symbolLength == 4) { + symbol = decodeBCD(symbol); + } + data[decodedBytes++] = symbol; + + // adjust the bit position of the next message symbol + bitPos += ovfBits; + bitPos -= symbolLength; + } + + // get the message symbols based on the encoding type + while(bitPos >= RADIOLIB_PAGER_MESSAGE_END_POS) { + // get the message symbol from the code word and reverse bits + uint32_t symbol = (cw & (0x7FUL << bitPos)) >> bitPos; + symbol = rlb_reflect((uint8_t)symbol, 8); + symbol >>= (8 - symbolLength); + + // decode BCD if needed + if(symbolLength == 4) { + symbol = decodeBCD(symbol); + } + data[decodedBytes++] = symbol; + + // now calculate if the next symbol is overflowing to the following code word + int8_t remBits = bitPos - RADIOLIB_PAGER_MESSAGE_END_POS; + if(remBits < symbolLength) { + // overflow! + prevCw = cw; + overflow = true; + ovfBits = remBits; + } + bitPos -= symbolLength; + } + + } + + // save the number of decoded bytes + *len = decodedBytes; + return(RADIOLIB_ERR_NONE); +} +#endif + +bool PagerClient::addressMatched(uint32_t addr) { + // check whether to match single or multiple addresses/masks + if(filterNumAddresses == 0) { + return((addr & filterMask) == (filterAddr & filterMask)); + } + + // multiple addresses, check there are some to match + if((filterAddresses == NULL) || (filterMasks == NULL)) { + return(false); + } + + for(size_t i = 0; i < filterNumAddresses; i++) { + if((filterAddresses[i] & filterMasks[i]) == (addr & filterMasks[i])) { + return(true); + } + } + + return(false); +} + +void PagerClient::write(uint32_t* data, size_t len) { + // write code words from buffer + for(size_t i = 0; i < len; i++) { + PagerClient::write(data[i]); + } +} + +void PagerClient::write(uint32_t codeWord) { + // write single code word + Module* mod = phyLayer->getMod(); + for(int8_t i = 31; i >= 0; i--) { + uint32_t mask = (uint32_t)0x01 << i; + RadioLibTime_t start = mod->hal->micros(); + + // figure out the shift direction - start by assuming the bit is 0 + int16_t change = shiftFreq; + + // now check if it's actually 1 + if(codeWord & mask) { + change = -shiftFreq; + } + + // finally, check if inversion is enabled + if(inv) { + change = -change; + } + + // now transmit the shifted frequency + phyLayer->transmitDirect(baseFreqRaw + change); + + // this is pretty silly, while(mod->hal->micros() ... ) would be enough + // but for some reason, MegaCore throws a linker error on it + // "relocation truncated to fit: R_AVR_7_PCREL against `no symbol'" + RadioLibTime_t now = mod->hal->micros(); + while(now - start < bitDuration) { + now = mod->hal->micros(); + } + } +} + +#if !RADIOLIB_EXCLUDE_DIRECT_RECEIVE +uint32_t PagerClient::read() { + uint32_t codeWord = 0; + codeWord |= (uint32_t)phyLayer->read() << 24; + codeWord |= (uint32_t)phyLayer->read() << 16; + codeWord |= (uint32_t)phyLayer->read() << 8; + codeWord |= (uint32_t)phyLayer->read(); + + // check if we need to invert bits + // the logic here is inverted, because modules like SX1278 + // assume high frequency to be logic 1, which is opposite to POCSAG + if(!inv) { + codeWord = ~codeWord; + } + + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("R\t%lX", (long unsigned int)codeWord); + // TODO BCH error correction here + return(codeWord); +} +#endif + +uint8_t PagerClient::encodeBCD(char c) { + switch(c) { + case '*': + return(0x0A); + case 'U': + return(0x0B); + case ' ': + return(0x0C); + case '-': + return(0x0D); + case ')': + return(0x0E); + case '(': + return(0x0F); + } + return(c - '0'); +} + +char PagerClient::decodeBCD(uint8_t b) { + switch(b) { + case 0x0A: + return('*'); + case 0x0B: + return('U'); + case 0x0C: + return(' '); + case 0x0D: + return('-'); + case 0x0E: + return(')'); + case 0x0F: + return('('); + } + return(b + '0'); +} + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/protocols/Pager/Pager.h b/software/firmware/source/libraries/RadioLib/src/protocols/Pager/Pager.h new file mode 100644 index 000000000..6963a686a --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/protocols/Pager/Pager.h @@ -0,0 +1,206 @@ +#if !defined(_RADIOLIB_PAGER_H) && !RADIOLIB_EXCLUDE_PAGER +#define _RADIOLIB_PAGER_H + +#include "../../TypeDef.h" +#include "../PhysicalLayer/PhysicalLayer.h" +#include "../../utils/FEC.h" + +// frequency shift in Hz +#define RADIOLIB_PAGER_FREQ_SHIFT_HZ (4500) + +// supported encoding schemes +#define RADIOLIB_PAGER_ASCII (0) +#define RADIOLIB_PAGER_BCD (1) + +// preamble length in 32-bit code words +#define RADIOLIB_PAGER_PREAMBLE_LENGTH (18) + +// protocol-specified code words +#define RADIOLIB_PAGER_PREAMBLE_CODE_WORD (0xAAAAAAAA) +#define RADIOLIB_PAGER_FRAME_SYNC_CODE_WORD (0x7CD215D8) +#define RADIOLIB_PAGER_IDLE_CODE_WORD (0x7A89C197) + +// code word type identification flags +#define RADIOLIB_PAGER_ADDRESS_CODE_WORD (0UL) +#define RADIOLIB_PAGER_MESSAGE_CODE_WORD (1UL) + +// length of code word in bits +#define RADIOLIB_PAGER_CODE_WORD_LEN (32) + +// number of message bits in a single code block +#define RADIOLIB_PAGER_ADDRESS_POS (13) +#define RADIOLIB_PAGER_FUNC_BITS_POS (11) +#define RADIOLIB_PAGER_MESSAGE_BITS_LENGTH (20) +#define RADIOLIB_PAGER_MESSAGE_END_POS (11) + +// number of code words in a batch +#define RADIOLIB_PAGER_BATCH_LEN (16) + +// mask for address bits in a single code word +#define RADIOLIB_PAGER_ADDRESS_BITS_MASK (0x7FFFE000UL) + +// mask for function bits in a single code word +#define RADIOLIB_PAGER_FUNCTION_BITS_MASK (0x00001800UL) + +// mask for BCH bits in a single code word +#define RADIOLIB_PAGER_BCH_BITS_MASK (0x000007FFUL) + +// message type functional bits +#define RADIOLIB_PAGER_FUNC_BITS_NUMERIC (0b00) +#define RADIOLIB_PAGER_FUNC_BITS_TONE (0b01) +#define RADIOLIB_PAGER_FUNC_BITS_ACTIVATION (0b10) +#define RADIOLIB_PAGER_FUNC_BITS_ALPHA (0b11) +#define RADIOLIB_PAGER_FUNC_AUTO 0xFF + +// the maximum allowed address (2^22 - 1) +#define RADIOLIB_PAGER_ADDRESS_MAX (2097151) + +/*! + \class PagerClient + \brief Client for Pager communication. +*/ +class PagerClient { + public: + /*! + \brief Default constructor. + \param phy Pointer to the wireless module providing PhysicalLayer communication. + */ + explicit PagerClient(PhysicalLayer* phy); + + // basic methods + + /*! + \brief Initialization method. + \param base Base (center) frequency to be used in MHz. + \param speed Bit rate to use in bps. Common POCSAG decoders can receive 512, 1200 and 2400 bps. + \param invert Enable frequency inversion. Disabled by default (high frequency is digital 0). + \param shift Set custom frequency shift, defaults to 4500 Hz. + \returns \ref status_codes + */ + int16_t begin(float base, uint16_t speed, bool invert = false, uint16_t shift = RADIOLIB_PAGER_FREQ_SHIFT_HZ); + + /*! + \brief Method to send a tone-only alert to a destination pager. + \param addr Address of the destination pager. Allowed values are 0 to 2097151 - values above 2000000 are reserved. + \returns \ref status_codes + */ + int16_t sendTone(uint32_t addr); + + #if defined(RADIOLIB_BUILD_ARDUINO) + /*! + \brief Arduino String transmit method. + \param str Address of Arduino string that will be transmitted. + \param addr Address of the destination pager. Allowed values are 0 to 2097151 - values above 2000000 are reserved. + \param encoding Encoding to be used (BCD or ASCII). Defaults to RADIOLIB_PAGER_BCD. + \param function bits (NUMERIC, TONE, ACTIVATION, ALPHANUMERIC). Allowed values 0 to 3. Defaults to auto select by specified encoding + \returns \ref status_codes + */ + int16_t transmit(String& str, uint32_t addr, uint8_t encoding = RADIOLIB_PAGER_BCD, uint8_t function = RADIOLIB_PAGER_FUNC_AUTO); + #endif + + /*! + \brief C-string transmit method. + \param str C-string that will be transmitted. + \param addr Address of the destination pager. Allowed values are 0 to 2097151 - values above 2000000 are reserved. + \param encoding Encoding to be used (BCD or ASCII). Defaults to RADIOLIB_PAGER_BCD. + \param function bits (NUMERIC, TONE, ACTIVATION, ALPHANUMERIC). Allowed values 0 to 3. Defaults to auto select by specified encoding + \returns \ref status_codes + */ + int16_t transmit(const char* str, uint32_t addr, uint8_t encoding = RADIOLIB_PAGER_BCD, uint8_t function = RADIOLIB_PAGER_FUNC_AUTO); + + /*! + \brief Binary transmit method. Will transmit arbitrary binary data. + \param data Binary data that will be transmitted. + \param len Length of binary data to transmit (in bytes). + \param addr Address of the destination pager. Allowed values are 0 to 2097151 - values above 2000000 are reserved. + \param encoding Encoding to be used (BCD or ASCII). Defaults to RADIOLIB_PAGER_BCD. + \param function bits (NUMERIC, TONE, ACTIVATION, ALPHANUMERIC). Allowed values 0 to 3. Defaults to auto select by specified encoding + \returns \ref status_codes + */ + int16_t transmit(uint8_t* data, size_t len, uint32_t addr, uint8_t encoding = RADIOLIB_PAGER_BCD, uint8_t function = RADIOLIB_PAGER_FUNC_AUTO); + + #if !RADIOLIB_EXCLUDE_DIRECT_RECEIVE + /*! + \brief Start reception of POCSAG packets. + \param pin Pin to receive digital data on (e.g., DIO2 for SX127x). + \param addr Address of this "pager". Allowed values are 0 to 2097151 - values above 2000000 are reserved. + \param mask Address filter mask - set individual bits to enable or disable match on that bit of the address. + Set to 0xFFFFF (all bits checked) by default. + \returns \ref status_codes + */ + int16_t startReceive(uint32_t pin, uint32_t addr, uint32_t mask = 0xFFFFF); + + /*! + \brief Start reception of POCSAG packets for multiple addresses and masks. + \param pin Pin to receive digital data on (e.g., DIO2 for SX127x). + \param addrs Array of addresses to receive. + \param masks Array of address masks to use for filtering. Masks will be applied to corresponding addresses in addr array. + \param numAddress Number of addresses/masks to match. + \returns \ref status_codes + */ + int16_t startReceive(uint32_t pin, uint32_t *addrs, uint32_t *masks, size_t numAddress); + + /*! + \brief Get the number of POCSAG batches available in buffer. Limited by the size of direct mode buffer! + \returns Number of available batches. + */ + size_t available(); + + #if defined(RADIOLIB_BUILD_ARDUINO) + /*! + \brief Reads data that was received after calling startReceive method. + \param str Address of Arduino String to save the received data. + \param len Expected number of characters in the message. When set to 0, the message length will be retrieved + automatically. When more bytes than received are requested, only the number of bytes requested will be returned. + \param addr Pointer to variable holding the address of the received pager message. + Set to NULL to not retrieve address. + \returns \ref status_codes + */ + int16_t readData(String& str, size_t len = 0, uint32_t* addr = NULL); + #endif + + /*! + \brief Reads data that was received after calling startReceive method. + \param data Pointer to array to save the received message. + \param len Pointer to variable holding the number of bytes that will be read. When set to 0, the packet length + will be retrieved automatically. When more bytes than received are requested, only the number of bytes + requested will be returned. Upon completion, the number of bytes received will be written to this variable. + \param addr Pointer to variable holding the address of the received pager message. + Set to NULL to not retrieve address. + \returns \ref status_codes + */ + int16_t readData(uint8_t* data, size_t* len, uint32_t* addr = NULL); +#endif + +#if !RADIOLIB_GODMODE + private: +#endif + PhysicalLayer* phyLayer; + + float baseFreq = 0; + float dataRate = 0; + uint32_t baseFreqRaw = 0; + uint16_t shiftFreq = 0; + uint16_t shiftFreqHz = 0; + RadioLibTime_t bitDuration = 0; + uint32_t filterAddr = 0; + uint32_t filterMask = 0; + uint32_t *filterAddresses = nullptr; + uint32_t *filterMasks = nullptr; + size_t filterNumAddresses = 0; + bool inv = false; + + void write(uint32_t* data, size_t len); + void write(uint32_t codeWord); + int16_t startReceiveCommon(); + bool addressMatched(uint32_t addr); + +#if !RADIOLIB_EXCLUDE_DIRECT_RECEIVE + uint32_t read(); +#endif + + uint8_t encodeBCD(char c); + char decodeBCD(uint8_t b); +}; + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/protocols/PhysicalLayer/PhysicalLayer.cpp b/software/firmware/source/libraries/RadioLib/src/protocols/PhysicalLayer/PhysicalLayer.cpp new file mode 100644 index 000000000..2872cca31 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/protocols/PhysicalLayer/PhysicalLayer.cpp @@ -0,0 +1,550 @@ +#include "PhysicalLayer.h" + +#include + +PhysicalLayer::PhysicalLayer(float step, size_t maxLen) { + this->freqStep = step; + this->maxPacketLength = maxLen; + #if !RADIOLIB_EXCLUDE_DIRECT_RECEIVE + this->bufferBitPos = 0; + this->bufferWritePos = 0; + #endif +} + +#if defined(RADIOLIB_BUILD_ARDUINO) +int16_t PhysicalLayer::transmit(__FlashStringHelper* fstr, uint8_t addr) { + // read flash string length + size_t len = 0; + PGM_P p = reinterpret_cast(fstr); + while(true) { + char c = RADIOLIB_NONVOLATILE_READ_BYTE(p++); + len++; + if(c == '\0') { + break; + } + } + + // dynamically allocate memory + #if RADIOLIB_STATIC_ONLY + char str[RADIOLIB_STATIC_ARRAY_SIZE]; + #else + char* str = new char[len]; + #endif + + // copy string from flash + p = reinterpret_cast(fstr); + for(size_t i = 0; i < len; i++) { + str[i] = RADIOLIB_NONVOLATILE_READ_BYTE(p + i); + } + + // transmit string + int16_t state = transmit(str, addr); + #if !RADIOLIB_STATIC_ONLY + delete[] str; + #endif + return(state); +} + +int16_t PhysicalLayer::transmit(String& str, uint8_t addr) { + return(transmit(str.c_str(), addr)); +} +#endif + +int16_t PhysicalLayer::transmit(const char* str, uint8_t addr) { + return(transmit((uint8_t*)str, strlen(str), addr)); +} + +int16_t PhysicalLayer::transmit(const uint8_t* data, size_t len, uint8_t addr) { + (void)data; + (void)len; + (void)addr; + return(RADIOLIB_ERR_UNSUPPORTED); +} + +#if defined(RADIOLIB_BUILD_ARDUINO) +int16_t PhysicalLayer::receive(String& str, size_t len) { + int16_t state = RADIOLIB_ERR_NONE; + + // user can override the length of data to read + size_t length = len; + + // build a temporary buffer + #if RADIOLIB_STATIC_ONLY + uint8_t data[RADIOLIB_STATIC_ARRAY_SIZE + 1]; + #else + uint8_t* data = NULL; + if(length == 0) { + data = new uint8_t[this->maxPacketLength + 1]; + } else { + data = new uint8_t[length + 1]; + } + RADIOLIB_ASSERT_PTR(data); + #endif + + // attempt packet reception + state = receive(data, length); + + // any of the following leads to at least some data being available + // let's leave the decision of whether to keep it or not up to the user + if((state == RADIOLIB_ERR_NONE) || (state == RADIOLIB_ERR_CRC_MISMATCH) || (state == RADIOLIB_ERR_LORA_HEADER_DAMAGED)) { + // read the number of actually received bytes (for unknown packets) + if(len == 0) { + length = getPacketLength(false); + } + + // add null terminator + data[length] = 0; + + // initialize Arduino String class + str = String((char*)data); + } + + // deallocate temporary buffer + #if !RADIOLIB_STATIC_ONLY + delete[] data; + #endif + + return(state); +} +#endif + +int16_t PhysicalLayer::receive(uint8_t* data, size_t len) { + (void)data; + (void)len; + return(RADIOLIB_ERR_UNSUPPORTED); +} + +int16_t PhysicalLayer::sleep() { + return(RADIOLIB_ERR_UNSUPPORTED); +} + +int16_t PhysicalLayer::standby() { + return(standby(RADIOLIB_STANDBY_DEFAULT)); +} + +int16_t PhysicalLayer::standby(uint8_t mode) { + (void)mode; + return(RADIOLIB_ERR_UNSUPPORTED); +} + +int16_t PhysicalLayer::startReceive() { + return(RADIOLIB_ERR_UNSUPPORTED); +} + +int16_t PhysicalLayer::startReceive(uint32_t timeout, RadioLibIrqFlags_t irqFlags, RadioLibIrqFlags_t irqMask, size_t len) { + (void)timeout; + (void)irqFlags; + (void)irqMask; + (void)len; + return(RADIOLIB_ERR_UNSUPPORTED); +} + +#if defined(RADIOLIB_BUILD_ARDUINO) +int16_t PhysicalLayer::startTransmit(String& str, uint8_t addr) { + return(startTransmit(str.c_str(), addr)); +} +#endif + +int16_t PhysicalLayer::startTransmit(const char* str, uint8_t addr) { + return(startTransmit((uint8_t*)str, strlen(str), addr)); +} + +int16_t PhysicalLayer::startTransmit(const uint8_t* data, size_t len, uint8_t addr) { + (void)data; + (void)len; + (void)addr; + return(RADIOLIB_ERR_UNSUPPORTED); +} + +int16_t PhysicalLayer::finishTransmit() { + return(RADIOLIB_ERR_UNSUPPORTED); +} + +#if defined(RADIOLIB_BUILD_ARDUINO) +int16_t PhysicalLayer::readData(String& str, size_t len) { + int16_t state = RADIOLIB_ERR_NONE; + + // read the number of actually received bytes + size_t length = getPacketLength(); + + if((len < length) && (len != 0)) { + // user requested less bytes than were received, this is allowed (but frowned upon) + // requests for more data than were received will only return the number of actually received bytes (unlike PhysicalLayer::receive()) + length = len; + } + + // build a temporary buffer + #if RADIOLIB_STATIC_ONLY + uint8_t data[RADIOLIB_STATIC_ARRAY_SIZE + 1]; + #else + uint8_t* data = new uint8_t[length + 1]; + RADIOLIB_ASSERT_PTR(data); + #endif + + // read the received data + state = readData(data, length); + + // any of the following leads to at least some data being available + // let's leave the decision of whether to keep it or not up to the user + if((state == RADIOLIB_ERR_NONE) || (state == RADIOLIB_ERR_CRC_MISMATCH) || (state == RADIOLIB_ERR_LORA_HEADER_DAMAGED)) { + // add null terminator + data[length] = 0; + + // initialize Arduino String class + str = String((char*)data); + } + + // deallocate temporary buffer + #if !RADIOLIB_STATIC_ONLY + delete[] data; + #endif + + return(state); +} +#endif + +int16_t PhysicalLayer::readData(uint8_t* data, size_t len) { + (void)data; + (void)len; + return(RADIOLIB_ERR_UNSUPPORTED); +} + +int16_t PhysicalLayer::transmitDirect(uint32_t frf) { + (void)frf; + return(RADIOLIB_ERR_UNSUPPORTED); +} + +int16_t PhysicalLayer::receiveDirect() { + return(RADIOLIB_ERR_UNSUPPORTED); +} + +int16_t PhysicalLayer::setFrequency(float freq) { + (void)freq; + return(RADIOLIB_ERR_UNSUPPORTED); +} + +int16_t PhysicalLayer::setBitRate(float br) { + (void)br; + return(RADIOLIB_ERR_UNSUPPORTED); +} + +int16_t PhysicalLayer::setFrequencyDeviation(float freqDev) { + (void)freqDev; + return(RADIOLIB_ERR_UNSUPPORTED); +} + +int16_t PhysicalLayer::setDataShaping(uint8_t sh) { + (void)sh; + return(RADIOLIB_ERR_UNSUPPORTED); +} + +int16_t PhysicalLayer::setEncoding(uint8_t encoding) { + (void)encoding; + return(RADIOLIB_ERR_UNSUPPORTED); +} + +int16_t PhysicalLayer::invertIQ(bool enable) { + (void)enable; + return(RADIOLIB_ERR_UNSUPPORTED); +} + +int16_t PhysicalLayer::setOutputPower(int8_t power) { + (void)power; + return(RADIOLIB_ERR_UNSUPPORTED); +} + +int16_t PhysicalLayer::checkOutputPower(int8_t power, int8_t* clipped) { + (void)power; + (void)clipped; + return(RADIOLIB_ERR_UNSUPPORTED); +} + +int16_t PhysicalLayer::setSyncWord(uint8_t* sync, size_t len) { + (void)sync; + (void)len; + return(RADIOLIB_ERR_UNSUPPORTED); +} + +int16_t PhysicalLayer::setPreambleLength(size_t len) { + (void)len; + return(RADIOLIB_ERR_UNSUPPORTED); +} + +int16_t PhysicalLayer::setDataRate(DataRate_t dr) { + (void)dr; + return(RADIOLIB_ERR_UNSUPPORTED); +} + +int16_t PhysicalLayer::checkDataRate(DataRate_t dr) { + (void)dr; + return(RADIOLIB_ERR_UNSUPPORTED); +} + +float PhysicalLayer::getFreqStep() const { + return(this->freqStep); +} + +size_t PhysicalLayer::getPacketLength(bool update) { + (void)update; + return(0); +} + +float PhysicalLayer::getRSSI() { + return(RADIOLIB_ERR_UNSUPPORTED); +} + +float PhysicalLayer::getSNR() { + return(RADIOLIB_ERR_UNSUPPORTED); +} + +RadioLibTime_t PhysicalLayer::getTimeOnAir(size_t len) { + (void)len; + return(0); +} + +RadioLibTime_t PhysicalLayer::calculateRxTimeout(RadioLibTime_t timeoutUs) { + (void)timeoutUs; + return(0); +} + +uint32_t PhysicalLayer::getIrqMapped(RadioLibIrqFlags_t irq) { + // iterate over all set bits and build the module-specific flags + uint32_t irqRaw = 0; + for(uint8_t i = 0; i < 8*(sizeof(RadioLibIrqFlags_t)); i++) { + if((irq & (uint32_t)(1UL << i)) && (this->irqMap[i] != RADIOLIB_IRQ_NOT_SUPPORTED)) { + irqRaw |= this->irqMap[i]; + } + } + + return(irqRaw); +} + +int16_t PhysicalLayer::checkIrq(RadioLibIrqType_t irq) { + if((irq > RADIOLIB_IRQ_TIMEOUT) || (this->irqMap[irq] == RADIOLIB_IRQ_NOT_SUPPORTED)) { + return(RADIOLIB_ERR_UNSUPPORTED); + } + + return(getIrqFlags() & this->irqMap[irq]); +} + +int16_t PhysicalLayer::setIrq(RadioLibIrqFlags_t irq) { + return(setIrqFlags(getIrqMapped(irq))); +} + +int16_t PhysicalLayer::clearIrq(RadioLibIrqFlags_t irq) { + return(clearIrqFlags(getIrqMapped(irq))); +} + +uint32_t PhysicalLayer::getIrqFlags() { + return(RADIOLIB_ERR_UNSUPPORTED); +} + +int16_t PhysicalLayer::setIrqFlags(uint32_t irq) { + (void)irq; + return(RADIOLIB_ERR_UNSUPPORTED); +} + +int16_t PhysicalLayer::clearIrqFlags(uint32_t irq) { + (void)irq; + return(RADIOLIB_ERR_UNSUPPORTED); +} + +int16_t PhysicalLayer::startChannelScan() { + return(RADIOLIB_ERR_UNSUPPORTED); +} + +int16_t PhysicalLayer::startChannelScan(const ChannelScanConfig_t &config) { + (void)config; + return(RADIOLIB_ERR_UNSUPPORTED); +} + +int16_t PhysicalLayer::getChannelScanResult() { + return(RADIOLIB_ERR_UNSUPPORTED); +} + +int16_t PhysicalLayer::scanChannel() { + return(RADIOLIB_ERR_UNSUPPORTED); +} + +int16_t PhysicalLayer::scanChannel(const ChannelScanConfig_t &config) { + (void)config; + return(RADIOLIB_ERR_UNSUPPORTED); +} + +int32_t PhysicalLayer::random(int32_t max) { + if(max == 0) { + return(0); + } + + // get random bytes from the radio + uint8_t randBuff[4]; + for(uint8_t i = 0; i < 4; i++) { + randBuff[i] = randomByte(); + } + + // create 32-bit TRNG number + int32_t randNum = ((int32_t)randBuff[0] << 24) | ((int32_t)randBuff[1] << 16) | ((int32_t)randBuff[2] << 8) | ((int32_t)randBuff[3]); + if(randNum < 0) { + randNum *= -1; + } + return(randNum % max); +} + +int32_t PhysicalLayer::random(int32_t min, int32_t max) { + if(min >= max) { + return(min); + } + + return(PhysicalLayer::random(max - min) + min); +} + +uint8_t PhysicalLayer::randomByte() { + return(0); +} + +int16_t PhysicalLayer::startDirect() { + // disable encodings + int16_t state = setEncoding(RADIOLIB_ENCODING_NRZ); + RADIOLIB_ASSERT(state); + + // disable shaping + state = setDataShaping(RADIOLIB_SHAPING_NONE); + RADIOLIB_ASSERT(state); + + // set frequency deviation to the lowest possible value + state = setFrequencyDeviation(-1); + return(state); +} + +#if !RADIOLIB_EXCLUDE_DIRECT_RECEIVE +int16_t PhysicalLayer::available() { + return(this->bufferWritePos); +} + +void PhysicalLayer::dropSync() { + if(this->directSyncWordLen > 0) { + this->gotSync = false; + this->syncBuffer = 0; + } +} + +uint8_t PhysicalLayer::read(bool drop) { + if(drop) { + dropSync(); + } + this->bufferWritePos--; + return(this->buffer[this->bufferReadPos++]); +} + +int16_t PhysicalLayer::setDirectSyncWord(uint32_t syncWord, uint8_t len) { + if(len > 32) { + return(RADIOLIB_ERR_INVALID_SYNC_WORD); + } + this->directSyncWordMask = 0xFFFFFFFF >> (32 - len); + this->directSyncWordLen = len; + this->directSyncWord = syncWord; + + // override sync word matching when length is set to 0 + if(this->directSyncWordLen == 0) { + this->gotSync = true; + } + + return(RADIOLIB_ERR_NONE); +} + +void PhysicalLayer::updateDirectBuffer(uint8_t bit) { + // check sync word + if(!this->gotSync) { + this->syncBuffer <<= 1; + this->syncBuffer |= bit; + + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("S\t%lu", (long unsigned int)this->syncBuffer); + + if((this->syncBuffer & this->directSyncWordMask) == this->directSyncWord) { + this->gotSync = true; + this->bufferWritePos = 0; + this->bufferReadPos = 0; + this->bufferBitPos = 0; + } + + } else { + // save the bit + if(bit) { + this->buffer[this->bufferWritePos] |= 0x01 << this->bufferBitPos; + } else { + this->buffer[this->bufferWritePos] &= ~(0x01 << this->bufferBitPos); + } + this->bufferBitPos++; + + // check complete byte + if(this->bufferBitPos == 8) { + this->buffer[this->bufferWritePos] = rlb_reflect(this->buffer[this->bufferWritePos], 8); + RADIOLIB_DEBUG_PROTOCOL_PRINTLN("R\t%X", this->buffer[this->bufferWritePos]); + + this->bufferWritePos++; + this->bufferBitPos = 0; + } + } +} + +void PhysicalLayer::setDirectAction(void (*func)(void)) { + (void)func; +} + +void PhysicalLayer::readBit(uint32_t pin) { + (void)pin; +} + +#endif + +int16_t PhysicalLayer::setDIOMapping(uint32_t pin, uint32_t value) { + (void)pin; + (void)value; + return(RADIOLIB_ERR_UNSUPPORTED); +} + +void PhysicalLayer::setPacketReceivedAction(void (*func)(void)) { + (void)func; +} + +void PhysicalLayer::clearPacketReceivedAction() { + +} + +void PhysicalLayer::setPacketSentAction(void (*func)(void)) { + (void)func; +} + +void PhysicalLayer::clearPacketSentAction() { + +} + +void PhysicalLayer::setChannelScanAction(void (*func)(void)) { + (void)func; +} + +void PhysicalLayer::clearChannelScanAction() { + +} + +int16_t PhysicalLayer::setModem(ModemType_t modem) { + (void)modem; + return(RADIOLIB_ERR_UNSUPPORTED); +} + +int16_t PhysicalLayer::getModem(ModemType_t* modem) { + (void)modem; + return(RADIOLIB_ERR_UNSUPPORTED); +} + +#if RADIOLIB_INTERRUPT_TIMING +void PhysicalLayer::setInterruptSetup(void (*func)(uint32_t)) { + Module* mod = getMod(); + mod->TimerSetupCb = func; +} + +void PhysicalLayer::setTimerFlag() { + Module* mod = getMod(); + mod->TimerFlag = true; +} +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/protocols/PhysicalLayer/PhysicalLayer.h b/software/firmware/source/libraries/RadioLib/src/protocols/PhysicalLayer/PhysicalLayer.h new file mode 100644 index 000000000..a8f66ce1a --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/protocols/PhysicalLayer/PhysicalLayer.h @@ -0,0 +1,733 @@ +#if !defined(_RADIOLIB_PHYSICAL_LAYER_H) +#define _RADIOLIB_PHYSICAL_LAYER_H + +#include "../../TypeDef.h" +#include "../../Module.h" + +// common IRQ values - the IRQ flags in RadioLibIrqFlags_t arguments are offset by this value +enum RadioLibIrqType_t { + RADIOLIB_IRQ_TX_DONE = 0x00, + RADIOLIB_IRQ_RX_DONE = 0x01, + RADIOLIB_IRQ_PREAMBLE_DETECTED = 0x02, + RADIOLIB_IRQ_SYNC_WORD_VALID = 0x03, + RADIOLIB_IRQ_HEADER_VALID = 0x04, + RADIOLIB_IRQ_HEADER_ERR = 0x05, + RADIOLIB_IRQ_CRC_ERR = 0x06, + RADIOLIB_IRQ_CAD_DONE = 0x07, + RADIOLIB_IRQ_CAD_DETECTED = 0x08, + RADIOLIB_IRQ_TIMEOUT = 0x09, + RADIOLIB_IRQ_NOT_SUPPORTED = 0x1F, // this must be the last value, intentionally set to 31 +}; + +// some commonly used default values - defined here to ensure all modules have the same default behavior +#define RADIOLIB_IRQ_RX_DEFAULT_FLAGS ((1UL << RADIOLIB_IRQ_RX_DONE) | (1UL << RADIOLIB_IRQ_TIMEOUT) | (1UL << RADIOLIB_IRQ_CRC_ERR) | (1UL << RADIOLIB_IRQ_HEADER_VALID) | (1UL << RADIOLIB_IRQ_HEADER_ERR)) +#define RADIOLIB_IRQ_RX_DEFAULT_MASK ((1UL << RADIOLIB_IRQ_RX_DONE)) +#define RADIOLIB_IRQ_CAD_DEFAULT_FLAGS ((1UL << RADIOLIB_IRQ_CAD_DETECTED) | (1UL << RADIOLIB_IRQ_CAD_DONE)) +#define RADIOLIB_IRQ_CAD_DEFAULT_MASK ((1UL << RADIOLIB_IRQ_CAD_DETECTED) | (1UL << RADIOLIB_IRQ_CAD_DONE)) + +/*! + \struct LoRaRate_t + \brief Data rate structure interpretation in case LoRa is used +*/ +struct LoRaRate_t { + /*! \brief LoRa spreading factor */ + uint8_t spreadingFactor; + + /*! \brief LoRa bandwidth in kHz */ + float bandwidth; + + /*! \brief LoRa coding rate */ + uint8_t codingRate; +}; + +/*! + \struct FSKRate_t + \brief Data rate structure interpretation in case FSK is used +*/ +struct FSKRate_t { + /*! \brief FSK bit rate in kbps */ + float bitRate; + + /*! \brief FSK frequency deviation in kHz */ + float freqDev; +}; + +/*! + \struct LrFhssRate_t + \brief Data rate structure interpretation in case LR-FHSS is used +*/ +struct LrFhssRate_t { + /*! \brief Bandwidth */ + uint8_t bw; + + /*! \brief Coding rate */ + uint8_t cr; + + /*! \brief Grid spacing */ + bool narrowGrid; +}; + +/*! + \union DataRate_t + \brief Common data rate structure +*/ +union DataRate_t { + /*! \brief Interpretation for LoRa modems */ + LoRaRate_t lora; + + /*! \brief Interpretation for FSK modems */ + FSKRate_t fsk; + + /*! \brief Interpretation for LR-FHSS modems */ + LrFhssRate_t lrFhss; +}; + +/*! + \struct CADScanConfig_t + \brief Channel scan configuration interpretation in case LoRa CAD is used +*/ +struct CADScanConfig_t { + /*! \brief Number of symbols to consider signal present */ + uint8_t symNum; + + /*! \brief Number of peak detection symbols */ + uint8_t detPeak; + + /*! \brief Number of minimum detection symbols */ + uint8_t detMin; + + /*! \brief Exit mode after signal detection is complete - module-specific value */ + uint8_t exitMode; + + /*! \brief Timeout in microseconds */ + RadioLibTime_t timeout; + + /*! \brief Optional IRQ flags to set, bits offset by the value of RADIOLIB_IRQ_ */ + RadioLibIrqFlags_t irqFlags; + + /*! \brief Optional IRQ mask to set, bits offset by the value of RADIOLIB_IRQ_ */ + RadioLibIrqFlags_t irqMask; +}; + +/*! + \struct RSSIScanConfig_t + \brief Channel scan configuration interpretation in case RSSI threshold is used +*/ +struct RSSIScanConfig_t { + /*! \brief RSSI limit in dBm */ + float limit; +}; + +/*! + \union ChannelScanConfig_t + \brief Common channel scan configuration structure +*/ +union ChannelScanConfig_t { + /*! \brief Interpretation for modems that use CAD (usually LoRa modems)*/ + CADScanConfig_t cad; + + /*! \brief Interpretation for modems that use RSSI threshold*/ + RSSIScanConfig_t rssi; +}; + +/*! + \enum ModemType_t + \brief Type of modem, used by setModem. +*/ +enum ModemType_t { + RADIOLIB_MODEM_FSK = 0, + RADIOLIB_MODEM_LORA, + RADIOLIB_MODEM_LRFHSS, +}; + +/*! + \class PhysicalLayer + + \brief Provides common interface for protocols that run on %LoRa/FSK modules, such as RTTY or LoRaWAN. + Also extracts some common module-independent methods. Using this interface class allows to use the protocols + on various modules without much code duplicity. Because this class is used mainly as interface, + all of its virtual members must be implemented in the module class. +*/ +class PhysicalLayer { + public: + + // constructor + + /*! + \brief Default constructor. + \param step Frequency step of the synthesizer in Hz. + \param maxLen Maximum length of packet that can be received by the module. + */ + PhysicalLayer(float step, size_t maxLen); + + // basic methods + + #if defined(RADIOLIB_BUILD_ARDUINO) + /*! + \brief Arduino Flash String transmit method. + \param str Pointer to Arduino Flash String that will be transmitted. + \param addr Node address to transmit the packet to. Only used in FSK mode. + \returns \ref status_codes + */ + int16_t transmit(__FlashStringHelper* fstr, uint8_t addr = 0); + + /*! + \brief Arduino String transmit method. + \param str Address of Arduino string that will be transmitted. + \param addr Node address to transmit the packet to. Only used in FSK mode. + \returns \ref status_codes + */ + int16_t transmit(String& str, uint8_t addr = 0); + #endif + + /*! + \brief C-string transmit method. + \param str C-string that will be transmitted. + \param addr Node address to transmit the packet to. Only used in FSK mode. + \returns \ref status_codes + */ + int16_t transmit(const char* str, uint8_t addr = 0); + + /*! + \brief Binary transmit method. Must be implemented in module class. + \param data Binary data that will be transmitted. + \param len Length of binary data to transmit (in bytes). + \param addr Node address to transmit the packet to. Only used in FSK mode. + \returns \ref status_codes + */ + virtual int16_t transmit(const uint8_t* data, size_t len, uint8_t addr = 0); + + #if defined(RADIOLIB_BUILD_ARDUINO) + /*! + \brief Arduino String receive method. + \param str Address of Arduino String to save the received data. + \param len Expected number of characters in the message. Leave as 0 if expecting a unknown size packet + \returns \ref status_codes + */ + int16_t receive(String& str, size_t len = 0); + #endif + + /*! + \brief Sets module to sleep. + \returns \ref status_codes + */ + virtual int16_t sleep(); + + /*! + \brief Sets module to standby. + \returns \ref status_codes + */ + virtual int16_t standby(); + + /*! + \brief Sets module to a specific standby mode. + \returns \ref status_codes + */ + virtual int16_t standby(uint8_t mode); + + /*! + \brief Sets module to received mode using its default configuration. + \returns \ref status_codes + */ + virtual int16_t startReceive(); + + /*! + \brief Interrupt-driven receive method. A DIO pin will be activated when full packet is received. + Must be implemented in module class. + \param timeout Raw timeout value. Some modules use this argument to specify operation mode + (single vs. continuous receive). + \param irqFlags Sets the IRQ flags. + \param irqMask Sets the mask of IRQ flags that will trigger the radio interrupt pin. + \param len Packet length, needed for some modules under special circumstances (e.g. LoRa implicit header mode). + \returns \ref status_codes + */ + virtual int16_t startReceive(uint32_t timeout, RadioLibIrqFlags_t irqFlags, RadioLibIrqFlags_t irqMask, size_t len); + + /*! + \brief Binary receive method. Must be implemented in module class. + \param data Pointer to array to save the received binary data. + \param len Packet length, needed for some modules under special circumstances (e.g. LoRa implicit header mode). + \returns \ref status_codes + */ + virtual int16_t receive(uint8_t* data, size_t len); + + #if defined(RADIOLIB_BUILD_ARDUINO) + /*! + \brief Interrupt-driven Arduino String transmit method. Unlike the standard transmit method, this one is non-blocking. + Interrupt pin will be activated when transmission finishes. + \param str Address of Arduino String that will be transmitted. + \param addr Node address to transmit the packet to. Only used in FSK mode. + \returns \ref status_codes + */ + int16_t startTransmit(String& str, uint8_t addr = 0); + #endif + + /*! + \brief Interrupt-driven Arduino String transmit method. Unlike the standard transmit method, this one is non-blocking. + Interrupt pin will be activated when transmission finishes. + \param str C-string that will be transmitted. + \param addr Node address to transmit the packet to. Only used in FSK mode. + \returns \ref status_codes + */ + int16_t startTransmit(const char* str, uint8_t addr = 0); + + /*! + \brief Interrupt-driven binary transmit method. + \param data Binary data that will be transmitted. + \param len Length of binary data to transmit (in bytes). + \param addr Node address to transmit the packet to. Only used in FSK mode. + \returns \ref status_codes + */ + virtual int16_t startTransmit(const uint8_t* data, size_t len, uint8_t addr = 0); + + /*! + \brief Clean up after transmission is done. + \returns \ref status_codes + */ + virtual int16_t finishTransmit(); + + #if defined(RADIOLIB_BUILD_ARDUINO) + /*! + \brief Reads data that was received after calling startReceive method. + \param str Address of Arduino String to save the received data. + \param len Expected number of characters in the message. When set to 0, the packet length will be retrieved + automatically. When more bytes than received are requested, only the number of bytes requested will be returned. + \returns \ref status_codes + */ + int16_t readData(String& str, size_t len = 0); + #endif + + /*! + \brief Reads data that was received after calling startReceive method. + \param data Pointer to array to save the received binary data. + \param len Number of bytes that will be read. When set to 0, the packet length will be retrieved automatically. + When more bytes than received are requested, only the number of bytes requested will be returned. + \returns \ref status_codes + */ + virtual int16_t readData(uint8_t* data, size_t len); + + /*! + \brief Enables direct transmission mode on pins DIO1 (clock) and DIO2 (data). Must be implemented in module class. + While in direct mode, the module will not be able to transmit or receive packets. Can only be activated in FSK mode. + \param frf 24-bit raw frequency value to start transmitting at. Required for quick frequency shifts in RTTY. + \returns \ref status_codes + */ + virtual int16_t transmitDirect(uint32_t frf = 0); + + /*! + \brief Enables direct reception mode on pins DIO1 (clock) and DIO2 (data). Must be implemented in module class. + While in direct mode, the module will not be able to transmit or receive packets. Can only be activated in FSK mode. + \returns \ref status_codes + */ + virtual int16_t receiveDirect(); + + // configuration methods + + /*! + \brief Sets carrier frequency. Must be implemented in module class. + \param freq Carrier frequency to be set in MHz. + \returns \ref status_codes + */ + virtual int16_t setFrequency(float freq); + + /*! + \brief Sets FSK bit rate. Only available in FSK mode. Must be implemented in module class. + \param br Bit rate to be set (in kbps). + \returns \ref status_codes + */ + virtual int16_t setBitRate(float br); + + /*! + \brief Sets FSK frequency deviation from carrier frequency. Only available in FSK mode. + Must be implemented in module class. + \param freqDev Frequency deviation to be set (in kHz). + \returns \ref status_codes + */ + virtual int16_t setFrequencyDeviation(float freqDev); + + /*! + \brief Sets GFSK data shaping. Only available in FSK mode. Must be implemented in module class. + \param sh Shaping to be set. See \ref config_shaping for possible values. + \returns \ref status_codes + */ + virtual int16_t setDataShaping(uint8_t sh); + + /*! + \brief Sets FSK data encoding. Only available in FSK mode. Must be implemented in module class. + \param encoding Encoding to be used. See \ref config_encoding for possible values. + \returns \ref status_codes + */ + virtual int16_t setEncoding(uint8_t encoding); + + /*! + \brief Set IQ inversion. Must be implemented in module class if the module supports it. + \param enable True to use inverted IQ, false for non-inverted. + \returns \ref status_codes + */ + virtual int16_t invertIQ(bool enable); + + /*! + \brief Set output power. Must be implemented in module class if the module supports it. + \param power Output power in dBm. The allowed range depends on the module used. + \returns \ref status_codes + */ + virtual int16_t setOutputPower(int8_t power); + + /*! + \brief Check if output power is configurable. Must be implemented in module class if the module supports it. + \param power Output power in dBm. The allowed range depends on the module used. + \param clipped Clipped output power value to what is possible within the module's range. + \returns \ref status_codes + */ + virtual int16_t checkOutputPower(int8_t power, int8_t* clipped); + + /*! + \brief Set sync word. Must be implemented in module class if the module supports it. + \param sync Pointer to the sync word. + \param len Sync word length in bytes. Maximum length depends on the module used. + \returns \ref status_codes + */ + virtual int16_t setSyncWord(uint8_t* sync, size_t len); + + /*! + \brief Set preamble length. Must be implemented in module class if the module supports it. + \param len Preamble length in bytes. Maximum length depends on the module used. + \returns \ref status_codes + */ + virtual int16_t setPreambleLength(size_t len); + + /*! + \brief Set data. Must be implemented in module class if the module supports it. + \param dr Data rate struct. Interpretation depends on currently active modem (FSK or LoRa). + \returns \ref status_codes + */ + virtual int16_t setDataRate(DataRate_t dr); + + /*! + \brief Check the data rate can be configured by this module. Must be implemented in module class if the module supports it. + \param dr Data rate struct. Interpretation depends on currently active modem (FSK or LoRa). + \returns \ref status_codes + */ + virtual int16_t checkDataRate(DataRate_t dr); + + /*! + \brief Gets the module frequency step size that was set in constructor. + \returns Synthesizer frequency step size in Hz. + */ + float getFreqStep() const; + + /*! + \brief Query modem for the packet length of received payload. Must be implemented in module class. + \param update Update received packet length. Will return cached value when set to false. + \returns Length of last received packet in bytes. + */ + virtual size_t getPacketLength(bool update = true); + + /*! + \brief Gets RSSI (Recorded Signal Strength Indicator) of the last received packet. + \returns RSSI of the last received packet in dBm. + */ + virtual float getRSSI(); + + /*! + \brief Gets SNR (Signal to Noise Ratio) of the last received packet. Only available for LoRa modem. + \returns SNR of the last received packet in dB. + */ + virtual float getSNR(); + + /*! + \brief Get expected time-on-air for a given size of payload + \param len Payload length in bytes. + \returns Expected time-on-air in microseconds. + */ + virtual RadioLibTime_t getTimeOnAir(size_t len); + + /*! + \brief Calculate the timeout value for this specific module / series + (in number of symbols or units of time). + \param timeoutUs Timeout in microseconds to listen for. + \returns Timeout value in a unit that is specific for the used module. + */ + virtual RadioLibTime_t calculateRxTimeout(RadioLibTime_t timeoutUs); + + /*! + \brief Convert from radio-agnostic IRQ flags to radio-specific flags. + \param irq Radio-agnostic IRQ flags. + \returns Flags for a specific radio module. + */ + uint32_t getIrqMapped(RadioLibIrqFlags_t irq); + + /*! + \brief Check whether a specific IRQ bit is set (e.g. RxTimeout, CadDone). + \param irq IRQ type to check, one of RADIOLIB_IRQ_*. + \returns 1 when requested IRQ is set, 0 when it is not or RADIOLIB_ERR_UNSUPPORTED if the IRQ is not supported. + */ + int16_t checkIrq(RadioLibIrqType_t irq); + + /*! + \brief Set interrupt on specific IRQ bit(s) (e.g. RxTimeout, CadDone). + Keep in mind that not all radio modules support all RADIOLIB_IRQ_ flags! + \param irq Flags to set, multiple bits may be enabled. IRQ to enable corresponds to the bit index (RadioLibIrq_t). + For example, if bit 0 is enabled, the module will enable its RADIOLIB_IRQ_TX_DONE (if it is supported). + \returns \ref status_codes + */ + int16_t setIrq(RadioLibIrqFlags_t irq); + + /*! + \brief Clear interrupt on a specific IRQ bit (e.g. RxTimeout, CadDone). + Keep in mind that not all radio modules support all RADIOLIB_IRQ_ flags! + \param irq Flags to set, multiple bits may be enabled. IRQ to enable corresponds to the bit index (RadioLibIrq_t). + For example, if bit 0 is enabled, the module will enable its RADIOLIB_IRQ_TX_DONE (if it is supported). + \returns \ref status_codes + */ + int16_t clearIrq(RadioLibIrqFlags_t irq); + + /*! + \brief Read currently active IRQ flags. + Must be implemented in module class. + \returns IRQ flags. + */ + virtual uint32_t getIrqFlags(); + + /*! + \brief Set interrupt on DIO1 to be sent on a specific IRQ bit (e.g. RxTimeout, CadDone). + Must be implemented in module class. + \param irq Module-specific IRQ flags. + \returns \ref status_codes + */ + virtual int16_t setIrqFlags(uint32_t irq); + + /*! + \brief Clear interrupt on a specific IRQ bit (e.g. RxTimeout, CadDone). + Must be implemented in module class. + \param irq Module-specific IRQ flags. + \returns \ref status_codes + */ + virtual int16_t clearIrqFlags(uint32_t irq); + + /*! + \brief Interrupt-driven channel activity detection method. Interrupt will be activated + when packet is detected. Must be implemented in module class. + \returns \ref status_codes + */ + virtual int16_t startChannelScan(); + + /*! + \brief Interrupt-driven channel activity detection method. interrupt will be activated + when packet is detected. Must be implemented in module class. + \param config Scan configuration structure. Interpretation depends on currently active modem. + \returns \ref status_codes + */ + virtual int16_t startChannelScan(const ChannelScanConfig_t &config); + + /*! + \brief Read the channel scan result + \returns \ref status_codes + */ + virtual int16_t getChannelScanResult(); + + /*! + \brief Check whether the current communication channel is free or occupied. Performs CAD for LoRa modules, + or RSSI measurement for FSK modules. + \returns RADIOLIB_CHANNEL_FREE when channel is free, + RADIOLIB_PREAMBLE_DETECTEDwhen occupied or other \ref status_codes. + */ + virtual int16_t scanChannel(); + + /*! + \brief Check whether the current communication channel is free or occupied. Performs CAD for LoRa modules, + or RSSI measurement for FSK modules. + \param config Scan configuration structure. Interpretation depends on currently active modem. + \returns RADIOLIB_CHANNEL_FREE when channel is free, + RADIOLIB_PREAMBLE_DETECTEDwhen occupied or other \ref status_codes. + */ + virtual int16_t scanChannel(const ChannelScanConfig_t &config); + + /*! + \brief Get truly random number in range 0 - max. + \param max The maximum value of the random number (non-inclusive). + \returns Random number. + */ + int32_t random(int32_t max); + + /*! + \brief Get truly random number in range min - max. + \param min The minimum value of the random number (inclusive). + \param max The maximum value of the random number (non-inclusive). + \returns Random number. + */ + int32_t random(int32_t min, int32_t max); + + /*! + \brief Get one truly random byte from RSSI noise. Must be implemented in module class. + \returns TRNG byte. + */ + virtual uint8_t randomByte(); + + /*! + \brief Configure module parameters for direct modes. Must be called prior to "ham" modes like RTTY or AX.25. + Only available in FSK mode. + \returns \ref status_codes + */ + int16_t startDirect(); + + #if !RADIOLIB_EXCLUDE_DIRECT_RECEIVE + /*! + \brief Set sync word to be used to determine start of packet in direct reception mode. + \param syncWord Sync word bits. + \param len Sync word length in bits. Set to zero to disable sync word matching. + \returns \ref status_codes + */ + int16_t setDirectSyncWord(uint32_t syncWord, uint8_t len); + + /*! + \brief Set interrupt service routine function to call when data bit is received in direct mode. + Must be implemented in module class. + \param func Pointer to interrupt service routine. + */ + virtual void setDirectAction(void (*func)(void)); + + /*! + \brief Function to read and process data bit in direct reception mode. Must be implemented in module class. + \param pin Pin on which to read. + */ + virtual void readBit(uint32_t pin); + + /*! + \brief Get the number of direct mode bytes currently available in buffer. + \returns Number of available bytes. + */ + int16_t available(); + + /*! + \brief Forcefully drop synchronization. + */ + void dropSync(); + + /*! + \brief Get data from direct mode buffer. + \param drop Drop synchronization on read - next reading will require waiting for the sync word again. + Defaults to true. + \returns Byte from direct mode buffer. + */ + uint8_t read(bool drop = true); + #endif + + /*! + \brief Configure DIO pin mapping to get a given signal on a DIO pin (if available). + \param pin Pin number onto which a signal is to be placed. + \param value The value that indicates which function to place on that pin. See chip datasheet for details. + \returns \ref status_codes + */ + virtual int16_t setDIOMapping(uint32_t pin, uint32_t value); + + /*! + \brief Sets interrupt service routine to call when a packet is received. + \param func ISR to call. + */ + virtual void setPacketReceivedAction(void (*func)(void)); + + /*! + \brief Clears interrupt service routine to call when a packet is received. + */ + virtual void clearPacketReceivedAction(); + + /*! + \brief Sets interrupt service routine to call when a packet is sent. + \param func ISR to call. + */ + virtual void setPacketSentAction(void (*func)(void)); + + /*! + \brief Clears interrupt service routine to call when a packet is sent. + */ + virtual void clearPacketSentAction(); + + /*! + \brief Sets interrupt service routine to call when a channel scan is finished. + \param func ISR to call. + */ + virtual void setChannelScanAction(void (*func)(void)); + + /*! + \brief Clears interrupt service routine to call when a channel scan is finished. + */ + virtual void clearChannelScanAction(); + + /*! + \brief Set modem for the radio to use. Will perform full reset and reconfigure the radio + using its default parameters. + \param modem Modem type to set. Not all modems are implemented by all radio modules! + \returns \ref status_codes + */ + virtual int16_t setModem(ModemType_t modem); + + /*! + \brief Get modem currently in use by the radio. + \param modem Pointer to a variable to save the retrieved configuration into. + \returns \ref status_codes + */ + virtual int16_t getModem(ModemType_t* modem); + + #if RADIOLIB_INTERRUPT_TIMING + + /*! + \brief Set function to be called to set up the timing interrupt. + For details, see https://github.com/jgromes/RadioLib/wiki/Interrupt-Based-Timing + \param func Setup function to be called, with one argument (pulse length in microseconds). + */ + void setInterruptSetup(void (*func)(uint32_t)); + + /*! + \brief Set timing interrupt flag. + For details, see https://github.com/jgromes/RadioLib/wiki/Interrupt-Based-Timing + */ + void setTimerFlag(); + + #endif + +#if !RADIOLIB_GODMODE + protected: +#endif + uint32_t irqMap[10] = { 0 }; + +#if !RADIOLIB_EXCLUDE_DIRECT_RECEIVE + void updateDirectBuffer(uint8_t bit); +#endif + +#if !RADIOLIB_GODMODE + private: +#endif + float freqStep; + size_t maxPacketLength; + + #if !RADIOLIB_EXCLUDE_DIRECT_RECEIVE + uint8_t bufferBitPos = 0; + uint8_t bufferWritePos = 0; + uint8_t bufferReadPos = 0; + uint8_t buffer[RADIOLIB_STATIC_ARRAY_SIZE] = { 0 }; + uint32_t syncBuffer = 0; + uint32_t directSyncWord = 0; + uint8_t directSyncWordLen = 0; + uint32_t directSyncWordMask = 0; + bool gotSync = false; + #endif + + virtual Module* getMod() = 0; + + // allow specific classes access the private getMod method + friend class AFSKClient; + friend class RTTYClient; + friend class MorseClient; + friend class HellClient; + friend class SSTVClient; + friend class AX25Client; + friend class FSK4Client; + friend class PagerClient; + friend class BellClient; + friend class FT8Client; + friend class LoRaWANNode; + friend class M17Client; +}; + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/protocols/Print/ITA2String.cpp b/software/firmware/source/libraries/RadioLib/src/protocols/Print/ITA2String.cpp new file mode 100644 index 000000000..368edebf3 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/protocols/Print/ITA2String.cpp @@ -0,0 +1,137 @@ +#include "ITA2String.h" + +#include + +ITA2String::ITA2String(char c) { + asciiLen = 1; + #if !RADIOLIB_STATIC_ONLY + strAscii = new char[1]; + #endif + strAscii[0] = c; + ita2Len = 0; +} + +ITA2String::ITA2String(const char* str) { + asciiLen = strlen(str); + #if !RADIOLIB_STATIC_ONLY + strAscii = new char[asciiLen + 1]; + #endif + strcpy(strAscii, str); + ita2Len = 0; +} + +ITA2String::ITA2String(const ITA2String& ita2) { + this->asciiLen = ita2.asciiLen; + this->ita2Len = ita2.ita2Len; + #if !RADIOLIB_STATIC_ONLY + this->strAscii = new char[asciiLen + 1]; + #endif + strcpy(this->strAscii, ita2.strAscii); +} + +ITA2String& ITA2String::operator=(const ITA2String& ita2) { + if(&ita2 != this) { + this->asciiLen = ita2.asciiLen; + this->ita2Len = ita2.ita2Len; + #if !RADIOLIB_STATIC_ONLY + this->strAscii = new char[asciiLen + 1]; + #endif + strcpy(this->strAscii, ita2.strAscii); + } + return(*this); +} + +ITA2String::~ITA2String() { + #if !RADIOLIB_STATIC_ONLY + delete[] strAscii; + #endif +} + +size_t ITA2String::length() { + // length returned by this method is different than the length of ASCII-encoded strAscii + // ITA2-encoded string length varies based on how many number and characters the string contains + + if(ita2Len == 0) { + // ITA2 length wasn't calculated yet, call byteArr() to calculate it + byteArr(); + } + + return(ita2Len); +} + +uint8_t* ITA2String::byteArr() { + // create temporary array 2x the string length (figures may be 3 bytes) + #if RADIOLIB_STATIC_ONLY + uint8_t temp[RADIOLIB_STATIC_ARRAY_SIZE*2 + 1]; + #else + uint8_t* temp = new uint8_t[asciiLen*2 + 1]; + #endif + + // ensure the minimum possible array size is always initialized + temp[0] = 0; + + size_t arrayLen = 0; + bool flagFigure = false; + for(size_t i = 0; i < asciiLen; i++) { + uint16_t code = getBits(strAscii[i]); + uint8_t shift = (code >> 5) & 0b11111; + uint8_t character = code & 0b11111; + // check if the code is letter or figure + if(shift == RADIOLIB_ITA2_FIGS) { + // check if this is the first figure in sequence + if(!flagFigure) { + flagFigure = true; + temp[arrayLen++] = RADIOLIB_ITA2_FIGS; + } + + // add the character code + temp[arrayLen++] = character & 0b11111; + + // check the following character (skip for message end) + if(i < (asciiLen - 1)) { + uint16_t nextCode = getBits(strAscii[i+1]); + uint8_t nextShift = (nextCode >> 5) & 0b11111; + if(nextShift == RADIOLIB_ITA2_LTRS) { + // next character is a letter, terminate figure shift + temp[arrayLen++] = RADIOLIB_ITA2_LTRS; + flagFigure = false; + } + } else { + // reached the end of the message, terminate figure shift + temp[arrayLen++] = RADIOLIB_ITA2_LTRS; + flagFigure = false; + } + } else { + temp[arrayLen++] = character & 0b11111; + } + } + + // save ITA2 string length + ita2Len = arrayLen; + + uint8_t* arr = new uint8_t[arrayLen]; + memcpy(arr, temp, arrayLen); + #if !RADIOLIB_STATIC_ONLY + delete[] temp; + #endif + + return(arr); +} + +uint16_t ITA2String::getBits(char c) { + // search ITA2 table + uint16_t code = 0x0000; + for(uint8_t i = 0; i < RADIOLIB_ITA2_LENGTH; i++) { + if(RADIOLIB_NONVOLATILE_READ_BYTE(&ITA2Table[i][0]) == c) { + // character is in letter shift + code = (RADIOLIB_ITA2_LTRS << 5) | i; + break; + } else if(RADIOLIB_NONVOLATILE_READ_BYTE(&ITA2Table[i][1]) == c) { + // character is in figures shift + code = (RADIOLIB_ITA2_FIGS << 5) | i; + break; + } + } + + return(code); +} diff --git a/software/firmware/source/libraries/RadioLib/src/protocols/Print/ITA2String.h b/software/firmware/source/libraries/RadioLib/src/protocols/Print/ITA2String.h new file mode 100644 index 000000000..70e7b4b0f --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/protocols/Print/ITA2String.h @@ -0,0 +1,82 @@ +#if !defined(_RADIOLIB_ITA2_STRING_H) +#define _RADIOLIB_ITA2_STRING_H + +#include "../../TypeDef.h" + +#define RADIOLIB_ITA2_FIGS 0x1B +#define RADIOLIB_ITA2_LTRS 0x1F +#define RADIOLIB_ITA2_LENGTH 32 + +// ITA2 character table: - position in array corresponds to 5-bit ITA2 code +// - characters to the left are in letters shift, characters to the right in figures shift +// - characters marked 0x7F do not have ASCII equivalent +static const char ITA2Table[RADIOLIB_ITA2_LENGTH][2] RADIOLIB_NONVOLATILE = { + {'\0', '\0'}, {'E', '3'}, {'\n', '\n'}, {'A', '-'}, {' ', ' '}, {'S', '\''}, {'I', '8'}, {'U', '7'}, + {'\r', '\r'}, {'D', 0x05}, {'R', '4'}, {'J', '\a'}, {'N', ','}, {'F', '!'}, {'C', ':'}, {'K', '('}, + {'T', '5'}, {'Z', '+'}, {'L', ')'}, {'W', '2'}, {'H', 0x7F}, {'Y', '6'}, {'P', '0'}, {'Q', '1'}, + {'O', '9'}, {'B', '?'}, {'G', '&'}, {0x7F, 0x7F}, {'M', '.'}, {'X', '/'}, {'V', ';'}, {0x7F, 0x7F} +}; + +/*! + \class ITA2String + \brief ITA2-encoded string. +*/ +class ITA2String { + public: + /*! + \brief Default single-character constructor. + \param c ASCII-encoded character to encode as ITA2. + */ + explicit ITA2String(char c); + + /*! + \brief Default string constructor. + \param str ASCII-encoded string to encode as ITA2. + */ + explicit ITA2String(const char* str); + + /*! + \brief Copy constructor. + \param ita2 ITA2String instance to copy. + */ + ITA2String(const ITA2String& ita2); + + /*! + \brief Overload for assignment operator. + \param ita2 rvalue ITA2String. + */ + ITA2String& operator=(const ITA2String& ita2); + + /*! + \brief Default destructor. + */ + ~ITA2String(); + + /*! + \brief Gets the length of the ITA2 string. This number is not the same as the length of ASCII-encoded string! + \returns Length of ITA2-encoded string. + */ + size_t length(); + + /*! + \brief Gets the ITA2 representation of the ASCII string set in constructor. + \returns Pointer to dynamically allocated array, which contains ITA2-encoded bytes. + It is the caller's responsibility to deallocate this memory! + */ + uint8_t* byteArr(); + +#if !RADIOLIB_GODMODE + private: +#endif + #if RADIOLIB_STATIC_ONLY + char strAscii[RADIOLIB_STATIC_ARRAY_SIZE]; + #else + char* strAscii; + #endif + size_t asciiLen; + size_t ita2Len; + + static uint16_t getBits(char c); +}; + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/protocols/Print/Print.cpp b/software/firmware/source/libraries/RadioLib/src/protocols/Print/Print.cpp new file mode 100644 index 000000000..c7abe6957 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/protocols/Print/Print.cpp @@ -0,0 +1,312 @@ +#include "Print.h" + +#include +#include + +size_t RadioLibPrint::print(ITA2String& ita2) { + uint8_t enc = this->encoding; + this->encoding = RADIOLIB_ITA2; + uint8_t* arr = ita2.byteArr(); + size_t n = write(arr, ita2.length()); + delete[] arr; + this->encoding = enc; + return(n); +} + +size_t RadioLibPrint::println(ITA2String& ita2) { + size_t n = RadioLibPrint::print(ita2); + n += RadioLibPrint::println(); + return(n); +} + +size_t RadioLibPrint::write(const uint8_t *buffer, size_t size) { + size_t n = 0; + while (size--) { + if (write(*buffer++)) n++; + else break; + } + return n; +} + +#if defined(RADIOLIB_BUILD_ARDUINO) +size_t RadioLibPrint::print(const __FlashStringHelper* fstr) { + // read flash string length + size_t len = 0; + RADIOLIB_NONVOLATILE_PTR p = reinterpret_cast(fstr); + while(true) { + char c = RADIOLIB_NONVOLATILE_READ_BYTE(p++); + len++; + if(c == '\0') { + break; + } + } + + // dynamically allocate memory + #if RADIOLIB_STATIC_ONLY + char str[RADIOLIB_STATIC_ARRAY_SIZE]; + #else + char* str = new char[len]; + #endif + + // copy string from flash + p = reinterpret_cast(fstr); + for(size_t i = 0; i < len; i++) { + str[i] = RADIOLIB_NONVOLATILE_READ_BYTE(p + i); + } + + size_t n = 0; + if(this->encoding == RADIOLIB_ITA2) { + ITA2String ita2 = ITA2String(str); + n = RadioLibPrint::print(ita2); + } else { + n = write((uint8_t*)str, len); + } + #if !RADIOLIB_STATIC_ONLY + delete[] str; + #endif + return(n); +} + +size_t RadioLibPrint::print(const String& str) { + size_t n = 0; + if(this->encoding == RADIOLIB_ITA2) { + ITA2String ita2 = ITA2String(str.c_str()); + n = RadioLibPrint::print(ita2); + } else { + n = write((uint8_t*)str.c_str(), str.length()); + } + return(n); +} + +size_t RadioLibPrint::println(const __FlashStringHelper* fstr) { + size_t n = RadioLibPrint::print(fstr); + n += RadioLibPrint::println(); + return(n); +} + +size_t RadioLibPrint::println(const String& str) { + size_t n = RadioLibPrint::print(str); + n += RadioLibPrint::println(); + return(n); +} +#endif + +size_t RadioLibPrint::print(const char str[]) { + size_t n = 0; + if(this->encoding == RADIOLIB_ITA2) { + ITA2String ita2 = ITA2String(str); + n = RadioLibPrint::print(ita2); + } else { + n = write((uint8_t*)str, strlen(str)); + } + return(n); +} + +size_t RadioLibPrint::print(char c) { + size_t n = 0; + if(this->encoding == RADIOLIB_ITA2) { + ITA2String ita2 = ITA2String(c); + n = RadioLibPrint::print(ita2); + } else { + n = write(c); + } + return(n); +} + +size_t RadioLibPrint::print(unsigned char b, int base) { + return(RadioLibPrint::print((unsigned long)b, base)); +} + +size_t RadioLibPrint::print(int n, int base) { + return(RadioLibPrint::print((long)n, base)); +} + +size_t RadioLibPrint::print(unsigned int n, int base) { + return(RadioLibPrint::print((unsigned long)n, base)); +} + +size_t RadioLibPrint::print(long n, int base) { + if(base == 0) { + return(write(n)); + } else if(base == DEC) { + if (n < 0) { + int t = RadioLibPrint::print('-'); + n = -n; + return(RadioLibPrint::printNumber(n, DEC) + t); + } + return(RadioLibPrint::printNumber(n, DEC)); + } else { + return(RadioLibPrint::printNumber(n, base)); + } +} + +size_t RadioLibPrint::print(unsigned long n, int base) { + if(base == 0) { + return(write(n)); + } else { + return(RadioLibPrint::printNumber(n, base)); + } +} + +size_t RadioLibPrint::print(double n, int digits) { + return(RadioLibPrint::printFloat(n, digits)); +} + +size_t RadioLibPrint::println(const char str[]) { + size_t n = RadioLibPrint::print(str); + n += RadioLibPrint::println(); + return(n); +} + +size_t RadioLibPrint::println(char c) { + size_t n = RadioLibPrint::print(c); + n += RadioLibPrint::println(); + return(n); +} + +size_t RadioLibPrint::println(unsigned char b, int base) { + size_t n = RadioLibPrint::print(b, base); + n += RadioLibPrint::println(); + return(n); +} + +size_t RadioLibPrint::println(int num, int base) { + size_t n = RadioLibPrint::print(num, base); + n += RadioLibPrint::println(); + return(n); +} + +size_t RadioLibPrint::println(unsigned int num, int base) { + size_t n = RadioLibPrint::print(num, base); + n += RadioLibPrint::println(); + return(n); +} + +size_t RadioLibPrint::println(long num, int base) { + size_t n = RadioLibPrint::print(num, base); + n += RadioLibPrint::println(); + return(n); +} + +size_t RadioLibPrint::println(unsigned long num, int base) { + size_t n = RadioLibPrint::print(num, base); + n += RadioLibPrint::println(); + return(n); +} + +size_t RadioLibPrint::println(double d, int digits) { + size_t n = RadioLibPrint::print(d, digits); + n += RadioLibPrint::println(); + return(n); +} + +size_t RadioLibPrint::println(void) { + size_t n = 0; + if(this->encoding == RADIOLIB_ITA2) { + ITA2String lf = ITA2String("\r\n"); + n = RadioLibPrint::print(lf); + } else { + n = write("\r\n"); + } + return(n); +} + +size_t RadioLibPrint::printNumber(unsigned long n, uint8_t base) { + char buf[8 * sizeof(long) + 1]; + char *str = &buf[sizeof(buf) - 1]; + + *str = '\0'; + + if(base < 2) { + base = 10; + } + + do { + char c = n % base; + n /= base; + + *--str = c < 10 ? c + '0' : c + 'A' - 10; + } while(n); + + size_t l = 0; + if(this->encoding == RADIOLIB_ITA2) { + ITA2String ita2 = ITA2String(str); + uint8_t* arr = ita2.byteArr(); + l = write(arr, ita2.length()); + delete[] arr; + } else { + l = write(str); + } + + return(l); +} + +/// \todo improve ITA2 float print speed (characters are sent one at a time) +size_t RadioLibPrint::printFloat(double number, uint8_t digits) { + size_t n = 0; + + char code[] = {0x00, 0x00, 0x00, 0x00}; + if (isnan(number)) strcpy(code, "nan"); + if (isinf(number)) strcpy(code, "inf"); + if (number > (double)4294967040.0) strcpy(code, "ovf"); // constant determined empirically + if (number < (double)-4294967040.0) strcpy(code, "ovf"); // constant determined empirically + + if(code[0] != 0x00) { + if(this->encoding == RADIOLIB_ITA2) { + ITA2String ita2 = ITA2String(code); + uint8_t* arr = ita2.byteArr(); + n = write(arr, ita2.length()); + delete[] arr; + return(n); + } else { + return(write(code)); + } + } + + // Handle negative numbers + if (number < (double)0.0) { + if(this->encoding == RADIOLIB_ITA2) { + ITA2String ita2 = ITA2String("-"); + uint8_t* arr = ita2.byteArr(); + n += write(arr, ita2.length()); + delete[] arr; + } else { + n += RadioLibPrint::print('-'); + } + number = -number; + } + + // Round correctly so that print(1.999, 2) prints as "2.00" + double rounding = 0.5; + for(uint8_t i = 0; i < digits; ++i) { + rounding /= (double)10.0; + } + number += rounding; + + // Extract the integer part of the number and print it + unsigned long int_part = (unsigned long)number; + double remainder = number - (double)int_part; + n += RadioLibPrint::print(int_part); + + // Print the decimal point, but only if there are digits beyond + if(digits > 0) { + if(encoding == RADIOLIB_ITA2) { + ITA2String ita2 = ITA2String("."); + uint8_t* arr = ita2.byteArr(); + n += write(arr, ita2.length()); + delete[] arr; + } else { + n += RadioLibPrint::print('.'); + } + } + + // Extract digits from the remainder one at a time + while(digits-- > 0) { + remainder *= (double) 10.0; + unsigned int toPrint = (unsigned int)(remainder); + n += RadioLibPrint::print(toPrint); + remainder -= toPrint; + } + + return n; +} diff --git a/software/firmware/source/libraries/RadioLib/src/protocols/Print/Print.h b/software/firmware/source/libraries/RadioLib/src/protocols/Print/Print.h new file mode 100644 index 000000000..3645416f9 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/protocols/Print/Print.h @@ -0,0 +1,70 @@ +#if !defined(_RADIOLIB_PRINT_H) +#define _RADIOLIB_PRINT_H + +#include + +#include "ITA2String.h" + +// supported encoding schemes +#define RADIOLIB_ASCII 0 +#define RADIOLIB_ASCII_EXTENDED 1 +#define RADIOLIB_ITA2 2 + +/*! + \class RadioLibPrint + \brief Printing class, based on Arduino Print class with additional encodings. +*/ +class RadioLibPrint { + public: + virtual size_t write(uint8_t) = 0; + size_t write(const char *str) { + if (str == NULL) return 0; + return write(reinterpret_cast(str), strlen(str)); + } + virtual size_t write(const uint8_t *buffer, size_t size); + size_t write(const char *buffer, size_t size) { + return write(reinterpret_cast(buffer), size); + } + + size_t print(ITA2String& ita2); + size_t println(ITA2String& ita2); + + #if defined(RADIOLIB_BUILD_ARDUINO) + size_t print(const __FlashStringHelper *); + size_t print(const String &); + + size_t println(const __FlashStringHelper *); + size_t println(const String &); + #endif + + size_t print(const char[]); + size_t print(char); + size_t print(unsigned char, int = DEC); + size_t print(int, int = DEC); + size_t print(unsigned int, int = DEC); + size_t print(long, int = DEC); + size_t print(unsigned long, int = DEC); + size_t print(double, int = 2); + + size_t println(const char[]); + size_t println(char); + size_t println(unsigned char, int = DEC); + size_t println(int, int = DEC); + size_t println(unsigned int, int = DEC); + size_t println(long, int = DEC); + size_t println(unsigned long, int = DEC); + size_t println(double, int = 2); + size_t println(void); + +#if !RADIOLIB_GODMODE + protected: +#endif + uint8_t encoding = RADIOLIB_ASCII_EXTENDED; + const char* lineFeed = "\r\n"; + + size_t printNumber(unsigned long, uint8_t); + size_t printFloat(double, uint8_t); + +}; + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/protocols/RTTY/RTTY.cpp b/software/firmware/source/libraries/RadioLib/src/protocols/RTTY/RTTY.cpp new file mode 100644 index 000000000..17da7d20c --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/protocols/RTTY/RTTY.cpp @@ -0,0 +1,127 @@ +#include "RTTY.h" + +#include + +#if !RADIOLIB_EXCLUDE_RTTY + +RTTYClient::RTTYClient(PhysicalLayer* phy) { + phyLayer = phy; + lineFeed = "\r\n"; + #if !RADIOLIB_EXCLUDE_AFSK + audioClient = nullptr; + #endif +} + +#if !RADIOLIB_EXCLUDE_AFSK +RTTYClient::RTTYClient(AFSKClient* audio) { + phyLayer = audio->phyLayer; + lineFeed = "\r\n"; + audioClient = audio; +} +#endif + +int16_t RTTYClient::begin(float base, uint32_t shift, uint16_t rate, uint8_t enc, uint8_t stopBits) { + // save configuration + RadioLibPrint::encoding = enc; + stopBitsNum = stopBits; + baseFreqHz = base; + shiftFreqHz = shift; + + // calculate duration of 1 bit + bitDuration = (RadioLibTime_t)1000000/rate; + + // calculate module carrier frequency resolution + uint32_t step = round(phyLayer->getFreqStep()); + + // check minimum shift value + if(shift < step / 2) { + return(RADIOLIB_ERR_INVALID_RTTY_SHIFT); + } + + // round shift to multiples of frequency step size + if(shift % step < (step / 2)) { + shiftFreq = shift / step; + } else { + shiftFreq = (shift / step) + 1; + } + + // calculate 24-bit frequency + baseFreq = (base * 1000000.0) / phyLayer->getFreqStep(); + + // configure for direct mode + return(phyLayer->startDirect()); +} + +void RTTYClient::idle() { + mark(); +} + +size_t RTTYClient::write(uint8_t b) { + uint8_t dataBitsNum = 0; + switch(RadioLibPrint::encoding) { + case RADIOLIB_ASCII: + dataBitsNum = 7; + break; + case RADIOLIB_ASCII_EXTENDED: + dataBitsNum = 8; + break; + case RADIOLIB_ITA2: + dataBitsNum = 5; + break; + default: + return(0); + } + space(); + + uint16_t maxDataMask = 0x01 << (dataBitsNum - 1); + for(uint16_t mask = 0x01; mask <= maxDataMask; mask <<= 1) { + if(b & mask) { + mark(); + } else { + space(); + } + } + + for(uint8_t i = 0; i < stopBitsNum; i++) { + mark(); + } + + return(1); +} + +void RTTYClient::mark() { + Module* mod = phyLayer->getMod(); + RadioLibTime_t start = mod->hal->micros(); + transmitDirect(baseFreq + shiftFreq, baseFreqHz + shiftFreqHz); + mod->waitForMicroseconds(start, bitDuration); +} + +void RTTYClient::space() { + Module* mod = phyLayer->getMod(); + RadioLibTime_t start = mod->hal->micros(); + transmitDirect(baseFreq, baseFreqHz); + mod->waitForMicroseconds(start, bitDuration); +} + +int16_t RTTYClient::transmitDirect(uint32_t freq, uint32_t freqHz) { + #if !RADIOLIB_EXCLUDE_AFSK + if(audioClient != nullptr) { + return(audioClient->tone(freqHz)); + } + #endif + return(phyLayer->transmitDirect(freq)); +} + +int16_t RTTYClient::standby() { + // ensure everything is stopped in interrupt timing mode + Module* mod = phyLayer->getMod(); + mod->waitForMicroseconds(0, 0); + #if !RADIOLIB_EXCLUDE_AFSK + if(audioClient != nullptr) { + return(audioClient->noTone()); + } + #endif + return(phyLayer->standby()); +} + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/protocols/RTTY/RTTY.h b/software/firmware/source/libraries/RadioLib/src/protocols/RTTY/RTTY.h new file mode 100644 index 000000000..1807b36a3 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/protocols/RTTY/RTTY.h @@ -0,0 +1,85 @@ +#if !defined(_RADIOLIB_RTTY_H) +#define _RADIOLIB_RTTY_H + +#include "../../TypeDef.h" + +#if !RADIOLIB_EXCLUDE_RTTY + +#include "../PhysicalLayer/PhysicalLayer.h" +#include "../AFSK/AFSK.h" +#include "../Print/Print.h" +#include "../Print/ITA2String.h" + +/*! + \class RTTYClient + \brief Client for RTTY communication. The public interface is the same as Arduino Serial. +*/ +class RTTYClient: public RadioLibPrint { + public: + /*! + \brief Constructor for 2-FSK mode. + \param phy Pointer to the wireless module providing PhysicalLayer communication. + */ + explicit RTTYClient(PhysicalLayer* phy); + + #if !RADIOLIB_EXCLUDE_AFSK + /*! + \brief Constructor for AFSK mode. + \param audio Pointer to the AFSK instance providing audio. + */ + explicit RTTYClient(AFSKClient* audio); + #endif + + // basic methods + + /*! + \brief Initialization method. + \param base Base (space) frequency to be used in MHz (in 2-FSK mode), or the space tone frequency in Hz (in AFSK mode) + \param shift Frequency shift between mark and space in Hz. + \param rate Baud rate to be used during transmission. + \param enc Encoding to be used. Defaults to ASCII. + \param stopBits Number of stop bits to be used. + \returns \ref status_codes + */ + int16_t begin(float base, uint32_t shift, uint16_t rate, uint8_t enc = RADIOLIB_ASCII, uint8_t stopBits = 1); + + /*! + \brief Send out idle condition (RF tone at mark frequency). + */ + void idle(); + + /*! + \brief Stops transmitting. + \returns \ref status_codes + */ + int16_t standby(); + + /*! + \brief Write one byte. Implementation of interface of the RadioLibPrint/Print class. + \param b Byte to write. + \returns 1 if the byte was written, 0 otherwise. + */ + size_t write(uint8_t b) override; + +#if !RADIOLIB_GODMODE + private: +#endif + PhysicalLayer* phyLayer; + #if !RADIOLIB_EXCLUDE_AFSK + AFSKClient* audioClient; + #endif + + uint32_t baseFreq = 0, baseFreqHz = 0; + uint32_t shiftFreq = 0, shiftFreqHz = 0; + RadioLibTime_t bitDuration = 0; + uint8_t stopBitsNum = 0; + + void mark(); + void space(); + + int16_t transmitDirect(uint32_t freq = 0, uint32_t freqHz = 0); +}; + +#endif + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/protocols/SSTV/SSTV.cpp b/software/firmware/source/libraries/RadioLib/src/protocols/SSTV/SSTV.cpp new file mode 100644 index 000000000..3f72a3b23 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/protocols/SSTV/SSTV.cpp @@ -0,0 +1,376 @@ +#include "SSTV.h" +#if !RADIOLIB_EXCLUDE_SSTV + +const SSTVMode_t Scottie1 { + .visCode = RADIOLIB_SSTV_SCOTTIE_1, + .width = 320, + .height = 256, + .scanPixelLen = 432, + .numTones = 7, + .tones = { + { .type = tone_t::GENERIC, .len = 1500, .freq = 1500 }, + { .type = tone_t::SCAN_GREEN_Y, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 1500, .freq = 1500 }, + { .type = tone_t::SCAN_BLUE_CB, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 9000, .freq = 1200 }, + { .type = tone_t::GENERIC, .len = 1500, .freq = 1500 }, + { .type = tone_t::SCAN_RED_CR, .len = 0, .freq = 0 } + } +}; + +const SSTVMode_t Scottie2 { + .visCode = RADIOLIB_SSTV_SCOTTIE_2, + .width = 320, + .height = 256, + .scanPixelLen = 275, + .numTones = 7, + .tones = { + { .type = tone_t::GENERIC, .len = 1500, .freq = 1500 }, + { .type = tone_t::SCAN_GREEN_Y, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 1500, .freq = 1500 }, + { .type = tone_t::SCAN_BLUE_CB, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 9000, .freq = 1200 }, + { .type = tone_t::GENERIC, .len = 1500, .freq = 1500 }, + { .type = tone_t::SCAN_RED_CR, .len = 0, .freq = 0 } + } +}; + +const SSTVMode_t ScottieDX { + .visCode = RADIOLIB_SSTV_SCOTTIE_DX, + .width = 320, + .height = 256, + .scanPixelLen = 1080, + .numTones = 7, + .tones = { + { .type = tone_t::GENERIC, .len = 1500, .freq = 1500 }, + { .type = tone_t::SCAN_GREEN_Y, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 1500, .freq = 1500 }, + { .type = tone_t::SCAN_BLUE_CB, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 9000, .freq = 1200 }, + { .type = tone_t::GENERIC, .len = 1500, .freq = 1500 }, + { .type = tone_t::SCAN_RED_CR, .len = 0, .freq = 0 } + } +}; + +const SSTVMode_t Martin1 { + .visCode = RADIOLIB_SSTV_MARTIN_1, + .width = 320, + .height = 256, + .scanPixelLen = 458, + .numTones = 8, + .tones = { + { .type = tone_t::GENERIC, .len = 4862, .freq = 1200 }, + { .type = tone_t::GENERIC, .len = 572, .freq = 1500 }, + { .type = tone_t::SCAN_GREEN_Y, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 572, .freq = 1500 }, + { .type = tone_t::SCAN_BLUE_CB, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 572, .freq = 1500 }, + { .type = tone_t::SCAN_RED_CR, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 572, .freq = 1500 } + } +}; + +const SSTVMode_t Martin2 { + .visCode = RADIOLIB_SSTV_MARTIN_2, + .width = 320, + .height = 256, + .scanPixelLen = 229, + .numTones = 8, + .tones = { + { .type = tone_t::GENERIC, .len = 4862, .freq = 1200 }, + { .type = tone_t::GENERIC, .len = 572, .freq = 1500 }, + { .type = tone_t::SCAN_GREEN_Y, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 572, .freq = 1500 }, + { .type = tone_t::SCAN_BLUE_CB, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 572, .freq = 1500 }, + { .type = tone_t::SCAN_RED_CR, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 572, .freq = 1500 } + } +}; + +const SSTVMode_t Wrasse { + .visCode = RADIOLIB_SSTV_WRASSE_SC2_180, + .width = 320, + .height = 256, + .scanPixelLen = 734, + .numTones = 5, + .tones = { + { .type = tone_t::GENERIC, .len = 5523, .freq = 1200 }, + { .type = tone_t::GENERIC, .len = 500, .freq = 1500 }, + { .type = tone_t::SCAN_RED_CR, .len = 0, .freq = 0 }, + { .type = tone_t::SCAN_GREEN_Y, .len = 0, .freq = 0 }, + { .type = tone_t::SCAN_BLUE_CB, .len = 0, .freq = 0 } + } +}; + +const SSTVMode_t PasokonP3 { + .visCode = RADIOLIB_SSTV_PASOKON_P3, + .width = 640, + .height = 496, + .scanPixelLen = 208, + .numTones = 7, + .tones = { + { .type = tone_t::GENERIC, .len = 5208, .freq = 1200 }, + { .type = tone_t::GENERIC, .len = 1042, .freq = 1500 }, + { .type = tone_t::SCAN_RED_CR, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 1042, .freq = 1500 }, + { .type = tone_t::SCAN_GREEN_Y, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 1042, .freq = 1500 }, + { .type = tone_t::SCAN_BLUE_CB, .len = 0, .freq = 0 } + } +}; + +const SSTVMode_t PasokonP5 { + .visCode = RADIOLIB_SSTV_PASOKON_P5, + .width = 640, + .height = 496, + .scanPixelLen = 312, + .numTones = 7, + .tones = { + { .type = tone_t::GENERIC, .len = 7813, .freq = 1200 }, + { .type = tone_t::GENERIC, .len = 1563, .freq = 1500 }, + { .type = tone_t::SCAN_RED_CR, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 1563, .freq = 1500 }, + { .type = tone_t::SCAN_GREEN_Y, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 1563, .freq = 1500 }, + { .type = tone_t::SCAN_BLUE_CB, .len = 0, .freq = 0 } + } +}; + +const SSTVMode_t PasokonP7 { + .visCode = RADIOLIB_SSTV_PASOKON_P7, + .width = 640, + .height = 496, + .scanPixelLen = 417, + .numTones = 7, + .tones = { + { .type = tone_t::GENERIC, .len = 10417, .freq = 1200 }, + { .type = tone_t::GENERIC, .len = 2083, .freq = 1500 }, + { .type = tone_t::SCAN_RED_CR, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 2083, .freq = 1500 }, + { .type = tone_t::SCAN_GREEN_Y, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 2083, .freq = 1500 }, + { .type = tone_t::SCAN_BLUE_CB, .len = 0, .freq = 0 } + } +}; + +const SSTVMode_t Robot36 { + .visCode = RADIOLIB_SSTV_ROBOT_36, + .width = 320, + .height = 240, + .scanPixelLen = 275, // this is the Y-scan length, Cb/Cr are one half + .numTones = 6, + .tones = { + { .type = tone_t::GENERIC, .len = 9000, .freq = 1200 }, + { .type = tone_t::GENERIC, .len = 3000, .freq = 1500 }, + { .type = tone_t::SCAN_GREEN_Y, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 4500, .freq = 1500 }, + { .type = tone_t::GENERIC, .len = 1500, .freq = 1900 }, + { .type = tone_t::SCAN_BLUE_CB, .len = 0, .freq = 0 }, // on even lines, this is the Cr component + } +}; + +const SSTVMode_t Robot72 { + .visCode = RADIOLIB_SSTV_ROBOT_72, + .width = 320, + .height = 240, + .scanPixelLen = 431, // this is the Y-scan length, Cb/Cr are one half + .numTones = 9, + .tones = { + { .type = tone_t::GENERIC, .len = 9000, .freq = 1200 }, + { .type = tone_t::GENERIC, .len = 3000, .freq = 1500 }, + { .type = tone_t::SCAN_GREEN_Y, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 4500, .freq = 1500 }, + { .type = tone_t::GENERIC, .len = 1500, .freq = 1900 }, + { .type = tone_t::SCAN_RED_CR, .len = 0, .freq = 0 }, + { .type = tone_t::GENERIC, .len = 4500, .freq = 2300 }, + { .type = tone_t::GENERIC, .len = 1500, .freq = 1500 }, + { .type = tone_t::SCAN_BLUE_CB, .len = 0, .freq = 0 }, + } +}; + +SSTVClient::SSTVClient(PhysicalLayer* phy) { + phyLayer = phy; + #if !RADIOLIB_EXCLUDE_AFSK + audioClient = nullptr; + #endif +} + +#if !RADIOLIB_EXCLUDE_AFSK +SSTVClient::SSTVClient(AFSKClient* audio) { + phyLayer = audio->phyLayer; + audioClient = audio; +} +#endif + +#if !RADIOLIB_EXCLUDE_AFSK +int16_t SSTVClient::begin(const SSTVMode_t& mode) { + if(audioClient == nullptr) { + // this initialization method can only be used in AFSK mode + return(RADIOLIB_ERR_WRONG_MODEM); + } + + return(begin(0, mode)); +} +#endif + +int16_t SSTVClient::begin(float base, const SSTVMode_t& mode) { + // save mode + txMode = mode; + + // calculate 24-bit frequency + baseFreq = (base * 1000000.0) / phyLayer->getFreqStep(); + + // configure for direct mode + return(phyLayer->startDirect()); +} + +int16_t SSTVClient::setCorrection(float correction) { + // check if mode is initialized + if(txMode.visCode == 0) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // apply correction factor to all timings + txMode.scanPixelLen *= correction; + for(uint8_t i = 0; i < txMode.numTones; i++) { + txMode.tones[i].len *= correction; + } + return(RADIOLIB_ERR_NONE); +} + +void SSTVClient::idle() { + phyLayer->transmitDirect(); + this->tone(RADIOLIB_SSTV_TONE_LEADER); +} + +void SSTVClient::sendHeader() { + // reset line counter + lineCount = 0; + phyLayer->transmitDirect(); + + // send the first part of header (leader-break-leader) + this->tone(RADIOLIB_SSTV_TONE_LEADER, RADIOLIB_SSTV_HEADER_LEADER_LENGTH); + this->tone(RADIOLIB_SSTV_TONE_BREAK, RADIOLIB_SSTV_HEADER_BREAK_LENGTH); + this->tone(RADIOLIB_SSTV_TONE_LEADER, RADIOLIB_SSTV_HEADER_LEADER_LENGTH); + + // VIS start bit + this->tone(RADIOLIB_SSTV_TONE_BREAK, RADIOLIB_SSTV_HEADER_BIT_LENGTH); + + // VIS code + uint8_t parityCount = 0; + for(uint8_t mask = 0x01; mask < 0x80; mask <<= 1) { + if(txMode.visCode & mask) { + this->tone(RADIOLIB_SSTV_TONE_VIS_1, RADIOLIB_SSTV_HEADER_BIT_LENGTH); + parityCount++; + } else { + this->tone(RADIOLIB_SSTV_TONE_VIS_0, RADIOLIB_SSTV_HEADER_BIT_LENGTH); + } + } + + // VIS parity + if(parityCount % 2 == 0) { + // even parity + this->tone(RADIOLIB_SSTV_TONE_VIS_0, RADIOLIB_SSTV_HEADER_BIT_LENGTH); + } else { + // odd parity + this->tone(RADIOLIB_SSTV_TONE_VIS_1, RADIOLIB_SSTV_HEADER_BIT_LENGTH); + } + + // VIS stop bit + this->tone(RADIOLIB_SSTV_TONE_BREAK, RADIOLIB_SSTV_HEADER_BIT_LENGTH); +} + +void SSTVClient::sendLine(const uint32_t* imgLine) { + // check first line in Scottie modes + if((lineCount == 0) && ((txMode.visCode == RADIOLIB_SSTV_SCOTTIE_1) || (txMode.visCode == RADIOLIB_SSTV_SCOTTIE_2) || (txMode.visCode == RADIOLIB_SSTV_SCOTTIE_DX))) { + // send start sync tone + this->tone(RADIOLIB_SSTV_TONE_BREAK, 9000); + } + + // send all tones in sequence + for(uint8_t i = 0; i < txMode.numTones; i++) { + if((txMode.tones[i].type == tone_t::GENERIC) && (txMode.tones[i].len > 0)) { + // Robot36 has different separator tones for even and odd lines + uint32_t freq = txMode.tones[i].freq; + if((txMode.visCode == RADIOLIB_SSTV_ROBOT_36) && (i == 3)) { + freq = (lineCount % 2) ? 2300 : txMode.tones[3].freq; + } + + // sync/porch tones + this->tone(freq, txMode.tones[i].len); + + } else { + // scan lines + for(uint16_t j = 0; j < txMode.width; j++) { + uint32_t color = imgLine[j]; + uint32_t len = txMode.scanPixelLen; + + // Robot modes work in YCbCr + if((txMode.visCode == RADIOLIB_SSTV_ROBOT_36) || (txMode.visCode == RADIOLIB_SSTV_ROBOT_72)) { + uint8_t r = (color & 0x00FF0000) >> 16; + uint8_t g = (color & 0x0000FF00) >> 8; + uint8_t b = (color & 0x000000FF); + uint8_t y = 16.0 + (0.003906 * ((65.738 * r) + (129.057 * g) + (25.064 * b))); + uint8_t cb = 128.0 + (0.003906 * ((-37.945 * r) + (-74.494 * g) + (112.439 * b))); + uint8_t cr = 128.0 + (0.003906 * ((112.439 * r) + (-94.154 * g) + (-18.285 * b))); + color = ((uint32_t)y << 8); + if(txMode.visCode == RADIOLIB_SSTV_ROBOT_36) { + // odd lines carry Cb, even lines carry Cr + color |= (lineCount % 2) ? cb : cr; + } else { + color |= ((uint32_t)cr << 16) | cb; + } + + } + + switch(txMode.tones[i].type) { + case(tone_t::SCAN_RED_CR): + color &= 0x00FF0000; + color >>= 16; + if((txMode.visCode == RADIOLIB_SSTV_ROBOT_36) || (txMode.visCode == RADIOLIB_SSTV_ROBOT_72)) { + len /= 2; + } + break; + case(tone_t::SCAN_GREEN_Y): + color &= 0x0000FF00; + color >>= 8; + break; + case(tone_t::SCAN_BLUE_CB): + color &= 0x000000FF; + if((txMode.visCode == RADIOLIB_SSTV_ROBOT_36) || (txMode.visCode == RADIOLIB_SSTV_ROBOT_72)) { + len /= 2; + } + break; + case(tone_t::GENERIC): + break; + } + this->tone(RADIOLIB_SSTV_TONE_BRIGHTNESS_MIN + ((float)color * 3.1372549), len); + } + } + } + + // increment line counter (needed for Robot36 mode) + lineCount++; +} + +uint16_t SSTVClient::getPictureHeight() const { + return(txMode.height); +} + +void SSTVClient::tone(float freq, RadioLibTime_t len) { + Module* mod = phyLayer->getMod(); + RadioLibTime_t start = mod->hal->micros(); + #if !RADIOLIB_EXCLUDE_AFSK + if(audioClient != nullptr) { + audioClient->tone(freq, false); + } else { + phyLayer->transmitDirect(baseFreq + (freq / phyLayer->getFreqStep())); + } + #else + phyLayer->transmitDirect(baseFreq + (freq / phyLayer->getFreqStep())); + #endif + mod->waitForMicroseconds(start, len); +} + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/protocols/SSTV/SSTV.h b/software/firmware/source/libraries/RadioLib/src/protocols/SSTV/SSTV.h new file mode 100644 index 000000000..3ec493a4e --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/protocols/SSTV/SSTV.h @@ -0,0 +1,208 @@ +#if !defined(_RADIOLIB_SSTV_H) +#define _RADIOLIB_SSTV_H + +#include "../../TypeDef.h" + +#if !RADIOLIB_EXCLUDE_SSTV + +#include "../PhysicalLayer/PhysicalLayer.h" +#include "../AFSK/AFSK.h" + +// the following implementation is based on information from +// http://www.barberdsp.com/downloads/Dayton%20Paper.pdf + +// VIS codes +#define RADIOLIB_SSTV_SCOTTIE_1 60 +#define RADIOLIB_SSTV_SCOTTIE_2 56 +#define RADIOLIB_SSTV_SCOTTIE_DX 76 +#define RADIOLIB_SSTV_MARTIN_1 44 +#define RADIOLIB_SSTV_MARTIN_2 40 +#define RADIOLIB_SSTV_WRASSE_SC2_180 55 +#define RADIOLIB_SSTV_PASOKON_P3 113 +#define RADIOLIB_SSTV_PASOKON_P5 114 +#define RADIOLIB_SSTV_PASOKON_P7 115 +#define RADIOLIB_SSTV_ROBOT_36 8 +#define RADIOLIB_SSTV_ROBOT_72 12 + +// SSTV tones in Hz +#define RADIOLIB_SSTV_TONE_LEADER 1900 +#define RADIOLIB_SSTV_TONE_BREAK 1200 +#define RADIOLIB_SSTV_TONE_VIS_1 1100 +#define RADIOLIB_SSTV_TONE_VIS_0 1300 +#define RADIOLIB_SSTV_TONE_BRIGHTNESS_MIN 1500 +#define RADIOLIB_SSTV_TONE_BRIGHTNESS_MAX 2300 + +// calibration header timing in us +#define RADIOLIB_SSTV_HEADER_LEADER_LENGTH 300000 +#define RADIOLIB_SSTV_HEADER_BREAK_LENGTH 10000 +#define RADIOLIB_SSTV_HEADER_BIT_LENGTH 30000 + +/*! + \struct tone_t + \brief Structure to save data about tone. +*/ +struct tone_t { + + /*! + \brief Tone type: GENERIC for sync and porch tones, SCAN_GREEN, SCAN_BLUE and SCAN_RED for scan lines. + */ + enum { + GENERIC = 0, + SCAN_GREEN_Y, + SCAN_BLUE_CB, + SCAN_RED_CR + } type; + + /*! + \brief Length of tone in us, set to 0 for picture scan tones. + */ + RadioLibTime_t len; + + /*! + \brief Frequency of tone in Hz, set to 0 for picture scan tones. + */ + uint16_t freq; +}; + +/*! + \struct SSTVMode_t + \brief Structure to save data about supported SSTV modes. +*/ +struct SSTVMode_t { + + /*! + \brief Unique VIS code of the SSTV mode. + */ + uint8_t visCode; + + /*! + \brief Picture width in pixels. + */ + uint16_t width; + + /*! + \brief Picture height in pixels. + */ + uint16_t height; + + /*! + \brief Pixel scan length in us. + */ + uint16_t scanPixelLen; + + /*! + \brief Number of tones in each transmission line. Picture scan data is considered single tone. + */ + uint8_t numTones; + + /*! + \brief Sequence of tones in each transmission line. This is used to create the correct encoding sequence. + */ + tone_t tones[9]; +}; + +// all currently supported SSTV modes +extern const SSTVMode_t Scottie1; +extern const SSTVMode_t Scottie2; +extern const SSTVMode_t ScottieDX; +extern const SSTVMode_t Martin1; +extern const SSTVMode_t Martin2; +extern const SSTVMode_t Wrasse; +extern const SSTVMode_t PasokonP3; +extern const SSTVMode_t PasokonP5; +extern const SSTVMode_t PasokonP7; +extern const SSTVMode_t Robot36; +extern const SSTVMode_t Robot72; + +/*! + \class SSTVClient + \brief Client for SSTV transmissions. +*/ +class SSTVClient { + public: + /*! + \brief Constructor for 2-FSK mode. + \param phy Pointer to the wireless module providing PhysicalLayer communication. + */ + explicit SSTVClient(PhysicalLayer* phy); + + #if !RADIOLIB_EXCLUDE_AFSK + /*! + \brief Constructor for AFSK mode. + \param audio Pointer to the AFSK instance providing audio. + */ + explicit SSTVClient(AFSKClient* audio); + #endif + + // basic methods + + /*! + \brief Initialization method for 2-FSK. + \param base Base "0 Hz tone" RF frequency to be used in MHz. + \param mode SSTV mode to be used. Currently supported modes are Scottie1, Scottie2, + ScottieDX, Martin1, Martin2, Wrasse, PasokonP3, PasokonP5 and PasokonP7, + Robot36 and Robot37. + \returns \ref status_codes + */ + int16_t begin(float base, const SSTVMode_t& mode); + + #if !RADIOLIB_EXCLUDE_AFSK + /*! + \brief Initialization method for AFSK. + \param mode SSTV mode to be used. Currently supported modes are Scottie1, Scottie2, + ScottieDX, Martin1, Martin2, Wrasse, PasokonP3, PasokonP5 and PasokonP7, + Robot36 and Robot37. + \returns \ref status_codes + */ + int16_t begin(const SSTVMode_t& mode); + #endif + + /*! + \brief Set correction coefficient for tone length. + \param correction Timing correction factor, used to adjust the length of timing pulses. + Less than 1.0 leads to shorter timing pulses, defaults to 1.0 (no correction). + \returns \ref status_codes + */ + int16_t setCorrection(float correction); + + /*! + \brief Sends out tone at 1900 Hz. + */ + void idle(); + + /*! + \brief Sends synchronization header for the SSTV mode set in begin method. + */ + void sendHeader(); + + /*! + \brief Sends single picture line in the currently configured SSTV mode. + \param imgLine Image line to send, in 24-bit RGB. It is up to the user to ensure that + imgLine has enough pixels to send it in the current SSTV mode. + */ + void sendLine(const uint32_t* imgLine); + + /*! + \brief Get picture height of the currently configured SSTV mode. + \returns Picture height of the currently configured SSTV mode in pixels. + */ + uint16_t getPictureHeight() const; + +#if !RADIOLIB_GODMODE + private: +#endif + PhysicalLayer* phyLayer; + #if !RADIOLIB_EXCLUDE_AFSK + AFSKClient* audioClient; + #endif + + uint32_t baseFreq = 0; + SSTVMode_t txMode = Scottie1; + uint32_t lineCount = 0; + + void tone(float freq, RadioLibTime_t len = 0); +}; + +#endif + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/utils/CRC.cpp b/software/firmware/source/libraries/RadioLib/src/utils/CRC.cpp new file mode 100644 index 000000000..d0dd03a0a --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/utils/CRC.cpp @@ -0,0 +1,35 @@ +#include "CRC.h" + +RadioLibCRC::RadioLibCRC() { + +} + +uint32_t RadioLibCRC::checksum(const uint8_t* buff, size_t len) { + uint32_t crc = this->init; + size_t pos = 0; + for(size_t i = 0; i < 8*len; i++) { + if(i % 8 == 0) { + uint32_t in = buff[pos++]; + if(this->refIn) { + in = rlb_reflect(in, 8); + } + crc ^= (in << (this->size - 8)); + } + + if(crc & ((uint32_t)1 << (this->size - 1))) { + crc <<= (uint32_t)1; + crc ^= this->poly; + } else { + crc <<= (uint32_t)1; + } + } + + crc ^= this->out; + if(this->refOut) { + crc = rlb_reflect(crc, this->size); + } + crc &= (uint32_t)0xFFFFFFFF >> (32 - this->size); + return(crc); +} + +RadioLibCRC RadioLibCRCInstance; diff --git a/software/firmware/source/libraries/RadioLib/src/utils/CRC.h b/software/firmware/source/libraries/RadioLib/src/utils/CRC.h new file mode 100644 index 000000000..a93a0259c --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/utils/CRC.h @@ -0,0 +1,65 @@ +#if !defined(_RADIOLIB_CRC_H) +#define _RADIOLIB_CRC_H + +#include "../TypeDef.h" +#include "../Module.h" + +// CCITT CRC properties (used by AX.25) +#define RADIOLIB_CRC_CCITT_POLY (0x1021) +#define RADIOLIB_CRC_CCITT_INIT (0xFFFF) +#define RADIOLIB_CRC_CCITT_OUT (0xFFFF) + +/*! + \class RadioLibCRC + \brief Class to calculate CRCs of varying formats. +*/ +class RadioLibCRC { + public: + /*! + \brief CRC size in bits. + */ + uint8_t size = 8; + + /*! + \brief CRC polynomial. + */ + uint32_t poly = 0; + + /*! + \brief Initial value. + */ + uint32_t init = 0; + + /*! + \brief Final XOR value. + */ + uint32_t out = 0; + + /*! + \brief Whether to reflect input bytes. + */ + bool refIn = false; + + /*! + \brief Whether to reflect the result. + */ + bool refOut = false; + + /*! + \brief Default constructor. + */ + RadioLibCRC(); + + /*! + \brief Calculate checksum of a buffer. + \param buff Buffer to calculate the checksum over. + \param len Size of the buffer in bytes. + \returns The resulting checksum. + */ + uint32_t checksum(const uint8_t* buff, size_t len); +}; + +// the global singleton +extern RadioLibCRC RadioLibCRCInstance; + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/utils/Cryptography.cpp b/software/firmware/source/libraries/RadioLib/src/utils/Cryptography.cpp new file mode 100644 index 000000000..9411bd1a3 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/utils/Cryptography.cpp @@ -0,0 +1,294 @@ +#include "Cryptography.h" + +#include + +RadioLibAES128::RadioLibAES128() { + +} + +void RadioLibAES128::init(uint8_t* key) { + this->keyPtr = key; + this->keyExpansion(this->roundKey, key); +} + +size_t RadioLibAES128::encryptECB(uint8_t* in, size_t len, uint8_t* out) { + size_t num_blocks = len / RADIOLIB_AES128_BLOCK_SIZE; + if(len % RADIOLIB_AES128_BLOCK_SIZE) { + num_blocks++; + } + + memset(out, 0x00, RADIOLIB_AES128_BLOCK_SIZE * num_blocks); + memcpy(out, in, len); + + for(size_t i = 0; i < num_blocks; i++) { + this->cipher((state_t*)(out + (RADIOLIB_AES128_BLOCK_SIZE * i)), this->roundKey); + } + + return(num_blocks*RADIOLIB_AES128_BLOCK_SIZE); +} + +size_t RadioLibAES128::decryptECB(uint8_t* in, size_t len, uint8_t* out) { + size_t num_blocks = len / RADIOLIB_AES128_BLOCK_SIZE; + if(len % RADIOLIB_AES128_BLOCK_SIZE) { + num_blocks++; + } + + memset(out, 0x00, RADIOLIB_AES128_BLOCK_SIZE * num_blocks); + memcpy(out, in, len); + + for(size_t i = 0; i < num_blocks; i++) { + this->decipher((state_t*)(out + (RADIOLIB_AES128_BLOCK_SIZE * i)), this->roundKey); + } + + return(num_blocks*RADIOLIB_AES128_BLOCK_SIZE); +} + +void RadioLibAES128::generateCMAC(uint8_t* in, size_t len, uint8_t* cmac) { + uint8_t key1[RADIOLIB_AES128_BLOCK_SIZE]; + uint8_t key2[RADIOLIB_AES128_BLOCK_SIZE]; + this->generateSubkeys(key1, key2); + + size_t num_blocks = len / RADIOLIB_AES128_BLOCK_SIZE; + bool flag = true; + if(len % RADIOLIB_AES128_BLOCK_SIZE) { + num_blocks++; + flag = false; + } + + uint8_t* buff = new uint8_t[num_blocks * RADIOLIB_AES128_BLOCK_SIZE]; + memset(buff, 0, num_blocks * RADIOLIB_AES128_BLOCK_SIZE); + memcpy(buff, in, len); + if (flag) { + this->blockXor(&buff[(num_blocks - 1)*RADIOLIB_AES128_BLOCK_SIZE], &buff[(num_blocks - 1)*RADIOLIB_AES128_BLOCK_SIZE], key1); + } else { + buff[len] = 0x80; + this->blockXor(&buff[(num_blocks - 1)*RADIOLIB_AES128_BLOCK_SIZE], &buff[(num_blocks - 1)*RADIOLIB_AES128_BLOCK_SIZE], key2); + } + + uint8_t X[] = { + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 + }; + uint8_t Y[RADIOLIB_AES128_BLOCK_SIZE]; + + for(size_t i = 0; i < num_blocks - 1; i++) { + this->blockXor(Y, &buff[i*RADIOLIB_AES128_BLOCK_SIZE], X); + this->encryptECB(Y, RADIOLIB_AES128_BLOCK_SIZE, X); + } + this->blockXor(Y, &buff[(num_blocks - 1)*RADIOLIB_AES128_BLOCK_SIZE], X); + this->encryptECB(Y, RADIOLIB_AES128_BLOCK_SIZE, cmac); + delete[] buff; +} + +bool RadioLibAES128::verifyCMAC(uint8_t* in, size_t len, const uint8_t* cmac) { + uint8_t cmacReal[RADIOLIB_AES128_BLOCK_SIZE]; + this->generateCMAC(in, len, cmacReal); + for(size_t i = 0; i < RADIOLIB_AES128_BLOCK_SIZE; i++) { + if((cmacReal[i] != cmac[i])) { + return(false); + } + } + return(true); +} + +void RadioLibAES128::keyExpansion(uint8_t* roundKey, const uint8_t* key) { + uint8_t tmp[4]; + + // the first round key is the key itself + for(uint8_t i = 0; i < RADIOLIB_AES128_N_K; i++) { + for(uint8_t j = 0; j < 4; j++) { + roundKey[(i * 4) + j] = key[(i * 4) + j]; + } + } + + // All other round keys are found from the previous round keys. + for(uint8_t i = RADIOLIB_AES128_N_K; i < RADIOLIB_AES128_N_B * (RADIOLIB_AES128_N_R + 1); ++i) { + uint8_t j = (i - 1) * 4; + for(uint8_t k = 0; k < 4; k++) { + tmp[k] = roundKey[j + k]; + } + + if(i % RADIOLIB_AES128_N_K == 0) { + this->rotWord(tmp); + this->subWord(tmp); + tmp[0] = tmp[0] ^ aesRcon[i/RADIOLIB_AES128_N_K]; + } + + j = i * 4; + uint8_t k = (i - RADIOLIB_AES128_N_K) * 4; + for(uint8_t l = 0; l < 4; l++) { + roundKey[j + l] = roundKey[k + l] ^ tmp[l]; + } + } +} + +void RadioLibAES128::cipher(state_t* state, uint8_t* roundKey) { + this->addRoundKey(0, state, roundKey); + for(uint8_t round = 1; round < RADIOLIB_AES128_N_R; round++) { + this->subBytes(state, aesSbox); + this->shiftRows(state, false); + this->mixColumns(state, false); + this->addRoundKey(round, state, roundKey); + } + + this->subBytes(state, aesSbox); + this->shiftRows(state, false); + this->addRoundKey(RADIOLIB_AES128_N_R, state, roundKey); +} + + +void RadioLibAES128::decipher(state_t* state, uint8_t* roundKey) { + this->addRoundKey(RADIOLIB_AES128_N_R, state, roundKey); + for(uint8_t round = RADIOLIB_AES128_N_R - 1; round > 0; --round) { + this->shiftRows(state, true); + this->subBytes(state, aesSboxInv); + this->addRoundKey(round, state, roundKey); + this->mixColumns(state, true); + } + + this->shiftRows(state, true); + this->subBytes(state, aesSboxInv); + this->addRoundKey(0, state, roundKey); +} + +void RadioLibAES128::subWord(uint8_t* word) { + for(size_t i = 0; i < 4; i++) { + word[i] = RADIOLIB_NONVOLATILE_READ_BYTE(&aesSbox[word[i]]); + } +} + +void RadioLibAES128::rotWord(uint8_t* word) { + uint8_t tmp[4]; + memcpy(tmp, word, 4); + for(size_t i = 0; i < 4; i++) { + word[i] = tmp[(i + 1) % 4]; + } +} + +void RadioLibAES128::addRoundKey(uint8_t round, state_t* state, const uint8_t* roundKey) { + for(size_t row = 0; row < 4; row++) { + for(size_t col = 0; col < 4; col++) { + (*state)[row][col] ^= roundKey[(round * RADIOLIB_AES128_N_B * 4) + (row * RADIOLIB_AES128_N_B) + col]; + } + } +} + +void RadioLibAES128::blockXor(uint8_t* dst, const uint8_t* a, const uint8_t* b) { + for(uint8_t j = 0; j < RADIOLIB_AES128_BLOCK_SIZE; j++) { + dst[j] = a[j] ^ b[j]; + } +} + +void RadioLibAES128::blockLeftshift(uint8_t* dst, const uint8_t* src) { + uint8_t ovf = 0x00; + for(int8_t i = RADIOLIB_AES128_BLOCK_SIZE - 1; i >= 0; i--) { + dst[i] = src[i] << 1; + dst[i] |= ovf; + ovf = (src[i] & 0x80) ? 1 : 0; + } +} + +void RadioLibAES128::generateSubkeys(uint8_t* key1, uint8_t* key2) { + uint8_t const_Zero[] = { + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 + }; + + uint8_t const_Rb[] = { + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x87 + }; + + uint8_t L[RADIOLIB_AES128_BLOCK_SIZE]; + this->encryptECB(const_Zero, RADIOLIB_AES128_BLOCK_SIZE, L); + this->blockLeftshift(key1, L); + if(L[0] & 0x80) { + this->blockXor(key1, key1, const_Rb); + } + + this->blockLeftshift(key2, key1); + if(key1[0] & 0x80) { + this->blockXor(key2, key2, const_Rb); + } +} + +void RadioLibAES128::subBytes(state_t* state, const uint8_t* box) { + for(size_t row = 0; row < 4; row++) { + for(size_t col = 0; col < 4; col++) { + (*state)[col][row] = RADIOLIB_NONVOLATILE_READ_BYTE(&box[(*state)[col][row]]); + } + } +} + +void RadioLibAES128::shiftRows(state_t* state, bool inv) { + uint8_t tmp[4]; + for(size_t row = 1; row < 4; row++) { + for(size_t col = 0; col < 4; col++) { + if(!inv) { + tmp[col] = (*state)[(row + col) % 4][row]; + } else { + tmp[(row + col) % 4] = (*state)[col][row]; + } + } + for(size_t col = 0; col < 4; col++) { + (*state)[col][row] = tmp[col]; + } + } +} + +void RadioLibAES128::mixColumns(state_t* state, bool inv) { + uint8_t tmp[4]; + uint8_t matmul[][4] = { + 0x02, 0x03, 0x01, 0x01, + 0x01, 0x02, 0x03, 0x01, + 0x01, 0x01, 0x02, 0x03, + 0x03, 0x01, 0x01, 0x02 + }; + if(inv) { + uint8_t matmul_inv[][4] = { + 0x0e, 0x0b, 0x0d, 0x09, + 0x09, 0x0e, 0x0b, 0x0d, + 0x0d, 0x09, 0x0e, 0x0b, + 0x0b, 0x0d, 0x09, 0x0e + }; + memcpy(matmul, matmul_inv, sizeof(matmul_inv)); + } + + for(size_t col = 0; col < 4; col++) { + for(size_t row = 0; row < 4; row++) { + tmp[row] = (*state)[col][row]; + } + for(size_t i = 0; i < 4; i++) { + (*state)[col][i] = 0x00; + for(size_t j = 0; j < 4; j++) { + (*state)[col][i] ^= mul(matmul[i][j], tmp[j]); + } + } + } +} + +uint8_t RadioLibAES128::mul(uint8_t a, uint8_t b) { + uint8_t sb[4]; + uint8_t out = 0; + sb[0] = b; + for(size_t i = 1; i < 4; i++) { + sb[i] = sb[i - 1] << 1; + if (sb[i - 1] & 0x80) { + sb[i] ^= 0x1b; + } + } + for(size_t i = 0; i < 4; i++) { + if(a >> i & 0x01) { + out ^= sb[i]; + } + } + return(out); +} + +RadioLibAES128 RadioLibAES128Instance; diff --git a/software/firmware/source/libraries/RadioLib/src/utils/Cryptography.h b/software/firmware/source/libraries/RadioLib/src/utils/Cryptography.h new file mode 100644 index 000000000..ed8f4d7a8 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/utils/Cryptography.h @@ -0,0 +1,174 @@ +#if !defined(_RADIOLIB_CRYPTOGRAPHY_H) +#define _RADIOLIB_CRYPTOGRAPHY_H + +#include "../TypeDef.h" +#include "../Module.h" + +// AES-128 constants +#define RADIOLIB_AES128_BLOCK_SIZE (16) +#define RADIOLIB_AES128_KEY_SIZE (RADIOLIB_AES128_BLOCK_SIZE) +#define RADIOLIB_AES128_N_K ((RADIOLIB_AES128_BLOCK_SIZE) / sizeof(uint32_t)) +#define RADIOLIB_AES128_N_B (4) +#define RADIOLIB_AES128_N_R (10) +#define RADIOLIB_AES128_KEY_EXP_SIZE (176) + +// helper type +typedef uint8_t state_t[4][4]; + +// AES lookup tables +static const uint8_t aesSbox[] RADIOLIB_NONVOLATILE = { + 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, + 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, + 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, + 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, + 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, + 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, + 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, + 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, + 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, + 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, + 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, + 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, + 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, + 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, + 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, + 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, + 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, + 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, + 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, + 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, + 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, + 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, + 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, + 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, + 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, + 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, + 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, + 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, + 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, + 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, + 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, + 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 +}; + +static const uint8_t aesSboxInv[] RADIOLIB_NONVOLATILE = { + 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, + 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, + 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, + 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, + 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, + 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, + 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, + 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, + 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, + 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, + 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, + 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, + 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, + 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, + 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, + 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, + 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, + 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, + 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, + 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, + 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, + 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, + 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, + 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, + 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, + 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, + 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, + 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, + 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, + 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, + 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d +}; + +static const uint8_t aesRcon[] = { 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36 }; + +/*! + \class RadioLibAES128 + Most of the implementation here is adapted from https://github.com/kokke/tiny-AES-c + Additional code and CMAC calculation is from https://github.com/megrxu/AES-CMAC + \brief Class to perform AES encryption, decryption and CMAC. +*/ +class RadioLibAES128 { + public: + /*! + \brief Default constructor. + */ + RadioLibAES128(); + + /*! + \brief Initialize the AES. + \param key AES key to use. + */ + void init(uint8_t* key); + + /*! + \brief Perform ECB-type AES encryption. + \param in Input plaintext data (unpadded). + \param len Length of the input data. + \param out Buffer to save the output ciphertext into. It is up to the caller + to ensure the buffer is sufficiently large to save the data! + \returns The number of bytes saved into the output buffer. + */ + size_t encryptECB(uint8_t* in, size_t len, uint8_t* out); + + /*! + \brief Perform ECB-type AES decryption. + \param in Input ciphertext data. + \param len Length of the input data. + \param out Buffer to save the output plaintext into. It is up to the caller + to ensure the buffer is sufficiently large to save the data! + \returns The number of bytes saved into the output buffer. + */ + size_t decryptECB(uint8_t* in, size_t len, uint8_t* out); + + /*! + \brief Calculate message authentication code according to RFC4493. + \param in Input data (unpadded). + \param len Length of the input data. + \param cmac Buffer to save the output MAC into. The buffer must be at least 16 bytes long! + */ + void generateCMAC(uint8_t* in, size_t len, uint8_t* cmac); + + /*! + \brief Verify the received CMAC. This just calculates the CMAC again and compares the results. + \param in Input data (unpadded). + \param len Length of the input data. + \param cmac CMAC to verify. + \returns True if valid, false otherwise. + */ + bool verifyCMAC(uint8_t* in, size_t len, const uint8_t* cmac); + + private: + uint8_t* keyPtr = nullptr; + uint8_t roundKey[RADIOLIB_AES128_KEY_EXP_SIZE] = { 0 }; + + void keyExpansion(uint8_t* roundKey, const uint8_t* key); + void cipher(state_t* state, uint8_t* roundKey); + void decipher(state_t* state, uint8_t* roundKey); + + void subWord(uint8_t* word); + void rotWord(uint8_t* word); + + void blockXor(uint8_t* dst, const uint8_t* a, const uint8_t* b); + void blockLeftshift(uint8_t* dst, const uint8_t* src); + void generateSubkeys(uint8_t* key1, uint8_t* key2); + + void subBytes(state_t* state, const uint8_t* box); + void shiftRows(state_t* state, bool inv); + void mixColumns(state_t* state, bool inv); + + // cppcheck seems convinced these are nut used, which is not true + uint8_t mul(uint8_t a, uint8_t b); // cppcheck-suppress unusedPrivateFunction + void addRoundKey(uint8_t round, state_t* state, const uint8_t* roundKey); // cppcheck-suppress unusedPrivateFunction +}; + +// the global singleton +extern RadioLibAES128 RadioLibAES128Instance; + +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/utils/FEC.cpp b/software/firmware/source/libraries/RadioLib/src/utils/FEC.cpp new file mode 100644 index 000000000..7aadf4ab7 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/utils/FEC.cpp @@ -0,0 +1,365 @@ +#include "FEC.h" +#include + +RadioLibBCH::RadioLibBCH() { + +} + +RadioLibBCH::~RadioLibBCH() { + #if !RADIOLIB_STATIC_ONLY + delete[] this->alphaTo; + delete[] this->indexOf; + delete[] this->generator; + #endif +} + +/* + BCH Encoder based on https://www.codeproject.com/articles/13189/pocsag-encoder + + Significantly cleaned up and slightly fixed. +*/ +void RadioLibBCH::begin(uint8_t n, uint8_t k, uint32_t poly) { + this->n = n; + this->k = k; + this->poly = poly; + #if !RADIOLIB_STATIC_ONLY + this->alphaTo = new int32_t[n + 1]; + this->indexOf = new int32_t[n + 1]; + this->generator = new int32_t[n - k + 1]; + #endif + + // find the maximum power of the polynomial + for(this->m = 0; this->m < 31; this->m++) { + if((poly >> this->m) == 1) { + break; + } + } + + /* + * generate GF(2**m) from the irreducible polynomial p(X) in p[0]..p[m] + * lookup tables: index->polynomial form this->alphaTo[] contains j=alpha**i; + * polynomial form -> index form this->indexOf[j=alpha**i] = i alpha=2 is the + * primitive element of GF(2**m) + */ + + int32_t mask = 1; + this->alphaTo[this->m] = 0; + + for(uint8_t i = 0; i < this->m; i++) { + this->alphaTo[i] = mask; + + this->indexOf[this->alphaTo[i]] = i; + + if(this->poly & ((uint32_t)0x01 << i)) { + this->alphaTo[this->m] ^= mask; + } + + mask <<= 1; + } + + this->indexOf[this->alphaTo[this->m]] = this->m; + mask >>= 1; + + for(uint8_t i = this->m + 1; i < this->n; i++) { + if(this->alphaTo[i - 1] >= mask) { + this->alphaTo[i] = this->alphaTo[this->m] ^ ((this->alphaTo[i - 1] ^ mask) << 1); + } else { + this->alphaTo[i] = this->alphaTo[i - 1] << 1; + } + + this->indexOf[this->alphaTo[i]] = i; + } + + this->indexOf[0] = -1; + + /* + * Compute generator polynomial of BCH code of length = 31, redundancy = 10 + * (OK, this is not very efficient, but we only do it once, right? :) + */ + + int32_t ii = 0; + int32_t jj = 1; + int32_t ll = 0; + int32_t kaux = 0; + bool test = false; + int32_t aux = 0; + int32_t cycle[15][6] = { { 0 } }; + int32_t size[15] = { 0 }; + + // Generate cycle sets modulo 31 + cycle[0][0] = 0; size[0] = 1; + cycle[1][0] = 1; size[1] = 1; + + do { + // Generate the jj-th cycle set + ii = 0; + do { + ii++; + cycle[jj][ii] = (cycle[jj][ii - 1] * 2) % this->n; + size[jj]++; + aux = (cycle[jj][ii] * 2) % this->n; + } while(aux != cycle[jj][0]); + + // Next cycle set representative + ll = 0; + do { + ll++; + test = false; + for(ii = 1; ((ii <= jj) && !test); ii++) { + // Examine previous cycle sets + for(kaux = 0; ((kaux < size[ii]) && !test); kaux++) { + test = (ll == cycle[ii][kaux]); + } + } + } while(test && (ll < (this->n - 1))); + + if(!test) { + jj++; // next cycle set index + cycle[jj][0] = ll; + size[jj] = 1; + } + + } while(ll < (this->n - 1)); + + // Search for roots 1, 2, ..., m-1 in cycle sets + int32_t rdncy = 0; + #if RADIOLIB_STATIC_ONLY + int32_t min[RADIOLIB_BCH_MAX_N - RADIOLIB_BCH_MAX_K + 1] = { 0 }; + #else + int32_t* min = new int32_t[this->n - this->k + 1]; + #endif + kaux = 0; + + // ensure the first element is always initializer + min[0] = 0; + + for(ii = 1; ii <= jj; ii++) { + min[kaux] = 0; + for(jj = 0; jj < size[ii]; jj++) { + for(uint8_t root = 1; root < this->m; root++) { + if(root == cycle[ii][jj]) { + min[kaux] = ii; + } + } + } + + if(min[kaux]) { + rdncy += size[min[kaux]]; + kaux++; + } + } + + int32_t noterms = kaux; + #if RADIOLIB_STATIC_ONLY + int32_t zeros[RADIOLIB_BCH_MAX_N - RADIOLIB_BCH_MAX_K + 1] = { 0 }; + #else + int32_t* zeros = new int32_t[this->n - this->k + 1]; + #endif + kaux = 1; + + // ensure the first element is always initializer + zeros[1] = 0; + + for(ii = 0; ii < noterms; ii++) { + for(jj = 0; jj < size[min[ii]]; jj++) { + zeros[kaux] = cycle[min[ii]][jj]; + kaux++; + } + } + + #if !RADIOLIB_STATIC_ONLY + delete[] min; + #endif + + // Compute generator polynomial + this->generator[0] = this->alphaTo[zeros[1]]; + this->generator[1] = 1; // g(x) = (X + zeros[1]) initially + + for(ii = 2; ii <= rdncy; ii++) { + this->generator[ii] = 1; + for(jj = ii - 1; jj > 0; jj--) { + if(this->generator[jj] != 0) { + this->generator[jj] = this->generator[jj - 1] ^ this->alphaTo[(this->indexOf[this->generator[jj]] + zeros[ii]) % this->n]; + } else { + this->generator[jj] = this->generator[jj - 1]; + } + } + this->generator[0] = this->alphaTo[(this->indexOf[this->generator[0]] + zeros[ii]) % this->n]; + } + + #if !RADIOLIB_STATIC_ONLY + delete[] zeros; + #endif +} + +/* + BCH Encoder based on https://www.codeproject.com/articles/13189/pocsag-encoder + + Significantly cleaned up and slightly fixed. +*/ +uint32_t RadioLibBCH::encode(uint32_t dataword) { + // we only use the "k" most significant bits + #if RADIOLIB_STATIC_ONLY + int32_t data[RADIOLIB_BCH_MAX_K] = { 0 }; + #else + int32_t* data = new int32_t[this->k]; + memset(data, 0, this->k*sizeof(int32_t)); + #endif + int32_t j1 = 0; + for(int32_t i = this->n; i > (this->n - this->k); i--) { + if(dataword & ((uint32_t)1<n]; + memset(Mr, 0x00, this->n*sizeof(int32_t)); + #endif + + // copy the contents of data into Mr and add the zeros + memcpy(Mr, data, this->k*sizeof(int32_t)); + + int32_t j = 0; + int32_t start = 0; + int32_t end = this->n - this->k; + while(end < this->n) { + for(int32_t i = end; i > start-2; --i) { + if(Mr[start]) { + Mr[i] ^= this->generator[j]; + ++j; + } else { + ++start; + j = 0; + end = start + this->n - this->k; + break; + } + } + } + + #if RADIOLIB_STATIC_ONLY + int32_t bb[RADIOLIB_BCH_MAX_N - RADIOLIB_BCH_MAX_K + 1] = { 0 }; + #else + int32_t* bb = new int32_t[this->n - this->k + 1]; + memset(bb, 0, (this->n - this->k + 1)*sizeof(int32_t)); + #endif + j = 0; + for(int32_t i = start; i < end; ++i) { + bb[j] = Mr[i]; + ++j; + } + + #if !RADIOLIB_STATIC_ONLY + delete[] Mr; + #endif + + int32_t iEvenParity = 0; + #if RADIOLIB_STATIC_ONLY + int32_t recd[RADIOLIB_BCH_MAX_N + 1]; + #else + int32_t* recd = new int32_t[this->n + 1]; + #endif + for(uint8_t i = 0; i < this->k; i++) { + recd[this->n - i] = data[i]; + if(data[i] == 1) { + iEvenParity++; + } + } + + #if !RADIOLIB_STATIC_ONLY + delete[] data; + #endif + + for(uint8_t i = 0; i < this->n - this->k + 1; i++) { + recd[this->n - this->k - i] = bb[i]; + if(bb[i] == 1) { + iEvenParity++; + } + } + + #if !RADIOLIB_STATIC_ONLY + delete[] bb; + #endif + + if((iEvenParity % 2) == 0) { + recd[0] = 0; + } else { + recd[0] = 1; + } + + int32_t res = 0; + for(int32_t i = 0; i < this->n + 1; i++) { + if(recd[i]) { + res |= ((uint32_t)1<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; diff --git a/software/firmware/source/libraries/RadioLib/src/utils/FEC.h b/software/firmware/source/libraries/RadioLib/src/utils/FEC.h new file mode 100644 index 000000000..7e1341248 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/utils/FEC.h @@ -0,0 +1,154 @@ +#if !defined(_RADIOLIB_FEC_H) +#define _RADIOLIB_FEC_H + +#include "../TypeDef.h" +#include "../Module.h" + +// BCH(31, 21) code constants +#define RADIOLIB_PAGER_BCH_N (31) +#define RADIOLIB_PAGER_BCH_K (21) +#define RADIOLIB_PAGER_BCH_PRIMITIVE_POLY (0x25) + +#if RADIOLIB_STATIC_ONLY +#define RADIOLIB_BCH_MAX_N (63) +#define RADIOLIB_BCH_MAX_K (31) +#endif + +/*! + \class RadioLibBCH + \brief Class to calculate Bose–Chaudhuri–Hocquenghem (BCH) class of forward error correction codes. + In theory, this should be able to calculate an arbitrary BCH(N, K) code, + but so far it was only tested for BCH(31, 21). +*/ +class RadioLibBCH { + public: + /*! + \brief Default constructor. + */ + RadioLibBCH(); + + /*! + \brief Default destructor. + */ + ~RadioLibBCH(); + + /*! + \brief Initialization method. + \param n Code word length in bits, up to 32. + \param k Data portion length in bits, up to "n". + \param poly Powers of the irreducible polynomial. + */ + void begin(uint8_t n, uint8_t k, uint32_t poly); + + /*! + \brief Encoding method - encodes one data word (without check bits) into a code word (with check bits). + \param dataword Data word without check bits. The caller is responsible to make sure the data is + on the correct bit positions! + \returns Code word with error check bits. + */ + uint32_t encode(uint32_t dataword); + + private: + uint8_t n = 0; + uint8_t k = 0; + uint32_t poly = 0; + uint8_t m = 0; + + #if RADIOLIB_STATIC_ONLY + int32_t alphaTo[RADIOLIB_BCH_MAX_N + 1] = { 0 }; + int32_t indexOf[RADIOLIB_BCH_MAX_N + 1] = { 0 }; + int32_t generator[RADIOLIB_BCH_MAX_N - RADIOLIB_BCH_MAX_K + 1] = { 0 }; + #else + int32_t* alphaTo = nullptr; + int32_t* indexOf = nullptr; + int32_t* generator = nullptr; + #endif +}; + +// the global singleton +extern RadioLibBCH RadioLibBCHInstance; + +/*! + \class RadioLibConvCode + \brief Class to perform convolutional coding with variable rates. + Only 1/2 and 1/3 rate is currently supported. + + Convolutional coder implementation in this class is adapted from Semtech'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 diff --git a/software/firmware/source/libraries/RadioLib/src/utils/Utils.cpp b/software/firmware/source/libraries/RadioLib/src/utils/Utils.cpp new file mode 100644 index 000000000..c2f12a6a1 --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/utils/Utils.cpp @@ -0,0 +1,105 @@ +#include "Utils.h" + +#include +#include +#include +#include + +uint32_t rlb_reflect(uint32_t in, uint8_t bits) { + uint32_t res = 0; + for(uint8_t i = 0; i < bits; i++) { + res |= (((in & ((uint32_t)1 << i)) >> i) << (bits - i - 1)); + } + return(res); +} + +void rlb_hexdump(const char* level, uint8_t* data, size_t len, uint32_t offset, uint8_t width, bool be) { + #if RADIOLIB_DEBUG + size_t rem_len = len; + for(size_t i = 0; i < len; i+=16) { + char str[120]; + sprintf(str, "%08" PRIx32 ": ", (uint32_t)i+offset); + size_t line_len = 16; + if(rem_len < line_len) { + line_len = rem_len; + } + for(size_t j = 0; j < line_len; j+=width) { + if(width > 1) { + int m = 0; + int step = width/2; + if(be) { + step *= -1; + } + for(int32_t k = width - 1; k >= -width + 1; k+=step) { + sprintf(&str[10 + (j+m)*3], "%02x ", data[i+j+k+m]); + m++; + } + } else { + sprintf(&str[10 + (j)*3], "%02x ", data[i+j]); + } + } + for(size_t j = line_len; j < 16; j++) { + sprintf(&str[10 + j*3], " "); + } + str[58] = ' '; + + // at this point we need to start escaping "%" characters + char* strPtr = &str[59]; + for(size_t j = 0; j < line_len; j++) { + char c = data[i+j]; + if((c < ' ') || (c > '~')) { + c = '.'; + } else if(c == '%') { + *strPtr++ = '%'; + } + sprintf(strPtr++, "%c", c); + + } + for(size_t j = line_len; j < 16; j++) { + sprintf(strPtr++, " "); + } + if(level) { + RADIOLIB_DEBUG_PRINT(level); + } + RADIOLIB_DEBUG_PRINT(str); + RADIOLIB_DEBUG_PRINTLN(); + rem_len -= 16; + } + + #else + // outside of debug, this does nothing + (void)level; + (void)data; + (void)len; + (void)offset; + (void)width; + (void)be; + + #endif +} + +#if RADIOLIB_DEBUG && defined(RADIOLIB_BUILD_ARDUINO) +// https://github.com/esp8266/Arduino/blob/65579d29081cb8501e4d7f786747bf12e7b37da2/cores/esp8266/Print.cpp#L50 +size_t rlb_printf(const char* format, ...) { + va_list arg; + va_start(arg, format); + char temp[64]; + char* buffer = temp; + size_t len = vsnprintf(temp, sizeof(temp), format, arg); + va_end(arg); + if (len > sizeof(temp) - 1) { + buffer = new char[len + 1]; + if (!buffer) { + return 0; + } + va_start(arg, format); + vsnprintf(buffer, len + 1, format, arg); + va_end(arg); + } + len = RADIOLIB_DEBUG_PORT.write(reinterpret_cast(buffer), len); + if (buffer != temp) { + delete[] buffer; + } + return len; +} +#endif diff --git a/software/firmware/source/libraries/RadioLib/src/utils/Utils.h b/software/firmware/source/libraries/RadioLib/src/utils/Utils.h new file mode 100644 index 000000000..638f2b63d --- /dev/null +++ b/software/firmware/source/libraries/RadioLib/src/utils/Utils.h @@ -0,0 +1,42 @@ +#if !defined(_RADIOLIB_UTILS_H) +#define _RADIOLIB_UTILS_H + +#include +#include + +#include "../BuildOpt.h" + +// macros to access bits in byte array, from http://www.mathcs.emory.edu/~cheung/Courses/255/Syllabus/1-C-intro/bit-array.html +#define SET_BIT_IN_ARRAY_MSB(A, k) ( A[((k)/8)] |= (1 << ((k)%8)) ) +#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 ) + +/*! + \brief Function to reflect bits within a byte. + \param in The input to reflect. + \param bits Number of bits to reflect. + \return The reflected input. +*/ +uint32_t rlb_reflect(uint32_t in, uint8_t bits); + +/*! + \brief Function to dump data as hex into the debug port. + \param level RadioLib debug level, set to NULL to not print. + \param data Data to dump. + \param len Number of bytes to dump. + \param offset Address offset to add when printing the data. + \param width Word width (1 for uint8_t, 2 for uint16_t, 4 for uint32_t). + \param be Print multi-byte data as big endian. Defaults to false. +*/ +void rlb_hexdump(const char* level, uint8_t* data, size_t len, uint32_t offset = 0, uint8_t width = 1, bool be = false); + +#if RADIOLIB_DEBUG && defined(RADIOLIB_BUILD_ARDUINO) +size_t rlb_printf(const char* format, ...); +#endif + +#endif