From d24d9d5dda5602bc4eea613f3447f8a67ff606d9 Mon Sep 17 00:00:00 2001 From: crosstyan Date: Wed, 14 May 2025 12:17:06 +0800 Subject: [PATCH] init --- .gitattributes | 1 + CMakeLists.txt | 17 + README.md | 19 + docs/.gitattributes | 1 + docs/DS_LLCC68_V1.0.md | 3082 ++++++++++++++++++++ docs/DS_LLCC68_V1.0.pdf | 3 + docs/DS_SX1261-2_V2_1.pdf | 3 + inc/hal_error.hpp | 105 + inc/hal_gpio.hpp | 33 + inc/hal_spi.hpp | 118 + inc/llcc68.hpp | 1263 ++++++++ inc/llcc68_definitions.hpp | 391 +++ inc/radiolib_definitions.hpp | 439 +++ inc/template/app_const_llcc68_template.hpp | 31 + src/hal_spi.cpp | 413 +++ src/llcc68.cpp | 36 + 16 files changed, 5955 insertions(+) create mode 100644 .gitattributes create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 docs/.gitattributes create mode 100644 docs/DS_LLCC68_V1.0.md create mode 100644 docs/DS_LLCC68_V1.0.pdf create mode 100644 docs/DS_SX1261-2_V2_1.pdf create mode 100644 inc/hal_error.hpp create mode 100644 inc/hal_gpio.hpp create mode 100644 inc/hal_spi.hpp create mode 100644 inc/llcc68.hpp create mode 100644 inc/llcc68_definitions.hpp create mode 100644 inc/radiolib_definitions.hpp create mode 100644 inc/template/app_const_llcc68_template.hpp create mode 100644 src/hal_spi.cpp create mode 100644 src/llcc68.cpp diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..b634d85 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.pdf filter=lfs diff=lfs merge=lfs -text diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..88c537a --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,17 @@ +idf_component_register( + SRCS + src/llcc68.cpp + src/hal_spi.cpp + INCLUDE_DIRS + inc/ + REQUIRES + driver + app_utils + app_constant +) + +# This option would make LLCC68 ignore `SPI_CMD_INVALID` error. +option(APP_SPI_DISABLE_INVALID_STATUS_CHECK "Disable invalid status check" ON) +if (APP_SPI_DISABLE_INVALID_STATUS_CHECK) + target_compile_definitions(${COMPONENT_LIB} PUBLIC APP_SPI_DISABLE_INVALID_STATUS_CHECK) +endif() diff --git a/README.md b/README.md new file mode 100644 index 0000000..17459bf --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +# LLCC68 driver + +in-house driver for LLCC68 that should NOT be used other than for LLCC68. +Internal buffer, C++ 20 features is used. + +## Usage + +See [app_const_llcc68_template.hpp](inc/template/app_const_llcc68_template.hpp) + +## TODO + +- [ ] Long-Range Frequency Hopping Spread Spectrum (LR-FHSS) +- [ ] cleanup interface for public use + +## See + +- [Lora-net/sx126x_driver](https://github.com/Lora-net/sx126x_driver) +- [Lora-net/SWDM001](https://github.com/Lora-net/SWDM001) +- [jgromes/RadioLib](https://github.com/jgromes/RadioLib/tree/master/src/modules/SX126x) diff --git a/docs/.gitattributes b/docs/.gitattributes new file mode 100644 index 0000000..56c128f --- /dev/null +++ b/docs/.gitattributes @@ -0,0 +1 @@ +*.{pdf,PDF} filter=lfs diff=lfs merge=lfs -text \ No newline at end of file diff --git a/docs/DS_LLCC68_V1.0.md b/docs/DS_LLCC68_V1.0.md new file mode 100644 index 0000000..ee92119 --- /dev/null +++ b/docs/DS_LLCC68_V1.0.md @@ -0,0 +1,3082 @@ +## LLCC68 + +Long Range, Low Power, sub-GHz RF Transceiver + +## General Description + +## Applications + +The LLCC68 sub-GHz radio transceiver is ideal for long range wireless applications. It is designed for long battery life with just 4.2 mA of active receive current consumption. The LLCC68 can transmit up to +22 dBm with highly efficient integrated power amplifiers. + +The LLCC68 supports LoRa® modulation for LPWAN use cases and (G)FSK modulation for legacy use cases. It is highly configurable to meet different application requirements for industry and consumer use. + +The device provides LoRa® modulation compatible with Semtech transceivers used by the LoRaWAN™ specification released by the LoRa Alliance™. + +The radio is suitable for systems targeting compliance with radio regulations including but not limited to ETSI EN 300 220, FCC CFR 47 Part 15, China regulatory requirements and the Japanese ARIB T-108. Continuous frequency coverage from 150 MHz to 960 MHz allows the support of all major sub-GHz ISM bands around the world. + +The level of integration and the low consumption of the LLCC68 enable a new generation of Internet of Things applications. + +- Smart meters +- Supply chain and logistics +- Building automation +- Agricultural sensors +- Smart cities +- Retail store sensors +- Asset tracking +- Street lights +- Parking sensors +- Environmental sensors +- Healthcare +- Safety and security sensors +- Remote control applications + +## Ordering Information + +| Part Number | Delivery | Minimum Order Quantity | +| ------------ | --------------- | ---------------------- | +| LLCC68IMLTRT | Tape & Reel | 3'000 pieces | + +QFN 24 Package, Pb-free, Halogen free, RoHS/WEEE compliant product. + + +## Table of Contents + +- 1 Architecture +- 2 Pin Connection + - 2.1 I/O Description + - 2.2 Package View +- 3 Specifications + - 3.1 ESD Notice + - 3.2 Absolute Maximum Ratings + - 3.3 Operating Range + - 3.4 Crystal Specifications + - 3.5 Electrical Specifications + - 3.5.1 Power Consumption + - 3.5.2 General Specifications + - 3.5.3 Receive Mode Specifications + - 3.5.4 Transmit Mode Specifications + - 3.5.5 Digital I/O Specifications +- 4 Circuit Description + - 4.1 Clock References + - 4.1.1 RC Frequency References + - 4.1.2 High-Precision Frequency Reference + - 4.1.3 XTAL Control Block + - 4.1.4 TCXO Control Block + - 4.2 Phase-Locked Loop (PLL) + - 4.3 Receiver + - 4.3.1 Intermediate Frequencies + - 4.4 Transmitter + - 4.4.1 Power Amplifier Specifics + - 4.4.2 Power Amplifier Summary +- 5 Power Supply Considerations + - 5.1 Selecting DC-DC Converter or LDO Regulation + - 5.1.1 Option A: DC-DC Regulator + - 5.1.2 Option B: LDO Regulator + - 5.1.3 Consideration on the DC-DC Inductor Selection + - 5.2 Flexible DIO Supply +- 6 Modems + - 6.1 LoRa® Modem + - 6.1.1 Modulation Parameter + - 6.1.2 LoRa® Packet Engine + - 6.1.3 LoRa® Frame + - 6.1.4 LoRa® Time-on-Air + - 6.1.5 LoRa® Channel Activity Detection (CAD) + - 6.2 FSK Modem + - 6.2.1 Modulation Parameter + - 6.2.2 FSK Packet Engine + - 6.2.3 FSK Packet Format +- 7 Data Buffer + - 7.1 Principle of Operation + - 7.2 Data Buffer in Receive Mode + - 7.3 Data Buffer in Transmit Mode + - 7.4 Using the Data Buffer +- 8 Digital Interface and Control + - 8.1 Reset + - 8.2 SPI Interface + - 8.2.1 SPI Timing When the Transceiver is in Active Mode + - 8.2.2 SPI Timing When the Transceiver Leaves Sleep Mode + - 8.3 Multi-Purpose Digital Input/Output (DIO) + - 8.3.1 BUSY Control Line + - 8.4 Digital Interface Status versus Chip Modes + - 8.5 IRQ Handling +- 9 Operational Modes + - 9.1 Startup + - 9.2 Calibration + - 9.2.1 Image Calibration for Specific Frequency Bands + - 9.3 Sleep Mode + - 9.4 Standby (STDBY) Mode + - 9.5 Frequency Synthesis (FS) Mode + - 9.6 Receive (RX) Mode + - 9.7 Transmit (TX) Mode + - 9.7.1 PA Ramping + - 9.8 Active Mode Switching Time + +## List of Figures + +- Figure 2-1: LLCC68 Top View Pin Location QFN 4x4 24L +- Figure 4-1: LLCC68 Block Diagram +- Figure 4-2: TCXO Control Block +- Figure 4-3: VR_PA versus Output Power on the LLCC68 +- Figure 4-4: Power Linearity on the LLCC68 +- Figure 4-5: Current versus Programmed Output Power on the LLCC68 +- Figure 5-1: LLCC68 Diagram with the DC-DC Regulator Power Option +- Figure 5-2: LLCC68 Diagram with the LDO Regulator Power Option +- Figure 5-3: Separate DIO Supply +- Figure 6-1: LoRa® Signal Bandwidth +- Figure 6-2: LoRa® Packet Format +- Figure 6-3: Fixed-Length Packet Format +- Figure 6-4: Variable-Length Packet Format +- Figure 6-5: Data Whitening LFSR +- Figure 7-1: Data Buffer Diagram +- Figure 8-1: SPI Timing Diagram +- Figure 8-2: SPI Timing Transition +- Figure 8-3: Switching Time Definition +- Figure 8-4: Switching Time Definition in Active Mode +- Figure 9-1: Transceiver Circuit Modes +- Figure 13-1: Stopping Timer on Preamble or Header Detection +- Figure 13-2: RX Duty Cycle Energy Profile +- Figure 13-3: RX Duty Cycle when Receiving +- Figure 14-1: Application Schematic of the LLCC68 with RF Switch +- Figure 16-1: QFN 4x4 Package Outline Drawing +- Figure 16-2: LLCC68 Marking +- Figure 16-3: QFN 4x4mm Land Pattern + +## List of Tables + +- Table 2-1: LLCC68 Pinout in QFN 4x4 24L +- Table 3-1: ESD and Latch-up Notice +- Table 3-2: Absolute Maximum Ratings +- Table 3-3: Operating Range +- Table 3-4: Crystal Specifications +- Table 3-5: Power Consumption +- Table 3-6: Power Consumption in Transmit Mode +- Table 3-7: General Specifications +- Table 3-8: Receive Mode Specifications +- Table 3-9: Transmit Mode Specifications +- Table 3-10: Digital I/O Specifications +- Table 4-1: Internal Foot Capacitor Configuration +- Table 4-2: Intermediate Frequencies in FSK Mode +- Table 4-3: Intermediate Frequencies in LoRa® Mode +- Table 4-4: Power Amplifier Summary +- Table 5-1: Regulation Type versus Circuit Mode +- Table 5-2: OCP Configuration +- Table 5-3: Recommended 15 µH Inductors +- Table 6-1: Range of Spreading Factors (SF) +- Table 6-2: Signal Bandwidth Setting in LoRa® Mode +- Table 6-4: Bandwidth Definition in FSK Packet Type +- Table 6-5: Whitening Initial Value +- Table 6-6: CRC Type Configuration +- Table 6-7: CRC Initial Value +- Table 6-8: CRC Polynomial +- Table 8-1: SPI Timing Requirements +- Table 8-2: Switching Time +- Table 8-3: Digital Pads Configuration for Each Chip Mode +- Table 8-4: IRQ Status Registers +- Table 9-1: LLCC68 Operating Modes +- Table 9-2: Image Calibration Over the ISM Bands +- Table 9-3: Rx Gain Configuration +- Table 10-1: SPI Interface Command Sequence +- Table 11-1: Commands Selecting the Operating Modes of the Radio +- Table 11-2: Commands to Access the Radio Registers and FIFO Buffer +- Table 11-3: Commands Controlling the Radio IRQs and DIOs +- Table 11-4: Commands Controlling the RF and Packets Settings +- Table 11-5: Commands Returning the Radio Status +- Table 12-1: List of Registers +- Table 13-1: SetSleep SPI Transaction +- Table 13-2: Sleep Mode Definition +- Table 13-3: SetConfig SPI Transaction +- Table 13-4: STDBY Mode Configuration +- Table 13-5: SetFs SPI Transaction +- Table 13-6: SetTx SPI Transaction +- Table 13-7: SetTx Timeout Duration +- Table 13-8: SetRx SPI Transaction +- Table 13-9: SetRx Timeout Duration +- Table 13-10: StopTimerOnPreamble SPI Transaction +- Table 13-11: StopOnPreambParam Definition +- Table 13-12: SetRxDutyCycle SPI Transaction +- Table 13-13: SetCAD SPI Transaction +- Table 13-14: SetTxContinuousWave SPI Transaction +- Table 13-15: SetTxInfinitePreamble SPI Transaction +- Table 13-16: SetRegulatorMode SPI Transaction +- Table 13-17: Calibrate SPI Transaction +- Table 13-18: Calibration Setting +- Table 13-19: CalibrateImage SPI Transaction +- Table 13-20: SetPaConfig SPI Transaction +- Table 13-21: PA Operating Modes with Optimal Settings +- Table 13-22: SetRxTxFallbackMode SPI Transaction +- Table 13-24: WriteRegister SPI Transaction +- Table 13-25: ReadRegister SPI Transaction +- Table 13-26: WriteBuffer SPI Transaction +- Table 13-27: ReadBuffer SPI Transaction +- Table 13-28: SetDioIrqParams SPI Transaction +- Table 13-29: IRQ Registers +- Table 13-30: GetIrqStatus SPI Transaction +- Table 13-31: ClearIrqStatus SPI Transaction +- Table 13-32: SetDIO2AsRfSwitchCtrl SPI Transaction +- Table 13-33: Enable Configuration Definition +- Table 13-34: SetDIO3asTCXOCtrl SPI Transaction +- Table 13-35: tcxoVoltage Configuration Definition +- Table 13-36: SetRfFrequency SPI Transaction +- Table 13-37: SetPacketType SPI Transaction +- Table 13-38: PacketType Definition +- Table 13-39: GetPacketType SPI Transaction +- Table 13-40: SetTxParams SPI Transaction +- Table 13-41: RampTime Definition +- Table 13-42: SetModulationParams SPI Transaction +- Table 13-43: GFSK ModParam1, ModParam2 & ModParam3 - br +- Table 13-44: GFSK ModParam4 - PulseShape +- Table 13-45: GFSK ModParam5 - Bandwidth +- Table 13-46: GFSK ModParam6, ModParam7 & ModParam8 - Fdev +- Table 13-47: LoRa® ModParam1- SF +- Table 13-48: LoRa® ModParam2 - BW +- Table 13-49: LoRa® ModParam3 - CR +- Table 13-50: LoRa® ModParam4 - LowDataRateOptimize +- Table 13-51: SetPacketParams SPI Transaction +- Table 13-52: GFSK PacketParam1 & PacketParam2 - PreambleLength +- Table 13-53: GFSK PacketParam3 - PreambleDetectorLength +- Table 13-54: GFSK PacketParam4 - SyncWordLength +- Table 13-55: Sync Word Programming +- Table 13-56: GFSK PacketParam5 - AddrComp +- Table 13-57: Node Address Programming +- Table 13-58: Broadcast Address Programming +- Table 13-59: GFSK PacketParam6 - PacketType +- Table 13-60: GFSK PacketParam7 - PayloadLength +- Table 13-61: GFSK PacketParam8 - CRCType +- Table 13-62: CRC Initial Value Programming +- Table 13-63: CRC Polynomial Programming +- Table 13-64: GFSK PacketParam9 - Whitening +- Table 13-65: Whitening Initial Value +- Table 13-66: LoRa® PacketParam1 & PacketParam2 - PreambleLength +- Table 13-67: LoRa® PacketParam3 - HeaderType +- Table 13-68: LoRa® PacketParam4 - PayloadLength +- Table 13-69: LoRa® PacketParam5 - CRCType +- Table 13-70: LoRa® PacketParam6 - InvertIQ +- Table 13-71: SetCadParams SPI Transaction +- Table 13-72: CAD Number of Symbol Definition +- Table 13-73: CAD Exit Mode Definition +- Table 13-74: SetBufferBaseAddress SPI Transaction +- Table 13-75: SetLoRaSymbNumTimeout SPI Transaction +- Table 13-76: Status Bytes Definition +- Table 13-77: GetStatus SPI Transaction +- Table 13-78: GetRxBufferStatus SPI Transaction +- Table 13-79: GetPacketStatus SPI Transaction +- Table 13-80: Status Fields +- Table 13-81: GetRssiInst SPI Transaction +- Table 13-82: GetStats SPI Transaction +- Table 13-83: ResetStats SPI Transaction +- Table 13-84: GetDeviceErrors SPI Transaction +- Table 13-85: OpError Bits +- Table 13-86: ClearDeviceErrors SPI Transaction + +## 1. Architecture + +The LLCC68 is a half-duplex transceiver capable of low power operation in the 150-960 MHz ISM frequency band. The radio comprises four main blocks: + +- 1. Analog Front End : the transmit and receive chains, as well as the data converter interface to ensuing digital blocks. The LLCC68 transceiver is capable of delivering up to +22 dBm under the battery supply. +- 2. Digital Modem Bank : a range of modulation options is available in the LLCC68: +-  LoRa® Rx/Tx, BW = 125 -250 - 500 kHz +-  LoRa® SF = 5 - 6 - 7 - 8 - 9 for BW = 125 kHz +-  LoRa® SF = 5 - 6 - 7 - 8 - 9 - 10 for BW = 250 kHz +-  LoRa® SF = 5 - 6 - 7 - 8 - 9 - 10 - 11 for BW = 500 kHz +-  (G)FSK Rx/Tx, with BR = 0.6 - 300 kb/s +- 3. Digital Interface and Control : this comprises all payload data and protocol processing as well as access to configuration of the radio via the SPI interface. +- 4. Power Distribution : two forms of voltage regulation, DC-DC or linear regulator LDO, are available depending upon the design priorities of the application. + +## 2. Pin Connection + +## 2.1 I/O Description + +Table 2-1: LLCC68 Pinout in QFN 4x4 24L + +| Pin Number | Pin Name | Type (I = input O = Output) | Description | +| ----------- | -------- | --------------------------- | ---------------------------------------------------------------------------- | +| 0 | GND | - | Exposed Ground pad | +| 1 | VDD\_IN | I | Input voltage for power amplifier regulator, VR\_PA connected to pin 10 | +| 2 | GND | - | Ground | +| 3 | XTA | - | Crystal oscillator connection, can be used to input external reference clock | +| 4 | XTB | - | Crystal oscillator connection | +| 5 | GND | - | Ground | +| 6 | DIO3 | I/O | Multi-purpose digital I/O - external TCXO supply voltage | +| 7 | VREG | O | Regulated output voltage from the internal regulator LDO / DC-DC | +| 8 | GND | - | Ground | +| 9 | DCC\_SW | O | DC-DC Switcher Output | +| 10 | VBAT | I | Supply for the RFIC | +| 11 | VBAT\_IO | I | Supply for the Digital I/O interface pins (except DIO3) | +| 12 | DIO2 | I/O | Multi-purpose digital I/O / RF Switch control | +| 13 | DIO1 | I/O | Multi-purpose digital IO | +| 14 | BUSY | O | Busy indicator | +| 15 | NRESET | I | Reset signal, active low | +| 16 | MISO | O | SPI slave output | +| 17 | MOSI | I | SPI slave input | +| 18 | SCK | I | SPI clock | +| 19 | NSS | I | SPI Slave Select | +| 20 | GND | - | Ground | +| 21 | RFI\_P | I | RF receiver input | +| 22 | RFI\_N | I | RF receiver input | +| 23 | RFO | O | RF transmitter output | +| 24 | VR\_PA | - | Regulated power amplifier supply | + +## 2.2 Package View + +Figure 2-1: LLCC68 Top View Pin Location QFN 4x4 24L + + + +## 3. Specifications + +## 3.1 ESD Notice + + + +The LLCC68 transceiver is a high-performance radio frequency device, with high ESD and latch-up resistance. The chip should be handled with all the necessary ESD precautions to avoid any permanent damage. + +Table 3-1: ESD and Latch-up Notice + +| Symbol | Description | Min | Typ | Max | Unit | +| -------- | ------------------------------------------------------------------ | --- | --- | ---- | ---- | +| ESD\_HBM | Class 2 of ANSI/ESDA/JEDEC Standard JS-001-2014 (Human Body Model) | - | - | 2 | kV | +| ESD\_CDM | ESD Charged Device Model, JEDEC standard JESD22-C101D, class III | - | - | 1000 | V | +| LU | Latch-up, JEDEC standard JESD78 B, class I level A | - | - | 100 | mA | + +## 3.2 Absolute Maximum Ratings + +Stresses above the values listed below may cause permanent device failure. Exposure to absolute maximum ratings for extended periods may affect device reliability, reducing product life time. + +Table 3-2: Absolute Maximum Ratings + +| Symbol | Description | Min | Typ | Max | Unit | +| ------ | -------------------------------------------- | ---- | --- | --- | ---- | +| VDDmr | Supply voltage, applies to VBAT and VBAT\_IO | -0.5 | - | 3.9 | V | +| Tmr | Temperature | -55 | - | 125 | °C | +| Pmr | RF Input level | - | - | 10 | dBm | + +## 3.3 Operating Range + +Operating ranges define the limits for functional operation and parametric characteristics of the device. Functionality outside these limits is not guaranteed. + +Table 3-3: Operating Range + +| Symbol | Description | Min | Typ | Max | Unit | +| ------ | -------------------------------------------- | --- | --- | ---- | ---- | +| VDDop | Supply voltage, applies to VBAT and VBAT\_IO | 1.8 | - | 3.7 | V | +| Top | Temperature under bias | -40 | - | 85 | °C | +| Clop | Load capacitance on digital ports | - | - | 20 | pF | +| ML | RF Input power | - | - | 0 | dBm | +| VSWR | Voltage Standing Wave Ratio at antenna port | - | - | 10:1 | - | + +## 3.4 Crystal Specifications + +Table 3-4: Crystal Specifications + +| Symbol | Description | Min | Typ | Max | Unit | +| ------ | ---------------------------- | --- | ---- | --- | ---- | +| FXOSC | Crystal oscillator frequency | - | 32 | - | MHz | +| CLOAD | Crystal load capacitance | - | 10 | - | pF | +| C0XTAL | Crystal shunt capacitance | 0.3 | 0.6 | 2 | pF | +| RSXTAL | Crystal series resistance | - | 30 | 60 |  | +| CMXTAL | Crystal motional capacitance | 1.3 | 1.89 | 2.5 | fF | +| DRIVE | Drive level | - | - | 100 |  W | + +The reference frequency accuracy is defined by the complete system. The initial error, temperature drift, and ageing drift of both the transmitter and the receiver, over the lifetime of the product, should be taken into account in the calculations. + +## 3.5 Electrical Specifications + +The electrical specifications are given with the following conditions unless otherwise specified: + +- · VBAT\_IO = VBAT = 3.3 V, all current consumptions are given for VBAT connected to VBAT\_IO +- · Temperature = 25 °C +- · FXOSC = 32 MHz, with specified crystal +- · FRF = 434/490/868/915 MHz +- · All RF impedances matched +- · Transmit mode output power defined into 50  load impedance +- · FSK BER = 0.1%, 2-level FSK modulation without pre-filtering, BR = 4.8 kb/s, FDA = ± 5 kHz, BW\_F = 20 kHz double-sided +- · LoRa® PER = 1%, packet 64 bytes, preamble 8 symbols, CR = 4/5, CRC on payload enabled, explicit header mode +- · RX/TX specifications given using default RX gain step and direct tie connection between Rx and Tx +- · Blocking immunity, ACR and co-channel rejection are given for a single tone (CW) interferer and referenced to sensitivity +3 dB +- · Optional TCXO and RF Switch power consumption always excluded + +## Caution! + +Throughout this document, all receiver bandwidths are expressed as 'double-sideband'. This is valid for LoRa® and FSK modulations. + +## 3.5.1 Power Consumption + +Table 3-5: Power Consumption + +| Symbol | Mode | Conditions | Min | Typ | Max | Unit | +| ------ | ------------------------------------------- | ------------------------------------------------------ | --- | ------- | --- | ------ | +| IDDOFF | OFF mode (SLEEP mode with cold start ) 1 | All blocks off | - | 160 | - | nA | +| IDDSL | SLEEP mode (SLEEP mode with warm start 2 ) | Configuration retained Configuration retained + RC64k | - - | 600 1.2 | - - | nA  A | +| IDDSBR | STDBY\_RC mode | RC13M, XOSC OFF | - | 0.6 | - | mA | +| IDDSBX | STDBY\_XOSC mode | XOSC ON | - | 0.8 | - | mA | +| | Synthesizer mode | DC-DC mode used | - | 2.1 | - | mA | +| IDDFS | | LDO mode used FSK 4.8 kb/s | - | 3.55 | - | mA | +| | Receive mode | LoRa® 125 kHz | - - | 4.2 4.6 | - - | mA | +| | | Rx Boosted 3 , FSK 4.8 kb/s | - | | - | mA mA | +| | DC-DC mode used | | - | 4.8 | - | mA | +| | | Rx Boosted, LoRa® 125 kHz | | 5.3 | | | +| IDDRX | | LoRa® 125 kHz, VBAT = 1.8 V | - | 8.2 | - | mA | +| | Receive mode | LoRa® 125 kHz | - | 8.8 | - | mA | +| | LDO mode used | Rx Boosted, FSK 4.8 kb/s | - | 9.3 | - | mA | +| | | Rx Boosted, LoRa® 125 kHz | - | 10.1 | - | mA | + +- 1. Cold start is equivalent to the device at POR or when the device is waking up from Sleep mode with all blocks OFF, see Section 13.1.1 "SetSleep" on page 62 +- 2. Warm start is only happening when device is woken from Sleep mode with its configuration retained, see Section 13.1.1 "SetSleep" on page 62 +- 3. For more details on how to set the device in Rx Boosted gain mode, see Section 9.6 "Receive (RX) Mode" on page 53 + +Table 3-6: Power Consumption in Transmit Mode + +| Symbol | Frequency Band | PA Match / Condition | Power Output | Typical | Unit | +| ------- | -------------- | ----------------------------- | ------------ | ------- | ---- | +| IDDTX 1 | +22 dBm | | +22 dBm | 118 | mA | +| IDDTX 1 | +22 dBm | | +20 dBm | 102 | mA | +| IDDTX 1 | +22 dBm | +22 dBm | +17 dBm | 95 | mA | +| IDDTX 1 | +22 dBm | | +14 dBm | 90 | mA | +| IDDTX 1 | +22 dBm | +20 dBm / optimal settings 2 | +20 dBm | 84 | mA | +| IDDTX 1 | +22 dBm | +17 dBm / optimal settings 2 | +17 dBm | 58 | mA | +| IDDTX 1 | +22 dBm | +14 dBm / optimal settings 2 | +14 dBm | 45 | mA | +| IDDTX 1 | 434/490 MHz | | +22 dBm | 107 | mA | +| IDDTX 1 | 434/490 MHz | | +20 dBm | 90 | mA | +| IDDTX 1 | 434/490 MHz | | +17 dBm | 75 | mA | +| IDDTX 1 | 434/490 MHz | | +14 dBm | 63 | mA | +| IDDTX 1 | 434/490 MHz | +20 dBm / optimal settings 2 | +20 dBm | 65 | mA | +| IDDTX 1 | 434/490 MHz | +17 dBm / optimal settings 2 | +17 dBm | 42 | mA | +| IDDTX 1 | 434/490 MHz | +14 dBm / optimal settings 2 | +14 dBm | 32 | mA | + +- 1. DC-DC mode is used for the IC core but the PA is supplied from VBAT. For more details, see Section 5.1 "Selecting DC-DC Converter or LDO Regulation" on page 29 +- 2. Optimal settings adapted to the specified output power. For more details, see Section 13.1.14.1 "PA Optimal Settings" on page 71 + +## 3.5.2 General Specifications + +Table 3-7: General Specifications + +| Symbol | Description | Conditions | Min | Typ | Max | Unit | +| ------------ | ----------------------------------------------------------------- | ------------------------------------------------------------------- | ----- | ----- | -------- | ------ | +| FR | Synthesizer frequency range | - | 150 | - | 960 | MHz | +| | | Bit 2 of TxModulation = 1 (main use) | - | 0.95 | - | Hz | +| FSTEP | Synthesizer frequency step | Bit 2 of TxModulation = 0 (see Section 15.) | - | 122 | - | Hz | +| | | 1 kHz offset | - | -75 | - | dBc/Hz | +| | | 10 kHz offset | - | -95 | - | dBc/Hz | +| PHN 1 2 | Synthesizer phase noise | 100 kHz offset | - | -100 | - | dBc/Hz | +| | (for 868 / 915 MHz) | 1MHz offset | - | -120 | - | dBc/Hz | +| | | 10 MHz offset | - | -135 | - | dBc/Hz | +| TS\_FS | Synthesizer wake-up time | From STDBY\_XOSC mode | - | 40 | - |  s | +| TS\_OSC | Crystal oscillator wake-up time | from STDBY\_RC 3 | - | 150 | - |  s | +| OSC\_TRM | Crystal oscillator trimming range for crystal frequency | | +/-15 | +/-30 | - | ppm | +| BR\_F | Bit rate, FSK | Programmable Minimum modulation index is 0.5 | 0.6 | - | 300 5 | kb/s | +| FDA | Frequency deviation, FSK | Programmable FDA + BR\_F / 2  250 kHz | 0.6 | - | 200 | kHz | +| BR\_L | Bit rate LoRa® | Min. for SF9, BW\_L = 125 kHz Max. for SF5, BW\_L = 500 kHz | 1.76 | - | 62.5 6 | kb/s | +| BW\_L | Signal BW, LoRa® | Programmable | 125 | - | 500 6 | kHz | +| SF | Spreading factor for LoRa® | Programmable, chips/symbol = 2^SF | 5 | - | 9 7 10 8 | - | +| | | Min/Max values in typical conditions, Typ value for default setting | | | 11 9 | | +| VTCXO ILTCXO | Regulated voltage range for TCXO voltage supply Load current for | VDDop > VTCXO + 200 mV | 1.6 | 1.7 | 3.3 | V mA | +| TSVTCXO | TCXO regulator Start-up time for TCXO regulator | From enable to regulated voltage within 25 mV from target | - - | 1.5 - | 4 100 |  s | + +## Table 3-7: General Specifications + +Table 3-8: Receive Mode Specifications + +| Symbol | Description | Conditions | Min | Typ | Max | Unit | +| ------- | ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | --- | --- | ---- | ------ | +| IDDTCXO | Current consumption of the TCXO regulator | Quiescent current Relative to load current | - - | - 1 | 70 2 |  A % | +| ATCXO | Amplitude voltage for external TCXO applied to XTA pin | provided through a 220  resistor in series with a 10 pF capacitance See Section 4.1.4 "TCXO Control Block" on page 24 | 0.4 | 0.6 | 1.2 | Vpk-pk | + +- 1. Phase Noise specifications are given for the recommended PLL BW to be used for the specific modulation/BR, optimized settings may be used for specific applications +- 2. Phase Noise is not constant over frequency, due to the topology of the PLL, for two frequencies close to each other, the phase noise could change significantly +- 3. Wake-up time till crystal oscillator frequency is within +/- 10 ppm +- 4. OSC\_TRIM is the available trimming range to compensate for crystal initial frequency error and to allow crystal temperature compensation implementation; the total available trimming range is higher and allows the compensation for all IC process variations +- 5. Maximum bit rate is assumed to scale with the RF frequency; for example 300 kb/s in the 869/915 MHz frequency bands and only 50 kb/s at 150 MHz +- 6. For RF frequencies below 400 MHz, there is a scaling between the frequency and supported BW, some BW may not be available below 400 MHz +- 7. LoRa BW = 125 kHz + +8. LoRa BW = 250 kHz + +- 9. LoRa BW = 500 kHz + +## 3.5.3 Receive Mode Specifications + +| Symbol | Description | Conditions | Min | Typ | Max | Unit | +| -------- | ----------------------------------------------------------------------------------- | ------------------------------------------------ | --- | ---- | --- | ---- | +| | Sensitivity 2-FSK, | BR\_F = 0.6 kb/s, FDA = 0.8 kHz, BW\_F = 4 kHz | - | -125 | - | dBm | +| | RX Boosted gain, see Section 9.6 | BR\_F = 1.2 kb/s, FDA = 5 kHz, BW\_F = 20 kHz | - | -123 | - | dBm | +| RXS\_2FB | "Receive (RX) Mode" on page 53, | BR\_F = 4.8 kb/s, FDA = 5 kHz, BW\_F = 20 kHz | - | -118 | - | dBm | +| | split RF paths for Rx and Tx, RF | BR\_F = 38.4 kb/s, FDA = 40 kHz, BW\_F = 160 kHz | - | -109 | - | dBm | +| | switch insertion loss excluded | BR\_F = 250 kb/s, FDA = 125 kHz, BW\_F = 500 kHz | - | -104 | - | dBm | +| RXS\_LB | | BW\_L = 125 kHz, SF = 7 | - | -124 | - | dBm | +| RXS\_LB | Sensitivity LoRa®, | BW\_L = 125 kHz, SF = 9 | - | -129 | - | dBm | +| RXS\_LB | Rx Boosted gain, see Section 9.6 | BW\_L = 250 kHz, SF = 7 | - | -121 | - | dBm | +| RXS\_LB | "Receive (RX) Mode" on page 53, | BW\_L = 250 kHz, SF = 10 | - | -129 | - | dBm | +| RXS\_LB | split RF paths for Rx and Tx, RF switch insertion loss excluded | BW\_L = 500 kHz, SF = 7 | - | -117 | - | dBm | +| RXS\_LB | | BW\_L = 500 kHz, SF = 11 | - | -127 | - | dBm | +| RXS\_2F | Sensitivity 2-FSK Rx Power Saving gain with direct tie connection between Rx and Tx | BR\_F = 4.8 kb/s, FDA = 5 kHz, BW\_F = 20 kHz | - | -115 | - | dBm | +| RXS\_L | Sensitivity LoRa® Rx Power Saving gain with direct tie connection between Rx and Tx | BW\_L = 125 kHz, SF = 9 | - | -125 | - | dBm | + +Table 3-8: Receive Mode Specifications + +| Symbol | Description | Conditions | Min | Typ | Max | Unit | +| ------- | ----------------------------------------------------------- | ------------------------------------------------ | ---- | ---- | --- | ---- | +| CCR\_F | Co-channel rejection, FSK | | - | -9 | - | dB | +| | | SF = 7 | - | 5 | - | dB | +| CCR\_L | Co-channel rejection, LoRa® | SF = 11 | - | 16 | - | dB | +| ACR\_F | Adjacent channel rejection, FSK | Offset = +/- 50 kHz | - | 45 | - | dB | +| ACR\_L | Adjacent channel rejection, LoRa® | Offset = +/- 1.5 x BW\_L BW\_L = 125 kHz, SF = 7 | - | 60 | - | dB | +| | | BW\_L = 125 kHz, SF = 9 | - | 65 | - | dB | +| | | BR\_F = 4.8 kb/s, FDA = 5 kHz, BW\_F = 20 kHz | | | | | +| BI\_F | | Offset = +/- 1 MHz | - | 68 | - | dB | +| | Blocking immunity, FSK | Offset = +/- 2 MHz | - | 70 | - | dB | +| | | Offset = +/- 10 MHz | - | 80 | - | dB | +| | | BW\_L = 125 kHz, SF =9 | | | | | +| | | Offset = +/- 1 MHz | - | 80 | - | dB | +| BI\_L | Blocking immunity, LoRa® | Offset = +/- 2 MHz | - | 82 | - | dB | +| | | Offset = +/- 10 MHz | - | 91 | - | dB | +| | | Unwanted tones are 1 MHz and | | | | | +| IIP3 | 3rd order input intercept point | 1.96 MHz above LO | - | -5 | - | dBm | +| | | Without IQ calibration | - | 35 | - | dB | +| IMA | Image attenuation | With IQ calibration | - | 54 | - | dB | +| BW\_F | DSB channel filter BW, FSK | Programmable, typical values | 4.8 | - | 467 | kHz | +| TS\_RX | Receiver wake-up time | FS to RX | - | 41 | - |  s | +| FERR\_L | Maximum tolerated frequency offset between transmitter and | All bandwidths, ±25% of BW | | | | | +| | receiver, no sensitivity | The tighter limit applies (see below) | | ±25% | | BW | +| | Maximum tolerated frequency | | | | | | +| | offset between transmitter and | SF11 | -100 | - | 100 | ppm | +| | receiver, no sensitivity degradation, SF10 to SF11 | SF10 | -200 | - | 200 | ppm | + +## 3.5.4 Transmit Mode Specifications + +## Table 3-9: Transmit Mode Specifications + +Table 3-10: Digital I/O Specifications + +| Symbol | Description | Conditions | Min | Typ | Max | Unit | +| ------ | ------------------------------------------- | ------------------------------------------- | --- | --- | --- | ----- | +| TXOP | Maximum RF output power | Highest power step setting | - | +22 | - | dBm | +| TXDRP | RF output power drop versus supply voltage | +22 dBm, VBAT = 2.7 V +22 dBm, VBAT = 2.4 V | - - | 2 3 | - - | dB dB | + +## 3.5.5 Digital I/O Specifications + +| Symbol | Description | Conditions | Min | Typ | Max | Unit | +| ------ | ---------------------------------------------- | ---------------- | --------------- | --- | ---------------- | ---- | +| VIH | Input High Voltage | - | 0.7*VBAT\_IO 1 | - | VBAT\_IO 1 +0.3 | V | +| VIL | Input Low Voltage | - | -0.3 | - | 0.3*VBAT\_IO 1 | V | +| VIL\_N | Input Low Voltage for pin NRESET | - | -0.3 | - | 0.2*VBAT | V | +| VOH | Output High Voltage | I max = -2.5 mA | 0.9*VBAT\_IO 1 | - | VBAT\_IO 1 | V | +| VOL | Output Low Voltage | I max = 2.5 mA | 0 | - | 0.1*VBAT\_IO 1 | V | +| Ileak | Digital input leakage current (NSS, MOSI, SCK) | - | -1 | - | 1 |  A | + +1. excluding following pins: NRESET and DIO3, which are referred to VBAT + +## 4. Circuit Description + +Figure 4-1: LLCC68 Block Diagram + + + +LLCC68 is a half-duplex RF transceiver operating in the sub-GHz frequency bands and can handle constant envelope modulations schemes such as LoRa® or FSK. + +## 4.1 Clock References + +## 4.1.1 RC Frequency References + +Two RC oscillators are available: 64 kHz and 13 MHz RC oscillators. The 64 kHz RC oscillator (RC64k) is optionally used by the circuit in SLEEP mode to wake-up the transceiver when performing periodic or duty cycled operations. Several commands make use of this 64 kHz RC oscillator (called RTC across this document) to generate time-based events. The 13 MHz RC oscillator (RC13M) is enabled for all SPI communication to permit configuration of the device without the need to start the crystal oscillator. Both RC oscillators are supplied directly from the battery. + +## 4.1.2 High-Precision Frequency Reference + +In the LLCC68 the high-precision frequency reference can come either from an on-chip crystal oscillator (OSC) using an external crystal resonator or from an external TCXO (Temperature Compensated Crystal Oscillator), supplied by an internal regulator. + +The LLCC68 comes in a small form factor 4 x 4 mm QFN package and is able to transmit up to +22 dBm. When in transmit mode the circuit may heat up depending on the output power and current consumption. Careful PCB design using thermal isolation techniques must be applied between the circuit and the crystal resonator to avoid transferring the heat to the external crystal resonator. + +When using the LoRa® modulation with LowDataRateOptimize set to 0x00 (see Table 13-50: "LoRa® ModParam4 LowDataRateOptimize" on page 82), the total frequency drift over the packet transmission time should be minimized and kept lower than Freq\_drift\_max : + + + +When possible, using LowDataRateOptimize set to 0x01 will significantly relax the total frequency drift over the packet transmission requirement to 16 x Freq\_drift\_max . + +## Note: + +Recommendations for heat dissipation techniques to be applied to the PCB designs are given in detail in the application note AN1200.37 'Recommendations for Best Performance' on www.semtech.com . + +In miniaturized design implementations where heat dissipations techniques cannot be implemented or the use of the LowDataRateOptimize is not supported, the use of a TCXO will provide a more stable clock reference. + +## 4.1.3 XTAL Control Block + +The LLCC68 does not require the user to set external foot capacitors on the XTAL supplying the 32 MHz clock. Indeed, the device is fitted with internal programmable capacitors connected independently to the pins XTA and XTB of the device. Each capacitor can be set independently, balanced or unbalanced to each other, by 0.47 pF typical steps. + +Table 4-1: Internal Foot Capacitor Configuration + +| Pin | Register Address | Typical Values | +| --- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| XTA | 0x0911 | Each capacitor can be controlled independently in steps of 0.47 pF added to the minimal value: 0x00 sets the trimming cap to 11.3 pF (minimum) | +| XTB | 0x0912 | 0x2F sets the trimming cap to 33.4 pF (maximum) | + +## Note when using an XTAL: + +At POR or when waking-up from Sleep in cold start mode, the trimming cap registers will be initialized at the value 0x05 (13.6 pF). Once the device is set in STDBY\_XOSC mode, the internal state machine will overwrite both registers to the value 0x12 (19.7pF). Therefore, the user must ensure the device is already in STDBY\_XOSC mode before changing the trimming cap values so that they are not overwritten by the state machine. + +## Note when using a TCXO: + +Once the command SetDIO3AsTCXOCtrl(...) is sent to the device, the register controlling the internal cap on XTA will be automatically changed to 0x2F (33.4 pF) to filter any spurious which could occur and be propagated to the PLL. + +## 4.1.4 TCXO Control Block + +Under certain circumstances, typically small form factor designs with reduced heat dissipation or environments with extreme temperature variation, it may be required to use a TCXO (Temperature Compensated Crystal Oscillator) to achieve better frequency accuracy. This depends on the complete system, transmitter and receiver. The specification FERR\_L in Table 3-8: "Receive Mode Specifications" on page 19 provides information on the maximum tolerated frequency offset for optimal receiver performance. + +Figure 4-2: TCXO Control Block + + + +When a TCXO is used, it should be connected to pin 3 XTA, through a 220  resistor and a10 pF DC-cut capacitor. Pin 4 XTB should be left not connected. Pin 6 DIO3 can be used to provide a regulated DC voltage to power the TCXO, programmable from 1.6 to 3.3 V. VBAT should always be 200 mV higher than the programmed voltage to ensure proper operation. + +The nominal current drain is 1.5 mA, but the regulator can support up to 4 mA of load. Clipped-sine output TCXO are required, with the output amplitude not exceeding 1.2 V peak-to-peak. The commands to enable TCXO mode are described in Section 13.3.6 "SetDIO3AsTCXOCtrl" on page 76, and that includes DC voltage and timing information. + +## Note: + +A complete Reset of the chip as described in Section 8.1 "Reset" on page 45 is required to get back to normal XOSC operation, after the chip has been set to TCXO mode with the command SetDIO3AsTCXOCtrl . + +## 4.2 Phase-Locked Loop (PLL) + +A fractional-N third order sigma-delta PLL acts as the frequency synthesizer for the LO of both receiver and transmitter chains. LLCC68 is able to cover continuously all the sub-GHz frequency range 150-960 MHz. The PLL is capable of auto-calibration and has low switching-on or hopping times. Frequency modulation is performed inside the PLL bandwidth. The PLL frequency is derived from the crystal oscillator circuit which uses an external 32 MHz crystal reference. + +## 4.3 Receiver + +The received RF signal is first amplified by a differential Low Noise Amplifier (LNA), then down-converted to low- IF intermediate frequency by mixers operating in quadrature configuration. The I and Q signals are low-pass filtered and then digitized by a continuous time feedback architecture  converter (ADC) allowing more than 80 dB dynamic range. Once in the digital domain the signal is then decimated, down-converted again, decimated again, channel filtered and finally demodulated by the selected modem depending on modulation scheme: FSK modem or LoRa® modem. + +## 4.3.1 Intermediate Frequencies + +The LLCC68 receiver mostly operates in low-IF configuration, expect for specific high-bandwidth settings. + +Table 4-2: Intermediate Frequencies in FSK Mode + +| Setting Name | Bandwidth [kHz DSB ] | Intermediate Frequency [kHz] | +| ------------ | --------------------- | ---------------------------- | +| RX\_BW\_467 | 467 | 250 | +| RX\_BW\_234 | 234.3 | 250 | +| RX\_BW\_117 | 117.3 | 250 | +| RX\_BW\_58 | 58.6 | 250 | +| RX\_BW\_29 | 29.3 | 250 | +| RX\_BW\_14 | 14.6 | 250 | +| RX\_BW\_7 | 7.3 | 250 | +| RX\_BW\_373 | 373.6 | 200 | +| RX\_BW\_187 | 187.2 | 200 | +| RX\_BW\_93 | 93.8 | 200 | +| RX\_BW\_46 | 46.9 | 200 | +| RX\_BW\_23 | 23.4 | 200 | +| RX\_BW\_11 | 11.7 | 200 | +| RX\_BW\_5 | 5.8 | 200 | +| RX\_BW\_312 | 312 | 167 | +| RX\_BW\_156 | 156.2 | 167 | +| RX\_BW\_78 | 78.2 | 167 | +| RX\_BW\_39 | 39 | 167 | +| RX\_BW\_19 | 19.5 | 167 | +| RX\_BW\_9 | 9.7 | 167 | +| RX\_BW\_4 | 4.8 | 167 | + +Table 4-3: Intermediate Frequencies in LoRa® Mode + +| BW Setting | Bandwidth [kHz DSB ] | Intermediate Frequency [kHz] | +| ------------- | --------------------- | ---------------------------- | +| LORA\_BW\_500 | 500 | 0 | +| LORA\_BW\_250 | 250 | 250 | +| LORA\_BW\_125 | 125 | 250 | + +## 4.4 Transmitter + +The transmit chain uses the modulated output from the modem bank which directly modulates the fractional-N PLL. An optional pre-filtering of the bit stream can be enabled to reduce the power in the adjacent channels, also dependent on the selected modulation type. + +The default maximum RF output power of the transmitter is +22 dBm. The RF output power is programmable with 32 dB of dynamic range, in 1 dB steps. The power amplifier ramping time is also programmable to meet regulatory requirements. + +The power amplifier is supplied by the regulator VR\_PA and the connection between VR\_PA and RFO is done externally to the chip. VR\_PA, supplied through VDD\_IN, is taken directly from the battery and in this case maximum output power is limited by supply voltage at VDD\_IN. + +## 4.4.1 Power Amplifier Specifics + +## Caution! + +All figures below are indicative and typical, and are not a specification. These figures only highlight behavior of the PA over voltage and current. + +Figures are given with DC-DC regulation enabled, which applies only to the circuit core. + +The PA is optimized for maximum output power whilst maximizing the efficiency, which makes it mandatory to supply the power amplifier with fairly high voltages to maintain an high output power. To summarize: + +- · the current efficiency of the PA is optimal at the highest output power step +- · output power will be limited by the voltage supplied to VBAT + +This is illustrated in the following figure: + +Figure 4-3: VR\_PA versus Output Power on the LLCC68 + + + +The internal regulator for VR\_PA has a little less than 200 mV of drop-out, which means VBAT must be 200 mV higher than the published VR\_PA voltages in order to attain the corresponding output power. For example, for P out = +20 dBm, VR\_PA = 2.5 V is required, which means that the LLCC68 will be able to maintain P out = +20 dBm on the 2.7 V < VBAT < 3.7 V voltage range. Below 2.7 V, the output power will degrade as VBAT reduces. + +As can be seen from the blue curve on Figure 4-3: VR\_PA versus Output Power on the LLCC68, the LLCC68 will be capable of supplying almost 1.7 V when VBAT = 1.8 V, which, in turn, will make the output power plateau at +17 dBm for all power settings above +17 dBm. + +The following plot confirms the linearity of the output power, as long as the VBAT voltage is high enough to supply the required VR\_PA voltage: + +Figure 4-4: Power Linearity on the LLCC68 + + + +The power consumption evolves with the programmed output power, as follows (DC-DC regulation): + +Figure 4-5: Current versus Programmed Output Power on the LLCC68 + + + +## 4.4.2 Power Amplifier Summary + +The following table summarizes the power amplifier optimization keys between both PA supply modes: + +Table 4-4: Power Amplifier Summary + +| PA Summary | Conditions LLCC68 | +| ----------------------------------------------- | -------------------------------------------------------------------------- | +| Max Power with relevant matching and settings | + 22 dBm | +| at + 22 dBm, indicative at + 14 dBm, indicative | IDDTX 118 mA 90/45 1 mA | +| Output Power vs. VBAT | flat from 3.3 V to 3.7 V VBAT = 3.1 V for +22 dBm VBAT = 2.7 V for +20 dBm | + +- 1. See Section 13.1.14.1 "PA Optimal Settings" on page 71. + +## 5. Power Distribution + +## 5.1 Selecting DC-DC Converter or LDO Regulation + +Two forms of voltage regulation (DC-DC buck converter or linear LDO regulator) are available depending upon the design priorities of the application. The linear LDO regulator is always present in all modes but the transceiver will use DC-DC when selected. Alternatively a high efficiency DC to DC buck converter can be enabled in FS, Rx and Tx modes. + +The DC-DC can be driven by two clock sources: + +- · in STDBY\_XOSC: RC13M is used to supply clock and the frequency is RC13M / 4 so the switching frequency of the DC-DC converter will be 3.25 MHz +- · in FS, RX, TX: the PLL is used to supply clock and the frequency is ~5MHz; every time the command SetRFFrequency(...) is called the divider ratio is recalculated so that the switching frequency is as close as possible to the 5 MHz target. + +Unless specified, all specifications of the transceiver are given with the DC-DC regulator enabled. For applications where cost and size are constrained, LDO-only operation is possible which negates the need for the 47nH inductor before pin 1 and the 15 µH inductor between pins 7 and 9, conferring the benefits of a reduced bill of materials and reduced board space. The following table illustrates the power regulation options for different modes and user settings. + +Table 5-1: Regulation Type versus Circuit Mode + +| Circuit Mode | Sleep | STDBY\_RC | STDBY\_XOSC | FS | Rx | Tx | +| ------------------ | ----- | --------- | ----------- | ----------- | ----------- | ----------- | +| Regulator Type = 0 | - | LDO | LDO | LDO | LDO | LDO | +| Regulator Type = 1 | - | LDO | DC-DC + LDO | DC-DC + LDO | DC-DC + LDO | DC-DC + LDO | + +The user can specify the use of DC-DC by using the command SetRegulatorMode(...) . This operation must be carried out in STDBY\_RC mode only. + +When the DC-DC is enabled, the LDO will remain ON and its target voltage is set 50 mV below the DC-DC voltage to ensure voltage stability for high current peaks. If the DC-DC voltage drops to this level due to high current peak, the LDO will cover for the current need at the expense of the energy consumption of the radio which will be increased. + +However, to avoid consuming too much energy, the user is free to configure the Over Current Protection (OCP) register manually. At Reset, the OCP is configured to limit the current at 140 mA. + +Table 5-2: OCP Configuration + +| Register Address | OCP default | Maximum Current | +| ---------------- | ----------- | --------------- | +| 0x08E7 | 0x38 | 140 mA | + +The OCP is configurable by steps of 2.5 mA and the default value is re-configured automatically each time the function SetPaConfig(...) is called. If the user wants to adjust the OCP value, it is necessary to change the register as a second step after calling the function SetPaConfig(...). + +## 5.1.1 Option A: DC-DC Regulator + +The DC-DC Regulator is used with about 90% of efficiency, for the chip core only. The PA regulator is supplied with VBAT. + +Advantage of this option: + +The power consumption of the core is reduced. + +*VBAT=3.3 V min. to reach +22 dBmFigure 5-1: LLCC68 Diagram with the DC-DC Regulator Power Option + + + +## 5.1.2 Option B: LDO Regulator + +The LDO Regulator is used. Power consumption of the core is slightly higher than in Option A. + +Advantage of this option: + +The cost and space for an external 15  H inductor are spared. + +* VBAT=3.3 V min. to reach +22 dBmFigure 5-2: LLCC68 Diagram with the LDO Regulator Power Option + + + +## 5.1.3 Consideration on the DC-DC Inductor Selection + +The selection of the inductor is essential to ensure optimal performance of the DC-DC internal block. Selecting an incorrect inductor could cause various unwanted effects ranging from ripple currents to early aging of the device, as well as a degradation of the efficiency of the DC-DC regulator. + +For the LLCC68, the preferred inductor will be shielded, presenting a low internal series resistance and a resonance frequency much higher than the DC-DC switching frequency. When selecting the 15 µH inductor, the user should therefore select a part with the following considerations: + +- · DCR (max) = 2 ohms +- · Idc (min) = 100 mA +- · Freq (min) = 20 MHz + +Table 5-3: Recommended 15 µH Inductors + +| Reference | Manufacturer | Value (µH) | Idc max (mA) | Freq (MHz) | DCR (ohm) | Package (L x W x H in mm) | +| -------------- | ------------ | ----------- | ------------ | ----------- | --------- | ------------------------- | +| LPS3010-153 | Coilcraft | 15 | 370 | 43 | 0.95 | 2.95 x 2.95 x 0.9 | +| MLZ2012N150L | TDK | 15 | 90 | 40 | 0.47 | 2 x 1.25 x 1.25 | +| MLZ2012M150W | TDK | 15 | 120 | 40 | 0.95 | 2 x 1.25 x 1.25 | +| VLS2010ET-150M | TDK | 15 | 440 | 40 | 1.476 | 2 x 2 x 1 | +| VLS2012ET-150M | TDK | 15 | 440 | 40 | 1.062 | 2 x 2 x 1.2 | + +## 5.2 Flexible DIO Supply + +The transceiver has two power supply pins, one for the core of the transceiver called VBAT and one for the host controller interface (SPI, DIOs, BUSY) called VBAT\_IO. Both power supplies can be connected together in application. In case a low voltage micro-controller (typically with IO pads at 1.8 V) is used to control the transceiver, the user can: + +- · use VBAT at 3.3 V for optimal RF performance +- · directly connect VBAT\_IO to the same supply used for the micro-controller +- · connect the digital IOs directly to the micro-controller DIOs + +At any time, VBAT\_IO must be lower than or equal to VBAT. + +Requirement: VBAT ≥ VBAT\_IO + + + +Figure 5-3: Separate DIO Supply + +## 6. Modems + +The LLCC68 contains different modems capable of handling LoRa® and FSK modulations. LoRa® and FSK are associated with their own frame and modem. + +- · LoRa® modem  LoRa ® Frame +- · FSK modem  FSK Frame + +The user specifies the modem and frame type by using the command SetPacketType(...) . This command specifies the frame used and consequently the modem implemented. + +This function is the first one to be called before going to Rx or Tx and before defining frequency, modulation and packet parameters. The command GetPacketType() returns the current protocol of the radio. + +## 6.1 LoRa® Modem + +The LoRa® modem uses spread spectrum modulation and forward error correction techniques to increase the range and robustness of radio communication links compared to traditional FSK based modulation. + +An important facet of the LoRa® modem is its increased immunity to interference. The LoRa® modem is capable of co-channel GMSK rejection of up to 16 dB. This immunity to interference permits the simple coexistence of LoRa® modulated systems either in bands of heavy spectral usage or in hybrid communication networks that use LoRa® to extend range when legacy modulation schemes fail. + +## 6.1.1 Modulation Parameter + +It is possible to optimize the LoRa® modulation for a given application, access is given to the designer to four critical design parameters, each one permitting a trade-off between the link budget, immunity to interference, spectral occupancy and nominal data rate. These parameters are: + +- · Modulation BandWidth (BW\_L) +- · Spreading Factor (SF) +- · Coding Rate (CR) +- · Low Data Rate Optimization (LDRO) + +These parameters are set using the command SetModulationParams(...) which must be called after SetPacketType(...) . + +## 6.1.1.1 Spreading Factor + +The spread spectrum LoRa® modulation is performed by representing each bit of payload information by multiple chips of information. The rate at which the spread information is sent is referred to as the symbol rate (Rs). The ratio between the nominal symbol rate and chip rate is the spreading factor and it represents the number of symbols sent per bit of information. + +## Consideration on SF5 and SF6 + +In the LLCC68, two spreading factors have been added compared to the previous device family, SF5 and SF6. These two spreading factors have been modified slightly and will now be able to operate in both implicit and explicit mode. However, these modification have made the new spreading factor incompatible with previous device generations. Especially, SF6 on the LLCC68 will not be backward compatible with the SF6 used on the SX1276. Furthermore, due to the higher symbol rate, + +the minimum recommended preamble length needed to ensure correct detection and demodulation from the receiver is increased compared to other Spreading Factors. For SF5 and SF6, the user is invited to use 12 symbols of preamble to have optimal performances over the dynamic range or the receiver. + +## Note: + +The spreading factor must be known in advance on both transmit and receive sides of the link as different spreading factors are orthogonal to each other. Note also the resulting Signal to Noise Ratio (SNR) required at the receiver input. + +It is the capability to receive signals with negative SNR that increases the sensitivity as well as link budget and range of the LoRa® receiver. + +Table 6-1: Range of Spreading Factors (SF) + +| Spreading Factor (SF) 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | +| ---------------------------------- | ---- | --- | ---- | --- | ----- | ---- | ----- | +| 2^SF (Chips / Symbol) | 32 | 64 | 128 | 256 | 512 | 1024 | 2048 | +| Typical LoRa® Demodulator SNR [dB] | -2.5 | -5 | -7.5 | -10 | -12.5 | -15 | -17.5 | + +A higher spreading factor provides better receiver sensitivity at the expense of longer transmission times (time-on-air). With a knowledge of the key parameters that can be selected by the user, the LoRa® symbol rate is defined as: + + + +where BW is the programmed bandwidth and SF is the spreading factor. The transmitted signal is a constant envelope signal. Equivalently, one chip is sent per second per Hz of bandwidth. + +## 6.1.1.2 Bandwidth + +An increase in signal bandwidth permits the use of a higher effective data rate, thus reducing transmission time at the expense of reduced sensitivity improvement. + +LoRa® modem operates at a programmable bandwidth (BW\_L) around a programmable central frequency f RF + +Figure 6-1: LoRa® Signal Bandwidth + + + +An increase in LoRa® signal bandwidth (BW\_L) permits the use of a higher effective data rate, thus reducing transmission time at the expense of reduced sensitivity improvement. There are regulatory constraints in most countries on the + +permissible occupied bandwidth. The LoRa® modem bandwidth always refers to the double side band (DSB). The range of LoRa® signal bandwidths available is given in the table below: + +Table 6-2: Signal Bandwidth Setting in LoRa® Mode + +| Signal Bandwidth | 7 | 8 | 9 | +| ---------------- | ----- | ------ | ------ | +| BW\_L [kHz] 0 | 125 0 | 250 1 | 500 1 | + +- 1. For RF frequencies below 400 MHz, there is a scaling between the frequency and supported BW, some BW may not be available below 400 MHz + +For BW\_L up to 250 kHz, the receiver performs a double conversion. A first down conversion to low- IF is performed inside the RF chain, a second conversion to baseband is performed digitally inside the baseband modem. When the 500 kHz bandwidth is used, a single down-conversion to zero-IF is performed in the RF part. + +## 6.1.1.3 FEC Coding Rate + +To further improve the robustness of the link the LoRa® modem employs cyclic error coding to perform forward error detection and correction. + +Forward Error Correction (FEC) is particularly efficient in improving the reliability of the link in the presence of interference. So that the coding rate and robustness to interference can be changed in response to channel conditions. The coding rate selected on the transmitter side is communicated to the receiver through the header (when present). + +Table 6-3: Coding Rate Overhead + +| Coding Rate | Cyclic Coding Rate CR [in raw bits / total bits] | Overhead Ratio | +| ----------- | ------------------------------------------------ | -------------- | +| 1 | 4/5 | 1.25 | +| 2 | 4/6 | 1.5 | +| 3 | 4/7 | 1.75 | +| 4 | 4/8 | 2 | + +A higher coding rate provides better noise immunity at the expense of longer transmission time. In normal conditions a factor of 4/5 provides the best trade-off; in the presence of strong interferers a higher coding rate may be used. Error correction code does not have to be known in advance by the receiver since it is encoded in the header part of the packet. + +## 6.1.1.4 Low Data Rate Optimization + +For low data rates (typically for high SF or low BW) and very long payloads which may last several seconds in the air, the low data rate optimization (LDRO) can be enabled. This reduces the number of bits per symbol to the given SF minus two (see Section 6.1.4 "LoRa® Time-on-Air" on page 36) in order to allow the receiver to have a better tracking of the LoRa® signal. Depending on the payload size, the low data rate optimization is usually recommended when a LoRa® symbol time is equal or above 16.38 ms. + +## 6.1.2 LoRa® Packet Engine + +LoRa® has it own packet engine that supports the LoRa® PHY as described in the following section. + +## 6.1.3 LoRa® Frame + +The LoRa® modem employs two types of packet formats: explicit and implicit. The explicit packet includes a short header that contains information about the number of bytes, coding rate and whether a CRC is used in the packet. The packet format is shown in the following figure. + +Figure 6-2: LoRa® Packet Format + + + +The LoRa® packet starts with a preamble sequence which is used to synchronize the receiver with the incoming signal. By default the packet is configured with a 12-symbol long sequence. This is a programmable variable so the preamble length may be extended; for example, in the interest of reducing the receiver duty cycle in receive intensive applications. The transmitted preamble length may vary from 10 to 65535 symbols, once the fixed overhead of the preamble data is considered. This permits the transmission of near arbitrarily long preamble sequences. + +The receiver undertakes a preamble detection process that periodically restarts. For this reason the preamble length should be configured as identical to the transmitter preamble length. Where the preamble length is not known, or can vary, the maximum preamble length should be programmed on the receiver side. + +The preamble is followed by a header which contain information about the following payload. The packet payload is a variable-length field that contains the actual data coded at the error rate either as specified in the header in explicit mode or as selected by the user in implicit mode. An optional CRC may be appended. + +Depending upon the chosen mode of operation two types of header are available. + +## 6.1.3.1 Explicit Header Mode + +This is the default mode of operation. Here the header provides information on the payload, namely: + +- · The payload length in bytes +- · The forward error correction coding rate +- · The presence of an optional 16-bit CRC for the payload + +The header is transmitted with maximum error correction code (4/8). It also has its own CRC to allow the receiver to discard invalid headers. + +## 6.1.3.2 Implicit Header Mode + +In certain scenarios, where the payload, coding rate and CRC presence are fixed or known in advance, it may be advantageous to reduce transmission time by invoking implicit header mode. In this mode the header is removed from the packet. In this case the payload length, error coding rate and presence of the payload CRC must be manually configured identically on both sides of the radio link. + +## 6.1.4 LoRa® Time-on-Air + +The packet format for the LoRa® modem is detailed in Figure 6-3: Fixed-Length Packet Format and Figure 6-4: Variable-Length Packet Format. The equation to obtain Time On Air (ToA) is: + + + +- · SF : Spreading Factor (5 to 11) +- · BW : Bandwidth (in kHz) +- · ToA : the Time on Air in ms +- · N symbol : number of symbols + +The computation of the number of symbols differs depending on the parameters of the modulation. + +For SF5 and SF6: + + + +For all other SF: + +For all other SF with Low Data Rate Optimization activated: + +## With: + +- · N\_bit\_CRC = 16 if CRC activated, 0 if not +- · N\_symbol\_header = 20 with explicit header, 0 with implicit header +- · CR is 1, 2, 3 or 4 for respective coding rates 4/5, 4/6, 4/7 or 4/8 + +## 6.1.5 LoRa® Channel Activity Detection (CAD) + +The use of a spread spectrum modulation technique presents challenges in determining whether the channel is already in use by a signal that may be below the noise floor of the receiver. The use of the RSSI in this situation would clearly be impracticable. To this end the channel activity detector is used to detect the presence of other LoRa® signals. + +On the LLCC68, the channel activity detection mode is designed to detect the presence of a LoRa® preamble or data symbols while the previous generations of products were only able to detect LoRa® preamble symbols. + +Once in CAD mode, the LLCC68 will perform a scan of the band for a user-selectable duration (defined in number of symbols) and will then return with the Channel Activity Detected IRQ if LoRa® symbols have been detected during the CAD. + +The time taken for the channel activity detection is dependent upon the LoRa® modulation settings used. For a given configuration (SF/BW) the typical CAD detection time can be selected to be either 1, 2, 4, 8 or 16 symbols. Once the duration of the selected number of symbols has been done, the radio will remains for around half a symbol in Rx to post-process the measurement. + +## 6.2 FSK Modem + +## 6.2.1 Modulation Parameter + +The FSK modem is able to perform transmission and reception of 2-FSK modulated packets over a range of data rates ranging from 0.6 kbps to 300 kbps. All parameters are set by using the command SetModulationParams(...) . This function should be called only after defining the protocol. + +The bitrate setting is referenced to the crystal oscillator and provides a precise means of setting the bit rate (or equivalently chip) rate of the radio. In the command SetModulationParams(...) , the bitrate is expressed as 32 times the XTAL frequency divided the real bit rate used by the device. The generic formula is: + + + +FSK modulation is performed inside the PLL bandwidth, by changing the fractional divider ratio in the feedback loop of the PLL. The high resolution of the sigma-delta modulator, allows for very narrow frequency deviation. The frequency deviation Fdev is one of the parameters of the function SetModulationParams(...) and is expressed as: + + + +where: + + + +Additionally, in transmission mode, several shaping filters can be applied to the signal. In reception mode, the user needs to select the best reception bandwidth depending on its conditions. To ensure correct demodulation, the following limit must be respected for the selection of the bandwidth: + + + +The bandwidth is defined by parameter BW as described in the following table. + +Table 6-4: Bandwidth Definition in FSK Packet Type + +| BW | Value | Bandwidth [kHz DSB ] | +| --------- | ----- | --------------------- | +| 0x1F | 4.8 | BW4 | +| 0x17 | 5.8 | BW5 | +| 0x0F | 7.3 | BW7 | +| 0x1E | 9.7 | BW9 | +| 0x16 | 11.7 | BW11 | +| 0x0E | 14.6 | BW14 | +| 0x1D | 19.5 | BW19 | +| 0x15 | 23.4 | BW23 | +| BW29 0x0D | | 29.3 | + +Table 6-4: Bandwidth Definition in FSK Packet Type + +| BW | Value | Bandwidth [kHz DSB ] | +| ----- | ----- | --------------------- | +| BW39 | 0x1C | 39 | +| BW46 | 0x14 | 46.9 | +| BW58 | 0x0C | 58.6 | +| BW78 | 0x1B | 78.2 | +| BW93 | 0x13 | 93.8 | +| BW117 | 0x0B | 117.3 | +| BW156 | 0x1A | 156.2 | +| BW187 | 0x12 | 187.2 | +| BW234 | 0x0A | 234.3 | +| BW312 | 0x19 | 312 | +| BW373 | 0x11 | 373.6 | +| BW467 | 0x09 | 467 | + +The bandwidth must be chosen so that + +Bandwidth[DSB] ≥ BR + 2*frequency deviation + frequency error + +where the frequency error is two times the crystal frequency error used. + +The LLCC68 offers several pulse shaping options defined by the parameter PulseShape . If other unspecified values are given as parameters, then no filtering is used. + +## 6.2.2 FSK Packet Engine + +The LLCC68 is designed for packet-based transmission. The packet controller block is responsible for assembly of received data bit-stream into packets and their storage into the data buffer. It also performs the bit-stream decoding operations such as de-whitening & CRC-checks on the received bit-stream. + +On the transmit side, the packet handler can construct a packet and send it bit by bit to the modulator for transmission. It can whiten the payload and append the CRC-checksum to the end of the packet. The packet controller only works in half-duplex mode i.e. either in transmit or receive at a time. + +The packet controller is configured using the command SetPacketParams(...) as in Section 13.4.6 "SetPacketParams" on page 83. This function can be called only after defining the protocol. The next chapters describe in detail the different frames available in the LLCC68. + +## 6.2.2.1 Preamble Detection in Receiver Mode + +The LLCC68 is able to gate the reception of a packet if an insufficient number of alternating preamble symbols (usually referred to 0x55 or 0xAA in hexadecimal form) has been detected. This can be selected by the user by using the parameter PreambleDetectorLength used in the command SetPacketParams(...) . The user can select a value ranging from 'Preamble detector length off' - where the radio will not perform any gating and will try to lock directly on the following Sync Word - + +to 'Preamble detector length 32 bits' where the radio will be expecting to receive 32 bits of preamble before the following Sync Word. In this case, if the 32 bits of preamble are not detected, the radio will either drop the reception in RxSingle mode, or restart its tracking loop in RxContinuous mode. + +To achieve best performance of the device, it is recommended to set PreambleDetectorLength to 'Preamble detector length 8 bits' or 'Preamble detector length 16 bits' depending of the complete size of preamble which is sent by the transmitter. + +Note: In all cases, PreambleDetectorLength must be smaller than the size of the following Sync Word to achieve proper detection of the packets. If the preamble length is greater than the following Sync Word length (typically when no Sync Word is used) the user should fill some of the Sync Word bytes with 0x55. + +## 6.2.3 FSK Packet Format + +The FSK packet format provides a conventional packet format for application in proprietary NRZ coded, low energy communication links. The packet format has built in facilities for CRC checking of the payload, dynamic payload size and packet acknowledgement. Optionally whitening based upon pseudo random number generation can be enabled. Two principle packet formats are available in the FSK protocol: fixed length and variable length packets. + +## 6.2.3.1 Fixed-Length Packet + +If the packet length is fixed and known on both sides of the link then knowledge of the packet length does not need to be transmitted over the air. Instead the packet length can be written to the parameter packetLength which determines the packet length in bytes (0 to 255 bytes, but limited to 254 Bytes when the address filtering is activated, see Table 13-56). + +Figure 6-3: Fixed-Length Packet Format + + + +The preamble length is set from 8 to 65535 bits using the parameter PreambleLen . It is usually recommended to use a minimum of 16 bits for the preamble to guarantee a valid reception of the packet on the receiver side. The CRC operation, packet length and preamble length are defined using the command SetPacketParams(...) as defined in Section 11. "List of Commands" on page 57. + +## 6.2.3.2 Variable-Length Packet + +Where the packet is of uncertain or variable size, then information about the packet length (0 to 255 bytes, but limited to 254 Bytes when the address filtering is activated, see Table 13-56) must be transmitted within the packet. The format of the variable-length packet is shown below. + +Figure 6-4: Variable-Length Packet Format + + + +Whitening + +## 6.2.3.3 Setting the Packet Length or Node Address + +The packet length and Node or Broadcast address are not considered part of the payload and they are added automatically in hardware. + +The packet length is added automatically in the packet when the packetType field is set to variable size in the command SetPacketParam(...). + +The node or broadcast address can be enabled by using the AddrComp field is in the command SetPacketParam(...). This field allow the user to enable and select an additional packet filtering at the payload level. + +## 6.2.3.4 Whitening + +The whitening process is built around a 9-bit LFSR which is used to generate a random sequence and the payload (including the payload length, the Node or Broadcast address and CRC checksum when needed) is then XORed with this random sequence to generate the whitened payload. The data is de-whitened on the receiver side by XORing with the same random sequence. This setup limits the number of consecutive 1's or 0's to 9. Note that the data whitening is only required when the user data has high correlation with long strings of 0's and 1's. If the data is already random then the whitening is not required. For example a random source generating the Transmit data, when whitened, could produce longer strings of 1's and 0's, thus it's not required to randomize an already random sequence. + +LFSR Polynomial =X 9 + X 5 + 1Figure 6-5: Data Whitening LFSR + + + +The whitening is based around the 9-bit LFSR polynomial x^9+x^5+1. With this structure, the LSB at the output of the LFSR is XORed with the MSB of the data. + +At the initial stage, each flip-flop of the LFSR can be initialized through the registers at addresses 0x06B8 and 0x6B9. + +Table 6-5: Whitening Initial Value + +| Whitening Initial Value | Register Address | Default Value | +| --------------------------- | ---------------- | ------------- | +| Whitening initial value MSB | 0x06B8 | 0x01 | +| Whitening initial value LSB | 0x06B9 | 0x00 | + +## 6.2.3.5 CRC + +The LLCC68 offers full flexibility to select the polynomial and initial value of the selected polynomial. In additions, the user can also select a complete inversion of the computed CRC to comply with some international standards. + +The CRC can be enabled and configured by using the CRCType field in the command SetPacketParam(...). This field allows the user to enable and select the length and configuration of the CRC. + +## Table 6-6: CRC Type Configuration + +Table 6-8: CRC Polynomial + +| CRCType | Description | +| ------- | -------------------------------------------------------- | +| 0x01 | CRC\_OFF (No CRC) | +| 0x00 | CRC\_1\_BYTE (CRC computed on 1 byte) | +| 0x02 | CRC\_2\_BYTE (CRC computed on 2 bytes) | +| 0x04 | CRC\_1\_BYTE\_INV (CRC computed on 1 byte and inverted) | +| 0x06 | CRC\_2\_BYTE\_INV (CRC computed on 2 bytes and inverted) | + +The CRC selected must be modified together with the CRC initial value and CRC polynomial. + +## Table 6-7: CRC Initial Value + +| | Register Address | Default Value | +| ---------------------------- | ---------------- | ------------- | +| CRC MSB Initial Value [15:8] | 0x06BC | 0x1D | +| CRC LSB Initial Value [7:0] | 0x06BD | 0x0F | + +| | Register Address | Default Value | +| ------------------------------- | ---------------- | ------------- | +| CRC MSB Polynomial Value [15:8] | 0x06BE | 0x10 | +| CRC LSB Polynomial Value [7:0] | 0x06BF | 0x21 | + +This flexibility permits the user to select any standard CRC or to use his own CRC allowing a specific detection of a given packet. Examples: + +## To use the IBM CRC configuration, the user must select: + +- · 0x8005 for the CRC polynomial +- · 0xFFFF for the initial value +- · CRC\_2\_BYTE for the field CRCType in the command SetPacketParam(...) . + +## For the CCIT CRC configuration the user must select: + +- · 0x1021 for the CRC polynomial +- · 0x1D0F for the initial value +- · CRC\_2\_BYTE\_INV for the field CRCType in the command SetPacketParam(...) + +## 7. Data Buffer + +The transceiver is equipped with a 256-byte RAM data buffer which is accessible in all modes except sleep mode. This RAM area is fully customizable by the user and allows access to either data for transmission or from the last packet reception. + +## 7.1 Principle of Operation + +0xFF + +Figure 7-1: Data Buffer Diagram + + + +Data buffer + +Capacity = 256 bytes + +The data buffer can be configured to store both transmit and receive payloads. + +## 7.2 Data Buffer in Receive Mode + +In receive mode RxBaseAddr specifies the buffer offset in memory at which the received packet payload data will be written. The buffer offset of the last byte written in receive mode is then stored in RxDataPointer which is initialized to the value of RxBaseAddr at the beginning of the reception. + +The pointer to the first byte of the last packet received and the packet length can be read with the command GetRxbufferStatus() . + +In single mode, RxDataPointer is automatically initialized to RxBaseAddr each time the transceiver enters Rx mode. In continuous mode the pointer is incremented starting from the previous position. + +## 7.3 Data Buffer in Transmit Mode + +Upon each transition to transmit mode TxDataPointer is initialized to TxBaseAddr and is incremented each time a byte is sent over the air. This operation stops once the number of bytes sent equals the payloadlength parameter as defined in the function SetPacketParams(...) . + +## 7.4 Using the Data Buffer + +Both, RxBaseAddr and TxBaseAddr are set using the command SetBufferBaseAddresses(...) . + +By default RxBaseAddr and TxBaseAddr are initialized at address 0x00. + +Due to the contiguous nature of the data buffer, the base addresses for Tx and Rx are fully configurable across the 256-byte memory area. Each pointer can be set independently anywhere within the buffer. To exploit the maximum data buffer size in transmit or receive mode, the whole data buffer can be used in each mode by setting the base addresses TxBaseAddr and RxBaseAddr at the bottom of the memory (0x00). + +The data buffer is cleared when the device is put into Sleep mode (implying no access). The data is retained in all other modes of operation. + +The data buffer is acceded via the command WriteBuffer(...) and ReadBuffer(...) . In this function the parameter offset defines the address pointer of the first data to be written or read. Offset zero defines the first position of the data buffer. + +Before any read or write operation it is hence necessary to initialize this offset to the corresponding beginning of the buffer. Upon reading or writing to the data buffer the address pointer will then increment automatically. + +Two possibilities exist to obtain the offset value: + +- · First is to use the RxBaseAddr value since the user defines it before receiving a payload. +- · Second, offset can be initialized with the value of RxStartBufferPointer returned by GetRxbufferStatus(...) command. + +## Note: + +All the received data will be written to the data buffer even if the CRC is invalid, permitting user-defined post processing of corrupted data. When receiving, if the packet size exceeds the buffer memory allocated for the Rx, it will overwrite the transmit portion of the data buffer. + +## 8. Digital Interface and Control + +The LLCC68 is controlled via a serial SPI interface and a set of general purpose input/output (DIOs). At least one DIO must be used for IRQ and the BUSY line is mandatory to ensure the host controller is ready to accept the commands. The LLCC68 uses an internal controller (CPU) to handle communication and chip control (mode switching, API etc...). BUSY is used as a busy signal indicating that the chip is ready for new command only if this signal is low. When BUSY is high, the host controller must wait until it goes down again before sending another command. Through SPI the application sends commands to the internal chip or access directly the data memory space. + +## 8.1 Reset + +A complete 'factory reset' of the chip can be issued on request by toggling pin 15 NRESET of the LLCC68. It will be automatically followed by the standard calibration procedure and any previous context will be lost. The pin should be held low for typically 100 µs for the Reset to happen. + +## 8.2 SPI Interface + +The SPI interface gives access to the configuration register via a synchronous full-duplex protocol corresponding to CPOL = 0 and CPHA = 0 in Motorola/Freescale nomenclature. Only the slave side is implemented. + +An address byte followed by a data byte is sent for a write access whereas an address byte is sent and a read byte is received for the read access. The NSS pin goes low at the beginning of the frame and goes high after the data byte. + +MOSI is generated by the master on the falling edge of SCK and is sampled by the slave (i.e. this SPI interface) on the rising edge of SCK. MISO is generated by the slave on the falling edge of SCK. + +A transfer is always started by the NSS pin going low. MISO is high impedance when NSS is high. + +The SPI runs on the external SCK clock to allow high speed up to 16 MHz. + +## 8.2.1 SPI Timing When the Transceiver is in Active Mode + +In this mode the chip is able to handle SPI command in a standard way i.e. no extra delay needed at the first SPI transaction. + +Figure 8-1: SPI Timing Diagram + + + +All timings in following table are given for a max load cap of 10 pF. + +Table 8-1: SPI Timing Requirements + +| Symbol | Description | Minimum | Typical | Maximum | Unit | +| ------ | ------------------------------------------------------------------------------- | ------- | ------- | ------- | ---- | +| t1 | NSS falling edge to SCK setup time | 32 | - | - | ns | +| t2 | SCK period | 62.5 | - | - | ns | +| t3 | SCK high time | 31.25 | - | - | ns | +| t4 | MOSI to SCK hold time | 5 | - | - | ns | +| t5 | MOSI to SCK setup time | 5 | - | - | ns | +| t6 | NSS falling to MISO delay | 0 | - | 15 | ns | +| t7 | SCK falling to MISO delay, | 0 | - | 15 | ns | +| t8 | SCK to NSS rising edge hold time | 31.25 | - | - | ns | +| t9 | NSS high time | 125 | - | - | ns | +| t10 | NSS falling edge to SCK setup time when switching from SLEEP to STDBY\_RC mode | 100 | - | - | u s | +| t11 | NSS falling to MISO delay when switching from SLEEP to STDBY\_RC mode | 0 | - | 150 | u s | + +## 8.2.2 SPI Timing When the Transceiver Leaves Sleep Mode + +One way for the chip to leave Sleep mode is to wait for a falling edge of NSS. At falling edge, all necessary internal regulators are switched On; the chip starts chip initialization before being able to accept first SPI command. This means that the delay between the falling edge of NSS and the first rising edge of SCK must take into account the wake-up sequence and the chip initialization. In Sleep mode and during the initialization phase, the busy signal mapped on BUSY pin, is set high indicating to the host that the chip is not able to accept a new command. Once the chip is in STDBY\_RC mode, the busy signal goes low and the host can start sending a command. This is also true for start-up at battery insertion or after a hard reset. + +Figure 8-2: SPI Timing Transition + + + +## 8.3 Multi-Purpose Digital Input/Output (DIO) + +The chip is interfaced through the 4 control lines which are composed of the BUSY pin and 3 DIOs pins that can be configured as interrupt, debug or to control the radio immediate peripherals (TCXO or RF Switch). + +## 8.3.1 BUSY Control Line + +The BUSY control line is used to indicate the status of the internal state machine. When the BUSY line is held low, it indicates that the internal state machine is in idle mode and that the radio is ready to accept a command from the host controller. + +The BUSY control line is set back to zero once the chip has reached a stable mode and it is ready for a new command. Inherently, the amount of time the BUSY line will stay high depends on the nature of the command. For example, setting the device into TX mode from the STDBY\_RC mode will take much more time than simply changing some radio parameters because the internal state machine will maintain the BUSY line high until the radio is effectively transmitting the packet. + +Figure 8-3: Switching Time Definition + + + +For the internal state machine, all 'write' commands will cause the BUSY line to be asserted high after time T SW , as per the graph above. T SW represents the time required for the internal state machine to wake-up and start processing the command. + +Conversely, the 'read' command will be handled directly without the help of the internal state machine and thus the BUSY line will remains low after a 'read' command. + +The max value for T SW from NSS rising edge to the BUSY rising edge is, in all cases, 600 ns. + +In Sleep mode, the BUSY pin is held high through a 20 kΩ resistor and the BUSY line will go low as soon as the radio leaves the Sleep mode. + +In FS, BUSY will go low when the PLL is locked. + +In RX, BUSY will go to low as soon as the RX is up and ready to receive data. + +In TX, BUSY will go low when the PA has ramped-up and transmission of preamble starts. + +In addition to this, the BUSY will also go high to handle its internal IRQ. In this scenario, it is essential to wait for the BUSY line to go low before sending an SPI command (either a 'read' or 'write' command). + +Figure 8-4: Switching Time Definition in Active Mode + + + +The following table gives the value of T SW Mode for all possible transitions. The switching time is defined as the time between the rising edge of the NSS ending the SPI transaction and the falling edge of BUSY. + +Table 8-2: Switching Time + +| Transition | T SW Mode Typical Value [  s] | +| -------------------------------------------------- | ------------------------------- | +| SLEEP to STBY\_RC cold start (no data retention) | 3500 | +| SLEEP to STBY\_RC warm start (with data retention) | 340 | +| STBY\_RC to STBY\_XOSC | 31 | +| STBY\_RC to FS | 50 | +| STBY\_RC to RX | 83 | +| STBY\_RC to TX | 126 | +| STBY\_XOSC to FS | 40 | +| STBY\_XOSC to TX | 105 | +| STBY\_XOSC to RX | 62 | +| FS to RX | 41 | +| FS to TX | 76 | +| RX to FS | 15 | +| RX to TX | 92 | + +## 8.3.2 Digital Input/Output + +Any of the 3 DIOs can be selected as an output interrupt source for the application. When the application receives an interrupt, it can determine the source by using the command GetIrqStatus(...) . The interrupt can then be cleared using the ClearIrqStatus(...) command. The Pin Description is as follows: + +DIO1 is the generic IRQ line, any interrupt can be mapped to DIO1. The complete list of available IRQ can be found in Section 8.4 "Digital Interface Status versus Chip modes" on page 49. + +DIO2 has a double functionality. As DIO1, DIO2 can be used as a generic IRQ line and any IRQ can be routed through this pin. Also, DIO2 can be configured to drive an RF switch through the use of the command SetDio2AsRfSwitchCtrl(...) . In this mode, DIO2 will be at a logical 1 during Tx and at a logical 0 in any other mode. + +DIO3 also has a double functionality and as DIO1 or DIO2, it can be used as a generic IRQ line. Also, DIO3 can be used to automatically control a TCXO through the command SetDio3AsTCXOCtrl(...) . In this case, the device will automatically power cycle the TCXO when needed. + +## 8.4 Digital Interface Status versus Chip modes + +Table 8-3: Digital Pads Configuration for each Chip Mode + +| Mode | DIO3 | DIO2 | DIO1 | BUSY | MISO | MOSI | SCK | NSS | NRESET | +| ---------- | ------ | ------ | ------ | ------ | ---- | ---- | --- | --- | ------ | +| Reset | PD | PD | PD | PU | HIZ | HIZ | HIZ | IN | - | +| Start-up | HIZ PD | HIZ PD | HIZ PD | HIZ PU | HIZ | HIZ | HIZ | IN | IN PU | +| Sleep | HIZ PD | HIZ PD | HIZ PD | HIZ PU | HIZ | HIZ | HIZ | IN | IN PU | +| STBY\_RC | OUT | OUT | OUT | OUT | OUT | IN | IN | IN | IN PU | +| STBY\_XOSC | OUT | OUT | OUT | OUT | OUT | IN | IN | IN | IN PU | +| FS | OUT | OUT | OUT | OUT | OUT | IN | IN | IN | IN PU | +| RX | OUT | OUT | OUT | OUT | OUT | IN | IN | IN | IN PU | +| TX | OUT | OUT | OUT | OUT | OUT | IN | IN | IN | IN PU | + +## Note: + +- · PU = pull up with 50 k  at typical conditions +- · PD = pull down with 50 k  at typical conditions (the resistor value varies with the supply voltage) + +## 8.5 IRQ Handling + +In total there are 10 possible interrupt sources depending on the selected frame and chip mode. Each one can be enabled or masked. In addition, each one can be mapped to DIO1, DIO2 or DIO3. + +Table 8-4: IRQ Status Registers + +| Bit | IRQ | Description | Modulation | +| --- | ---------------- | ----------------------------------- | ---------- | +| 0 | TxDone | Packet transmission completed | All | +| 1 | RxDone | Packet received | All | +| 2 | PreambleDetected | Preamble detected | All | +| 3 | SyncWordValid | Valid Sync Word detected | FSK | +| 4 | HeaderValid | Valid LoRa® Header received | LoRa® | +| 5 | HeaderErr | LoRa® Header CRC error | LoRa® | +| 6 | CrcErr | Wrong CRC received | All | +| 7 | CadDone | Channel activity detection finished | LoRa® | +| 8 | CadDetected | Channel activity detected | LoRa® | +| 9 | Timeout | Rx or Tx Timeout | All | + +For more information on how to setup IRQ and DIOs, refer to the function SetDioIrqParams() in Section 13.3.1 "SetDioIrqParams" on page 74. + +## 9. Operational Modes + +The LLCC68 features six operating modes. The analog front-end and digital blocks that are enabled in each operating mode are explained in the following table. + +Table 9-1: LLCC68 Operating Modes + +| Mode | Enabled Blocks | +| ----------- | ---------------------------------------------------------------- | +| SLEEP | Optional registers, backup regulator, RC64k oscillator, data RAM | +| STDBY\_RC | Top regulator (LDO), RC13M oscillator | +| STDBY\_XOSC | Top regulator (DC-DC or LDO), XOSC | +| FS | All of the above + Frequency synthesizer at Tx frequency | +| Tx | Frequency synthesizer and transmitter, Modem | +| Rx | Frequency synthesizer and receiver, Modem | + +## 9.1 Startup + +At power-up or after a reset, the chip goes into STARTUP state, the control of the chip being done by the sleep state machine operating at the battery voltage. The BUSY pin is set to high indicating that the chip is busy and cannot accept a command. When the digital voltage and RC clock become available, the chip can boot up and the CPU takes control. At this stage the BUSY line goes down and the device is ready to accept commands. + +## 9.2 Calibration + +The calibration procedure is automatically called in case of POR or via the calibration command. Parameters can be added to the calibrate command to identify which section of calibration should be repeated. The following blocks can be calibrated: + +- · RC64k using the 32 MHz crystal oscillator as reference +- · RC13M using the 32 MHz crystal oscillator as reference +- · PLL to select the proper VCO frequency and division ratio for any RF frequency +- · RX ADC +- · Image (RX mode with defined tone) + +Once the calibration is finished, the chip enters STDBY\_RC mode. + +## 9.2.1 Image Calibration for Specific Frequency Bands + +Image calibration is done through the command CalibrateImage(...) for a given range of frequencies defined by the parameters freq1 and freq2 . Once performed, the calibration is valid for all frequencies between the two extremes used as parameters. Typically, the user can select the parameters freq1 and freq2 to cover any specific ISM band. + +Table 9-2: Image Calibration Over the ISM Bands + +| Frequency Band [MHz] | Freq1 | Freq2 | +| -------------------- | -------------- | -------------- | +| 430 - 440 | 0x6B | 0x6F | +| 470 - 510 | 0x75 | 0x81 | +| 779 - 787 | 0xC1 | 0xC5 | +| 863 - 870 | 0xD7 | 0xDB | +| 902 - 928 | 0xE1 (default) | 0xE9 (default) | + +In case of POR or when the device is recovering from Sleep mode in cold start mode, the image calibration is performed as part of the initial calibration process and for optimal image rejection in the band 902 - 928 MHz. However at this stage the internal state machine has no information whether an XTAL or a TCXO is fitted. When the 32 MHz clock is coming from a TCXO, the calibration will fail and the user should request a complete calibration after calling the function SetDIO3AsTcxoCtrl(...). + +By default, the image calibration is made in the band 902 - 928 MHz. Nevertheless, it is possible to request the device to perform a new image calibration at other frequencies. + +## Note: + +Contact your Semtech representative for the other optimal calibration settings outside of the given frequency bands. + +## 9.3 Sleep Mode + +In this mode, most of the radio internal blocks are powered down or in low power mode and optionally the RC64k clock and the timer are running. The chip may enter this mode from STDBY\_RC and can leave the SLEEP mode if one of the following events occurs: + +- · NSS pin goes low in any case +- · RTC timer generates an End-Of-Count (corresponding to Listen mode) + +When the radio is in Sleep mode, the BUSY pin is held high. + +## 9.4 Standby (STDBY) Mode + +In standby mode the host should configure the chip before going to RX or TX modes. By default in this state, the system is clocked by the 13 MHz RC oscillator to reduce power consumption (in all other modes except SLEEP the XTAL is turned ON). However if the application is time critical, the XOSC block can be turned or left ON. + +XOSC or RC13M selection in standby mode is determined by mode parameter in the command SetStandby(...) . + +The mode where only RC13M is used is called STDBY\_RC and the one with XOSC ON is called STDBY\_XOSC. + +If DC-DC is to be used, the selection should be made while the circuit is in STDBY\_RC mode by using the SetRegulatorMode(...) command, then the DC-DC will automatically switch ON when entering STDBY\_XOSC mode. The DC-DC will be clocked by the RC13M. The LDO will remain active with a target voltage 50 mV lower than the DC-DC one. + +## 9.5 Frequency Synthesis (FS) Mode + +In FS mode, PLL and related regulators are switched ON. The BUSY goes low as soon as the PLL is locked or timed out. + +For debugging purposes the chip may be requested to remain in this mode by using the SetFs() command. + +Since the LLCC68 uses low IF architecture, the RX and TX frequencies are different. The RX frequency is equal to TX one minus the intermediate frequency (IF). In FS or TX modes, the RF frequency is directly programmed by the user. + +## 9.6 Receive (RX) Mode + +In RX mode, the RF front-end, RX ADC and the selected modem (LoRa® or FSK) are turned ON. In RX mode the circuit can operate in different sub-modes: + +- · Continuous mode : the device remains in RX mode and waits for incoming packet reception until the host requests a different mode, +- · Single mode : the device returns automatically to STDBY\_RC mode after packet reception, +- · Single mode with timeout : the device returns automatically to STDBY\_RC mode after packet reception or after the selected timeout, +- · Listen mode : the device alternate between Sleep and Rx mode until an IRQ is triggered. + +In RX mode, BUSY will go low as soon as the RX is enabled and ready to receive data. + +The LLCC68 can operate in Rx Boosted gain setup or in Rx power saving gain setup. In the Rx power saving gain, the radio will consume less power at a small cost in sensitivity. In Rx Boosted gain, the radio will consume more power to improve the sensitivity. + +Table 9-3: Rx Gain Configuration + +| Rx Gain | Register Address | Value | +| ------- | ---------------- | ----------------------------------------------------------- | +| Rx Gain | 0x08AC | Rx Power Saving gain: 0x94 (default) Rx Boosted gain: 0x96 | + +The Rx Gain register is not part of the retention memory when waking-up from a warm-start mode. To include this register in the retention memory, the following steps are required: + +- · Set register 0x029F to 0x01 +- · Set register 0x02A0 to 0x08 +- · Set register 0x02A1 to 0xAC + +This procedure is mandatory when using the SetRxDutyCycle method, to keep on using the Rx Boosted gain. + +## Note: + +In LoRa® mode, the user can also use the command SetLoRaSymbNumTimeout(...) to perform a quick and immediate assessment of the presence (or not) of LoRa preamble symbols. If the user defined parameter SymbNum is different from 0, the modem will wait for a total of SymbNum LoRa® symbol to validate, or not, the correct detection of a LoRa® packet. If the various states of the demodulator are not lock at this moment, the radio will generate the RxTimeout IRQ. Otherwise, the radio will stay in Rx for the full duration of the packet. For more information, please see Section 13.4.9 "SetLoRaSymbNumTimeout" on page 89. + +## 9.7 Transmit (TX) Mode + +In TX mode, after enabling and ramping-up the Power Amplifier (PA), the contents of the data buffer are transmitted. The circuit can operate in different sub-modes: single mode or single with timeout mode. + +The timeout in Tx mode can be used as a security to ensure that if for any reason the Tx is aborted or does not succeed (ie. the TxDone IRQ never is never triggered), the TxTimeout will prevent the system from waiting for an unknown amount of time. Using the timeout while in Tx mode remove the need to use resources from the host MCU to perform the same task. + +In TX mode, BUSY will go low as soon as the PA has ramped-up and transmission of preamble starts. + +## 9.7.1 PA Ramping + +The ramping of the PA can be selected while setting the output power by using the command SetTxParams(...) . + +The PA ramp time can be selected to go from 10  s up to 3.4 ms. + +## 9.8 Active Mode Switching Time + +For more details on active mode switching time, see Section 8.3.1 "BUSY Control Line" on page 47. + +## 9.9 Transceiver Circuit Modes Graphical Illustration + +The device operating modes and the states through which each mode transitions are illustrated here: + +Figure 9-1: Transceiver Circuit Modes + + + +## 10. Host Controller Interface + +Through the SPI interface, the host can issue commands to the chip or access the data memory space to directly retrieve or write data. In normal operation, a reduced number of direct data write operations is required except when accessing the data buffer. + +The user interacts with the circuit through an API (instruction set). + +The LLCC68 uses the pin BUSY to indicate the status of the chip and its ability (or not) to receive another command while internal processing occurs. Prior to executing one of the generic functions, it is thus necessary to check the status of BUSY to make sure the chip is in a state where it can process another function. + +## 10.1 Command Structure + +In case of a command that does not require any parameter, the host sends only the opcode (1 byte). + +In case of a command which requires one or several parameters, the opcode byte is followed immediately by parameter bytes with the NSS rising edge terminating the command. + +Table 10-1: SPI Interface Command Sequence + +| Byte | 0 | [1:n] | +| -------------- | ------ | ---------- | +| Data from host | Opcode | Parameters | +| Data to host | RFU | Status | + +## 10.2 Transaction Termination + +The host terminates an SPI transaction with the rising NSS signal; the host does not explicitly send the command length as a parameter. The host must not raise NSS within the bytes of a transaction. + +If a transaction sends a command requiring parameters, all the parameters must be sent before rising NSS. If not the chip will take some unknown value for the missing parameters. + +## 11. List of Commands + +The following tables give the list of commands and their corresponding opcode. Unless specified, all parameters are 8-bit values. + +## 11.1 Operational Modes Commands + +These functions have a direct impact on the behaviour of the device. They control the internal state machine to transmit or receive packets, and all the modes in-between. + +Table 11-1: Commands Selecting the Operating Modes of the Radio + +| Command | Opcode | Parameters | Description | +| --------------------- | ------ | ------------------------------------ | --------------------------------------------------------------------------------------------------- | +| SetSleep | 0x84 | sleepConfig | Set Chip in SLEEP mode | +| SetStandby | 0x80 | standbyConfig | Set Chip in STDBY\_RC or STDBY\_XOSC mode | +| SetFs | 0xC1 | - | Set Chip in Frequency Synthesis mode | +| SetTx | 0x83 | timeout[23:0] | Set Chip in Tx mode | +| SetRx | 0x82 | timeout[23:0] | Set Chip in Rx mode | +| StopTimerOnPreamble | 0x9F | StopOnPreambleParam | Stop Rx timeout on Sync Word/Header or preamble detection | +| SetRxDutyCycle | 0x94 | rxPeriod[23:0], sleepPeriod[23:0] | Store values of RTC setup for listen mode and if period parameter is not 0, set chip into RX mode | +| SetCad | 0xC5 | - | Set chip into RX mode with passed CAD parameters | +| SetTxContinuousWave | 0xD1 | - | Set chip into TX mode with infinite carrier wave settings | +| SetTxInfinitePreamble | 0xD2 | - | Set chip into TX mode with infinite preamble settings | +| SetRegulatorMode | 0x96 | regModeParam | Select LDO or DC\_DC+LDO for CFG\_XOSC, FS, RX or TX mode | +| Calibrate | 0x89 | calibParam | Calibrate the RC13, RC64, ADC, PLL, Image according to parameter | +| CalibrateImage | 0x98 | freq1, freq2 | Launches an image calibration at the given frequencies | +| SetPaConfig | 0x95 | paDutyCycle, HpMax, deviceSel, paLUT | Configure the Duty Cycle, Max output power, device for the PA | +| SetRxTxFallbackMode | 0x93 | fallbackMode | Defines into which mode the chip goes after a TX / RX done. | + +## 11.2 Register and Buffer Access Commands + +## Table 11-2: Commands to Access the Radio Registers and FIFO Buffer + +Table 11-4: Commands Controlling the RF and Packets Settings + +| Command | Opcode | Parameters | Description | +| ------------- | ------ | ------------------------ | ----------------------------------- | +| WriteRegister | 0x0D | address[15:0], data[0:n] | Write into one or several registers | +| ReadRegister | 0x1D | address[15:0] | Read one or several registers | +| WriteBuffer | 0x0E | offset, data[0:n] | Write data into the FIFO | +| ReadBuffer | 0x1E | offset | Read data from the FIFO | + +## 11.3 DIO and IRQ Control + +## Table 11-3: Commands Controlling the Radio IRQs and DIOs + +| Command | Opcode | Parameters | Description | +| --------------------- | ------ | ---------------------------------------------------------------- | ---------------------------------------------------- | +| SetDioIrqParams | 0x08 | IrqMask[15:0], Dio1Mask[15:0], Dio2Mask[15:0], Dio3Mask[15:0], | Configure the IRQ and the DIOs attached to each IRQ | +| GetIrqStatus | 0x12 | - | Get the values of the triggered IRQs | +| ClearIrqStatus | 0x02 | - | Clear one or several of the IRQs | +| SetDIO2AsRfSwitchCtrl | 0x9D | enable | Configure radio to control an RF switch from DIO2 | +| SetDIO3AsTcxoCtrl | 0x97 | tcxoVoltage, timeout[23:0] | Configure the radio to use a TCXO controlled by DIO3 | + +## 11.4 RF, Modulation and Packet Commands + +| Command | Opcode | Parameters | Description | +| ------------------- | ------ | ------------------------------- | ----------------------------------------------------------------------------------- | +| SetRfFrequency | 0x86 | rfFreq[23:0] | Set the RF frequency of the radio | +| SetPacketType | 0x8A | protocol | Select the packet type corresponding to the modem | +| GetPacketType | 0x11 | - | Get the current packet configuration for the device | +| SetTxParams | 0x8E | power, rampTime | Set output power and ramp time for the PA | +| SetModulationParams | 0x8B | modParam1, modParam2, modParam3 | Compute and set values in selected protocol modem for given modulation parameters | + +Table 11-4: Commands Controlling the RF and Packets Settings + +| Command | Opcode | Parameters | Description | +| --------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------- | +| SetPacketParams | 0x8C | packetParam1, packetParam2, packetParam3, packetParam4, packetParam5, packetParam6, packetParam7, packetParam8, packetParam9 | Set values on selected protocol modem for given packet parameters | +| SetCadParams | 0x88 | cadSymbolNum, cadDetPeak, cadDetMin, cadExitMode, cadTimeout | Set the parameters which are used for performing a CAD (LoRa® only) | +| SetBufferBaseAddress | 0x8F | TxbaseAddr, RxbaseAddr | Store TX and RX base address in regis- ter of selected protocol modem | +| SetLoRaSymbNumTimeout | 0xA0 | SymbNum | Set the number of symbol the modem has to wait to validate a lock | + +## 11.5 Status Commands + +Table 11-5: Commands Returning the Radio Status + +| Command | Opcode | Parameters | Description | +| ----------------- | ------ | ---------- | ------------------------------------------------------------------------------------------------------------------- | +| GetStatus | 0xC0 | - | Returns the current status of the device | +| GetRssiInst | 0x15 | - | Returns the instantaneous measured RSSI while in Rx mode | +| GetRxBufferStatus | 0x13 | - | Returns PaylaodLengthRx(7:0), RxBufferPointer(7:0) | +| GetPacketStatus | 0x14 | - | Returns RssiAvg, RssiSync, PStatus2, PStatus3, PStatus4 in FSK protocol, returns RssiPkt, SnrPkt in LoRa® protocol | +| GetDeviceErrors | 0x17 | - | Returns the error which has occurred in the device | +| ClearDeviceErrors | 0x07 | 0x00 | Clear all the error(s). The error(s) cannot be cleared independently | +| GetStats | 0x10 | - | Returns statistics on the last few received packets | +| ResetStats | 0x00 | - | Resets the value read by the command GetStats | + +## 12. Register Map + +## 12.1 Register Table + +Table 12-1: List of Registers + +| Register Name | Address | Reset Value | Function | +| ---------------------------- | ------- | ----------- | -------------------------------------------------------------------------------------- | +| DIOx output enable | 0x0580 | 0x00 | Non-standard DIOx control 1 | +| DIOx input enable | 0x0583 | 0x00 | Non-standard DIOx control 1 | +| DIOx pull-up control | 0x0584 | 0x00 | Non-standard DIOx control 1 | +| DIOx pull-down control | 0x0585 | 0x00 | Non-standard DIOx control 1 | +| Whitening initial value MSB | 0x06B8 | 0xX1 | Initial value used for the whitening LFSR in FSK mode. The user should not change the | +| Whitening initial value LSB | 0x06B9 | 0x00 | value of the 7 MSB of this register Note: X is here an undefined value | +| CRC MSB Initial Value [0] | 0x06BC | 0x1D | Initial value used for the polynomial used to | +| CRC LSB Initial Value [1] | 0x06BD | 0x0F | compute the CRC in FSK mode | +| CRC MSB polynomial Value [0] | 0x06BE | 0x10 | Polynomial used to compute the CRC in FSK | +| CRC LSB polynomial Value [1] | 0x06BF | 0x21 | mode | +| SyncWord[0] | 0x06C0 | - | 1st byte of the Sync Word in FSK mode | +| SyncWord[1] | 0x06C1 | - | 2nd byte of the Sync Word in FSK mode | +| SyncWord[2] | 0x06C2 | - | 3rd byte of the Sync Word in FSK mode | +| SyncWord[3] | 0x06C3 | - | 4th byte of the Sync Word in FSK mode | +| SyncWord[4] | 0x06C4 | - | 5th byte of the Sync Word in FSK mode | +| SyncWord[5] | 0x06C5 | - | 6th byte of the Sync Word in FSK mode | +| SyncWord[6] | 0x06C6 | - | 7th byte of the Sync Word in FSK mode | +| SyncWord[7] | 0x06C7 | - | 8th byte of the Sync Word in FSK mode | +| Node Address | 0x06CD | 0x00 | Node Address used in FSK mode | +| Broadcast Address | 0x06CE | 0x00 | Broadcast Address used in FSK mode | +| IQ Polarity Setup | 0x0736 | 0x0D | Optimize the inverted IQ operation | +| LoRa Sync Word MSB | 0x0740 | 0x14 | Differentiate the LoRa® signal for Public or | +| LoRa Sync Word LSB | 0x0741 | 0x24 | Set to 0x3444 for Public Network Set to 0x1424 for Private Network | + +Table 12-1: List of Registers + +| Register Name | Address | Reset Value | Function | +| --------------------------- | ------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| RandomNumberGen[0] | 0x0819 | - | Can be used to get a 32-bit random number | +| RandomNumberGen[1] | 0x081A | - | Can be used to get a 32-bit random number | +| RandomNumberGen[2] | 0x081B | - | Can be used to get a 32-bit random number | +| RandomNumberGen[3] | 0x081C | - | Can be used to get a 32-bit random number | +| TxModulation | 0x0889 | 0x01 | Refer to Section 15. | +| Rx Gain | 0x08AC | 0x94 | Set the gain used in Rx mode: Rx Power Saving gain: 0x94 Rx Boosted gain: 0x96 | +| TxClampConfig | 0x08D8 | 0xC8 | Refer to Section 15. | +| OCP Configuration | 0x08E7 | 0x18 | Set the Over Current Protection level. The value is changed internally depending on the device selected. Default values are: Default value is 0x38 (140 mA) | +| RTC Control 2 | 0x0902 | 0x00 | Enable or disable RTC Timer | +| XTA trim | 0x0911 | 0x05 | Value of the trimming cap on XTA pin This register should only be changed while the radio is in STDBY\_XOSC mode. | +| XTB trim | 0x0912 | 0x05 | Value of the trimming cap on XTB pin This register should only be changed while the radio is in STDBY\_XOSC mode. | +| DIO3 output voltage control | 0x920 | 0x01 | Non-standard DIO3 control 1 | +| Event Mask | 0x944 | 0x00 | Used to clear events 1 | + +- 1. Use only with Semtech-provided code samples +- 2. Use only with Semtech-provided workaround of Section 15.3 + +## 13. Commands Interface + +## 13.1 Operational Modes Functions + +## 13.1.1 SetSleep + +The command SetSleep(...) is used to set the device in SLEEP mode with the lowest current consumption possible. This command can be sent only while in STDBY mode (STDBY\_RC or STDBY\_XOSC). After the rising edge of NSS, all blocks are switched OFF except the backup regulator if needed and the blocks specified in the parameter sleepConfig . + +Table 13-1: SetSleep SPI Transaction + +| Byte | 0 | 1 | +| -------------- | ------------- | ----------- | +| Data from host | Opcode = 0x84 | sleepConfig | + +The sleepConfig argument is defined in Table 13-2. + +Table 13-2: Sleep Mode Definition + +| SleepConfig[7:3] SleepConfig [2] | SleepConfig [1] | SleepConfig [0] | +| --------------------------------------------------------------- | --------------- | ---------------------------------- | +| RESERVED 0: cold start | 0: RFU | 0: RTC timeout disable | +| RESERVED 1: warm start (device configuration in retention) 1 | 0: RFU | 1: wake-up on RTC timeout (RC64k) | + +- 1. Note that only configuration for the activated modem before going to sleep is retained. Configuration of the other modems is lost and must be re-configured. + +When entering SLEEP mode, the BUSY line is asserted high and stays high for the duration of the SLEEP period. + +Once in SLEEP mode, it is possible to wake the device up from the host processor with a falling edge on the NSS line. The device can also wake up automatically based on a counter event driven by the RTC 64 kHz clock. If the RTC is used, a rising edge of NSS will still wake up the chip (the host keeps control of the chip). + +By default, when entering into SLEEP mode, the chip configuration is lost. However, being able to store chip configuration to lower host interaction or during RxDutyCycle mode can be implemented using the register in retention mode during SLEEP state. This is available when the SetSleep(...) command is sent with sleepConfig[2] set to 1. Once the chip leaves SLEEP mode (by NSS or RTC event), the chip will first restore the registers with the value stored into the retention register. + +## Caution: + +Once the command SetSleep(...) has been sent, the device will become unresponsive for around 500  s, time needed for the configuration saving process and proper switch off of the various blocks. The user must thus make sure the device will not be receiving SPI command during these 500  s to ensure proper operations of the device. + +## 13.1.2 SetStandby + +The command SetStandby(...) is used to set the device in a configuration mode which is at an intermediate level of consumption. In this mode, the chip is placed in halt mode waiting for instructions via SPI. This mode is dedicated to chip configuration using high level commands such as SetPacketType(...) . + +By default, after battery insertion or reset operation (pin NRESET goes low), the chip will enter in STDBY\_RC mode running with a 13 MHz RC clock. + +Table 13-3: SetConfig SPI Transaction + +| Byte | 0 | 1 | +| -------------- | ------------- | ----------- | +| Data from host | Opcode = 0x80 | StdbyConfig | + +The StdbyConfig byte definition is as follows: + +## Table 13-4: STDBY Mode Configuration + +Table 13-6: SetTx SPI Transaction + +| StdbyConfig | Value | Description | +| ----------- | ----- | -------------------------------------------------- | +| STDBY\_RC | 0 | Device running on RC13M, set STDBY\_RC mode | +| STDBY\_XOSC | 1 | Device running on XTAL 32MHz, set STDBY\_XOSC mode | + +## 13.1.3 SetFs + +The command SetFs() is used to set the device in the frequency synthesis mode where the PLL is locked to the carrier frequency. This mode is used for test purposes of the PLL and can be considered as an intermediate mode. It is automatically reached when going from STDBY\_RC mode to TX mode or RX mode. + +## Table 13-5: SetFs SPI Transaction + +| Byte | 0 | +| -------------- | ------------- | +| Data from host | Opcode = 0xC1 | + +In FS mode, the PLL will be set to the frequency programmed by the function SetRfFrequency(...) which is the same used for TX or RX operations. + +## 13.1.4 SetTx + +The command SetTx() sets the device in transmit mode. + +| Byte | 0 | 1-3 | +| -------------- | ------------- | ------------- | +| Data from host | Opcode = 0x83 | timeout(23:0) | + +- · Starting from STDBY\_RC mode, the oscillator is switched ON followed by the PLL, then the PA is switched ON and the PA regulator starts ramping according to the ramping time defined by the command SetTxParams(...) +- · When the ramping is completed the packet handler starts the packet transmission +- · When the last bit of the packet has been sent, an IRQ TX\_DONE is generated, the PA regulator is ramped down, the PA is switched OFF and the chip goes back to STDBY\_RC mode +- · A TIMEOUT IRQ is triggered if the TX\_DONE IRQ is not generated within the given timeout period +- · The chip goes back to STBY\_RC mode after a TIMEOUT IRQ or a TX\_DONE IRQ. + +The timeout duration can be computed with the formula: + +Timeout duration = Timeout * 15.625 µs + +Timeout is a 23-bit parameter defining the number of step used during timeout as defined in the following table. + +## Table 13-7: SetTx Timeout Duration + +Table 13-8: SetRx SPI Transaction + +| Timeout(23:0) | Timeout Duration | +| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 0x000000 | Timeout disable, Tx Single mode, the device will stay in TX Mode until the packet is transmitted and returns in STBY\_RC mode upon completion. | +| Others | Timeout active, the device remains in TX mode, it returns automatically to STBY\_RC mode on timer end-of-count or when a packet has been transmitted. The maximum timeout is then 262 s. | + +The value given for the timeout should be calculated for a given packet size, given modulation and packet parameters. The timeout behaves as a security in case of conflicting commands from the host controller. + +The timeout in Tx mode can be used as a security to ensure that if for any reason the Tx is aborted or does not succeed (ie. the TxDone IRQ never is never triggered), the TxTimeout will prevent the system from waiting for an unknown amount of time. Using the timeout while in Tx mode remove the need to use resources from the host MCU to perform the same task. + +## 13.1.5 SetRx + +The command SetRx() sets the device in receiver mode. + +| Byte | 0 | 1-3 | +| -------------- | ------------- | ------------- | +| Data from host | Opcode = 0x82 | timeout(23:0) | + +This command sets the chip in RX mode, waiting for the reception of one or several packets. The receiver mode operates with a timeout to provide maximum flexibility to end users. + +Table 13-9: SetRx Timeout Duration + +| Timeout(23:0) | Timeout Duration | +| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 0x000000 | No timeout. Rx Single mode. The device will stay in RX Mode until a reception occurs and the devices return in STBY\_RC mode upon completion | +| 0xFFFFFF | Rx Continuous mode. The device remains in RX mode until the host sends a command to change the operation mode. The device can receive several packets. Each time a packet is received, a packet done indication is given to the host and the device will automatically search for a new packet. | +| Others | Timeout active. The device remains in RX mode, it returns automatically to STBY\_RC mode on timer end-of-count or when a packet has been received. As soon as a packet is detected, the timer is automatically disabled to allow complete reception of the packet. The maximum timeout is then 262 s. | + +When the timeout is active (0x000000 < timeout < 0xFFFFFF), the radio will stop the reception at the end of the timeout period unless a preamble and Sync Word (in GFSK) or Header (in LoRa®) has been detected. This is to ensure that a valid packet will not be dropped in the middle of the reception due to the pre-defined timeout. By default, the timer will be stopped only if the Sync Word or header has been detected. However, it is also possible to stop the timer upon preamble detection by using the command StopTimerOnPreamble(...). + +## 13.1.6 StopTimerOnPreamble + +The command StopTimerOnPreamble(...) allows the user to select if the timer is stopped upon preamble detection of Sync Word / header detection. + +Table 13-10: StopTimerOnPreamble SPI Transaction + +| Byte | 0 | 1 | +| -------------- | ------------- | ------------------- | +| Data from host | Opcode = 0x9F | StopOnPreambleParam | + +The enable byte definition is given in the following table. + +Table 13-11: StopOnPreambParam Definition + +| StopOnPreambleParam | Value | Description | +| ------------------- | ----- | --------------------------------------------------- | +| disable | 0x00 | Timer is stopped upon Sync Word or Header detection | +| enable | 0x01 | Timer is stopped upon preamble detection | + +By default, the timer is stopped only when the Sync Word (in GFSK) or Header (in LoRa®) has been detected. When the function StopTimerOnPreamble(...) is used with the value enable at 0x01, then the timer will be stopped upon preamble + +detection and the device will stay in RX mode until a packet is received. It is important to notice that stopping the timer upon preamble may cause the device to stay in Rx for an unexpected long period of time in case of false detection. + +Radio Current + +Figure 13-1: Stopping Timer on Preamble or Header Detection + + + +## 13.1.7 SetRxDutyCycle + +This command sets the chip in sniff mode so that it regularly looks for new packets. This is the listen mode. + +Table 13-12: SetRxDutyCycle SPI Transaction + +| Byte | 0 | 1-3 | 4-6 | +| -------------- | ------------ | -------------- | ----------------- | +| Data from host | Opcode= 0x94 | rxPeriod(23:0) | sleepPeriod(23:0) | + +When this command is sent in STDBY\_RC mode, the context (device configuration) is saved and the chip enters in a loop defined by the following steps: + +- · The chip enters RX and listens for a packet for a period of time defined by rxPeriod +- · The chip is looking for a preamble in either LoRa® or FSK +- · Upon preamble detection, the timeout is stopped and restarted with the value 2 * rxPeriod + sleepPeriod +- · If no packet is received during the RX window (defined by rxPeriod), the chip goes into SLEEP mode with context saved for a period of time defined by sleepPeriod +- · At the end of the SLEEP window, the chip automatically restarts the process of restoring context and enters the RX mode, and so on. At any time, the host can stop the procedure. + +The loop is terminated if either: + +- · A packet is detected during the RX window, at which moment the chip interrupts the host via the RX\_DONE flag and returns to STBY\_RC mode +- · The host issues a SetStandby(...) command during the RX window (during SLEEP mode, the device is unable to receive commands straight away and must first be waken up by a falling edge of NSS). + +The SLEEP mode duration is defined by: + +Sleep Duration = sleepPeriod * 15.625 µs + +The RX mode duration is defined by + +Rx Duration = rxPeriod * 15.625 µs + +The following figure highlights operations being performed while in RxDutyCycle mode. It can be observed that the radio will spend around 1 ms to save the context and go into SLEEP mode and then re-initialize the radio, lock the PLL and go into RX. The delay is not accurate and may vary depending on the time needed for the XTAL to start, the PLL to lock, etc. + +Figure 13-2: RX Duty Cycle Energy Profile + + + +Upon preamble detection, the radio is set to look for a Sync Word (in GFSK) or a header (in LoRa®) and the timer is restarted with a new value which is computed as 2 * rxPeriod + sleepPeriod . This is to ensure that the radio does not spend an indefinite amount of time waiting in Rx for a packet which may never arrive (false preamble detection). + +This implies a strong relationship between the time-on-air of the packet to be received, and the amount of time the radio spends in RX and in SLEEP mode. If a long preamble is used on the TX side, care must be taken that the formula below is respected: + + + +Figure 13-3: RX Duty Cycle when Receiving + + + +Note: when using a TCXO controlled by the LLCC68 itself, the startup delay defined in delay(23:0) will be added between the Sleep and Rx periods + +## 13.1.8 SetCAD + +The command SetCAD() can be used only in LoRa® packet type. The Channel Activity Detection is a LoRa® specific mode of operation where the device searches for the presence of a LoRa® preamble signal. After the search has completed, the device returns in STDBY\_RC mode. The length of the search is configured via the command SetCadParams(...) . At the end of the search period, the device triggers the IRQ CADdone if it has been enabled. If a valid signal has been detected it also generates the IRQ CadDetected . + +Table 13-13: SetCAD SPI Transaction + +| Byte | 0 | +| -------------- | ------------- | +| Data from host | Opcode = 0xC5 | + +## 13.1.9 SetTxContinuousWave + +SetTxContinuousWave() is a test command available for all packet types to generate a continuous wave (RF tone) at selected frequency and output power. The device stays in TX continuous wave until the host sends a mode configuration command. + +Table 13-14: SetTxContinuousWave SPI Transaction + +| Byte | 0 | +| -------------- | ------------- | +| Data from host | Opcode = 0xD1 | + +While this command has no real use case in real life, it can provide valuable help to the developer to check and monitor the performances of the radio while in Tx mode. + +## 13.1.10 SetTxInfinitePreamble + +SetTxInfinitePreamble() is a test command to generate an infinite sequence of alternating zeros and ones in FSK modulation. In LoRa®, the radio is only able to constantly modulate LoRa® preamble symbols. The device will remain in TX infinite preamble until the host sends a mode configuration command. + +While this command has no real use case in real life, it can provide valuable help to the developer to check and monitor the performances of the radio while modulating in Tx mode. + +Table 13-15: SendTxInfinitePreamble SPI Transaction + +| Byte | 0 | +| -------------- | ------------- | +| Data from host | Opcode = 0xD2 | + +However, when using this function, it is impossible to define any data sent by the device. In LoRa® mode, the radio is only able to constantly modulate LoRa preamble symbols and, in FSK mode, the radio is only able to generate FSK preamble (0x55). Nevertheless, the end user will be able to easily monitor the spectral impact of its modulation parameters. + +## 13.1.11 SetRegulatorMode + +By default only the LDO is used. This is useful in low cost applications where the cost of the extra self needed for a DC-DC converter is prohibitive. Using only a linear regulator implies that the RX or TX current is almost doubled. This function allows to specify if DC-DC or LDO is used for power regulation. The regulation mode is defined by parameter regModeParam . + +## Note: + +This function is clearly related to the hardware implementation of the device. The user should always use this command while knowing what has been implemented at the hardware level. + +Table 13-16: SetRegulatorMode SPI Transaction + +| Byte | 0 | 1 | +| -------------- | ------------ | ------------------------------ | +| Data from host | Opcode= 0x96 | 0: Only LDO used for all modes | + +## 13.1.12 Calibrate Function + +At power up the radio performs calibration of RC64k, RC13M, PLL and ADC. It is however possible to launch a calibration of one or several blocks at any time starting in STDBY\_RC mode. The calibrate function starts the calibration of a block defined by calibParam . + +## Table 13-17: Calibrate SPI Transaction + +Table 13-18: Calibration Setting + +| Byte | 0 | 1 | +| -------------- | ------------- | ---------- | +| Data from host | Opcode = 0x89 | calibParam | + +The total calibration time if all blocks are calibrated is 3.5 ms. The calibration must be launched in STDBY\_RC mode and the BUSY pins will be high during the calibration process. A falling edge of BUSY indicates the end of the procedure. + +| CalibParam | Calibration Setting | +| ---------- | -------------------------------------------------------------------- | +| Bit 0 | 0: RC64k calibration disabled 1: RC64k calibration enabled | +| Bit 1 | 0: RC13Mcalibration disabled 1: RC13M calibration enabled | +| Bit 2 | 0: PLL calibration disabled 1: PLL calibration enabled | +| Bit 3 | 0: ADC pulse calibration disabled 1: ADC pulse calibration enabled | +| Bit 4 | 0: ADC bulk N calibration disabled 1: ADC bulk N calibration enabled | +| Bit 5 | 0: ADC bulk P calibration disabled 1: ADC bulk P calibration enabled | +| Bit 6 | 0: Image calibration disabled 1: Image calibration enabled | +| Bit 7 | 0: RFU | + +## 13.1.13 CalibrateImage + +The function CalibrateImage(...) allows the user to calibrate the image rejection of the device for the device operating frequency band. + +Table 13-19: CalibrateImage SPI Transaction + +| Byte | 0 | 1 | 2 | +| -------------- | ------------- | ----- | ----- | +| Data from host | Opcode = 0x98 | freq1 | freq2 | + +For more details on the specific frequency bands, see Section 9.2.1 "Image Calibration for Specific Frequency Bands" on page 51. + +## 13.1.14 SetPaConfig + +SetPaConfig is controlled as mentioned below. + +Table 13-20: SetPaConfig SPI Transaction + +| Byte | 0 | 1 | 2 | 3 | 4 | +| -------------- | ------------- | ----------- | ----- | ----------------------------------- | ------------------------------- | +| Data from host | Opcode = 0x95 | paDutyCycle | hpMax | deviceSel reserved and always 0x00 | paLut reserved and always 0x01 | + +paDutyCycle controls the duty cycle (conduction angle). The maximum output power, the power consumption, and the harmonics will drastically change with paDutyCycle . The values given across this datasheet are the recommended settings to achieve the best efficiency of the PA. Changing the paDutyCycle will affect the distribution of the power in the harmonics and should be selected to work in conjunction with a given matching network. + +hpMax selects the size of the PA. The maximum output power can be reduced by reducing the value of hpMax . The valid range is between 0x00 and 0x07 and 0x07 is the maximum supported value to achieve +22 dBm output power. Increasing hpMax above 0x07 could cause early aging of the device could damage the device when used in extreme temperatures. + +deviceSel is reserved and has always the value 0x00. + +paLut is reserved and has always the value 0x01. + +## 13.1.14.1 PA Optimal Settings + +PA optimal settings are used to maximize the PA efficiency when the requested output power is lower than the nominal +22 dBm. For example, the maximum output power in Japan is +10 dBm, and in China it is +17 dBm in some bands. Those optimal settings require: + +- · a dedicated matching / PA load impedance +- · a specific tweaking of the PA settings, described in Table 13-21: PA Operating Modes with Optimal Settings + +Table 13-21: PA Operating Modes with Optimal Settings + +| Output Power | paDutyCycle | hpMax | deviceSel | paLut | Value in SetTxParams 1 | +| ------------ | ----------- | ----- | --------- | ----- | ----------------------- | +| +22 dBm | 0x04 | 0x07 | 0x00 | 0x01 | +22 dBm | +| +20 dBm | 0x03 | 0x05 | 0x00 | 0x01 | +22 dBm | +| +17 dBm | 0x02 | 0x03 | 0x00 | 0x01 | +22 dBm | +| +14 dBm | 0x02 | 0x02 | 0x00 | 0x01 | +22 dBm | + +1. See Section 13.4.4 "SetTxParams" on page 79. + +## Note: + +These changes make the use of nominal power either sub-optimal or unachievable. + +## Caution! + +The following restrictions must be observed to avoid voltage overstress on the PA, exceeding the maximum ratings may cause irreversible damage to the device: paDutyCycle should not be higher than 0x04. + +## 13.1.15 SetRxTxFallbackMode + +The command SetRxTxFallbackMode defines into which mode the chip goes after a successful transmission or after a packet reception. + +Table 13-22: SetRxTxFallbackMode SPI Transaction + +| Byte | 0 | 1 | +| -------------- | ------------- | ------------ | +| Data from host | Opcode = 0x93 | fallbackMode | + +The fallbackMode byte definition is given as follows: + +Table 13-23: Fallback Mode Definition + +| Fallback Mode | Value | Description | +| ------------- | ----- | --------------------------------------------------- | +| FS | 0x40 | The radio goes into FS mode after Tx or Rx | +| STDBY\_XOSC | 0x30 | The radio goes into STDBY\_XOSC mode after Tx or Rx | +| STDBY\_RC | 0x20 | The radio goes into STDBY\_RC mode after Tx or Rx | + +By default, the radio will always return in STDBY\_RC unless the configuration is changed by using this command. Changing the default mode from STDBY\_RC to STDBY\_XOSC or FS will only have an impact on the switching time of the radio. + +## 13.2 Registers and Buffer Access + +## 13.2.1 WriteRegister Function + +The command WriteRegister(...) allows writing a block of bytes in a data memory space starting at a specific address. The address is auto incremented after each data byte so that data is stored in contiguous memory locations. The SPI data transfer is described in the following table. + +Table 13-24: WriteRegister SPI Transaction + +| Byte | 0 | 1 | 2 | 3 | 4 | ... | n | +| --------------- | -------------- | ------------- | ------------ | ------------ | -------------- | --- | ------------------- | +| Data from host | Opcode = 0x0D | address[15:8] | address[7:0] | data@address | data@address+1 | ... | data@address+ (n-3) | +| Data to host | RFU | Status | Status | Status | Status | ... | status | + +## 13.2.2 ReadRegister Function + +The command ReadRegister(...) allows reading a block of data starting at a given address. The address is auto-incremented after each byte. The SPI data transfer is described in Table 13-25. Note that the host has to send an NOP after sending the 2 bytes of address to start receiving data bytes on the next NOP sent. + +Table 13-25: ReadRegister SPI Transaction + +| Byte | 0 | 1 | 2 | 3 | 4 | 5 | ... | n | +| --------------- | -------------- | ------------- | ------------ | ------ | ------------ | -------------- | --- | ------------------- | +| Data from host | Opcode = 0x1D | address[15:8] | address[7:0] | NOP | NOP | NOP | ... | NOP | +| Data to host | RFU | Status | Status | Status | data@address | data@address+1 | ... | data@address+ (n-4) | + +## 13.2.3 WriteBuffer Function + +This function is used to store data payload to be transmitted. The address is auto-incremented; when it exceeds the value of 255 it is wrapped back to 0 due to the circular nature of the data buffer. The address starts with an offset set as a parameter of the function. Table 13-26 describes the SPI data transfer. + +Table 13-26: WriteBuffer SPI Transaction + +| Byte | 0 | 1 | 2 | 3 | ... | n | +| -------------- | -------------- | ------ | ----------- | ------------- | --- | ----------------- | +| Data from host | Opcode = 0x0E | offset | data@offset | data@offset+1 | ... | data@offset+(n-2) | +| Data to host | RFU | Status | Status | Status | ... | Status | + +## 13.2.4 ReadBuffer Function + +This function allows reading (n-3) bytes of payload received starting at offset. Note that the NOP must be sent after sending the offset. + +Table 13-27: ReadBuffer SPI Transaction + +| Byte | 0 | 1 | 2 | 3 | 4 | ... | n | +| --------------- | -------------- | ------ | ------ | ----------- | ------------- | --- | ----------------- | +| Data from host | Opcode = 0x1E | offset | NOP | NOP | NOP | ... | NOP | +| Data to host | RFU | Status | Status | data@offset | data@offset+1 | ... | data@offset+(n-3) | + +## 13.3 DIO and IRQ Control Functions + +## 13.3.1 SetDioIrqParams + +This command is used to set the IRQ flag. + +## Table 13-28: SetDioIrqParams SPI Transaction + +Table 13-29: IRQ Registers + +| Byte | 0 | 1-2 | 3-4 | 5-6 | 7-8 | +| -------------- | ----------------------- | -------------- | -------------- | -------------- | -------------- | +| Data from host | SetDioIrqParams (0x08) | Irq Mask(15:0) | DIO1Mask(15:0) | DIO2Mask(15:0) | DIO3Mask(15:0) | + +## 13.3.2 IrqMask + +The IrqMask masks or unmasks the IRQ which can be triggered by the device. By default, all IRQ are masked (all '0') and the user can enable them one by one (or several at a time) by setting the corresponding mask to '1'. + +## 13.3.2.1 DioxMask + +The interrupt causes a DIO to be set if the corresponding bit in DioxMask and the IrqMask are set. As an example, if bit 0 of IrqMask is set to 1 and bit 0 of DIO1Mask is set to 1 then, a rising edge of IRQ source TxDone will be logged in the IRQ register and will appear at the same time on DIO1. + +One IRQ can be mapped to all DIOs, one DIO can be mapped to all IRQs (an OR operation is done) but some IRQ sources will be available only on certain modes of operation and frames. + +In total there are 10 possible interrupt sources depending on the chosen frame and chip mode. Each one of them can be enabled or masked. In addition, every one of them can be mapped to DIO1, DIO2 or DIO3. Note that if DIO2 or DIO3 are used to control the RF Switch or the TCXO, the IRQ will not be generated even if it is mapped to the pins. + +| Bit | IRQ | Description | Modulation | +| --- | ---------------- | ----------------------------------- | ---------- | +| 0 | TxDone | Packet transmission completed | All | +| 1 | RxDone | Packet received | All | +| 2 | PreambleDetected | Preamble detected | All | +| 3 | SyncWordValid | Valid sync word detected | FSK | +| 4 | HeaderValid | Valid LoRa header received | LoRa® | +| 5 | HeaderErr | LoRa header CRC error | LoRa® | +| 6 | CrcErr | Wrong CRC received | All | +| 7 | CadDone | Channel activity detection finished | LoRa® | + +## Table 13-29: IRQ Registers + +Table 13-31: ClearIrqStatus SPI Transaction + +| Bit | IRQ | Description | Modulation | +| --- | ----------- | ------------------------- | ---------- | +| 8 | CadDetected | Channel activity detected | LoRa® | +| 9 | Timeout | Rx or Tx timeout | All | + +A dedicated 10-bit register called IRQ\_reg is used to log IRQ sources. Each position corresponds to one IRQ source as described in the table above. A set of user commands is used to configure IRQ mask, DIOs mapping and IRQ clearing as explained in the following chapters. + +## 13.3.3 GetIrqStatus + +This command returns the value of the IRQ register. + +Table 13-30: GetIrqStatus SPI Transaction + +| Byte | 0 | 1 | 2-3 | +| -------------- | ------------- | ------ | --------------- | +| Data from host | Opcode = 0x12 | NOP | NOP | +| Data to host | RFU | Status | IrqStatus(15:0) | + +## 13.3.4 ClearIrqStatus + +This command clears an IRQ flag in the IRQ register. + +| Byte | 0 | 1-2 | +| -------------- | ------------- | ------------------- | +| Data from host | Opcode = 0x02 | ClearIrqParam(15:0) | + +This function clears an IRQ flag in the IRQ register by setting to 1 the bit of ClearIrqParam corresponding to the same position as the IRQ flag to be cleared. As an example, if bit 0 of ClearIrqParam is set to 1 then IRQ flag at bit 0 of IRQ register is cleared. + +If a DIO is mapped to one single IRQ source, the DIO is cleared if the corresponding bit in the IRQ register is cleared. If DIO is set to 0 with several IRQ sources, then the DIO remains set to one until all bits mapped to the DIO in the IRQ register are cleared. + +## 13.3.5 SetDIO2AsRfSwitchCtrl + +This command is used to configure DIO2 so that it can be used to control an external RF switch. + +## Table 13-32: SetDIO2AsRfSwitchCtrl SPI Transaction + +Table 13-34: SetDIO3asTCXOCtrl SPI Transaction + +| Byte | 0 | 1 | +| -------------- | ------------- | ------ | +| Data from host | Opcode = 0x9D | enable | + +When controlling the external RX switch, the pin DIO2 will toggle accordingly to the internal state machine. DIO2 will be asserted high a few microseconds before the ramp-up of the PA and will be set to zero after the ramp-down of the PA. + +The enable byte definition is given as follows: + +## Table 13-33: Enable Configuration Definition + +| Enable | Description | +| ------ | --------------------------------------------------------------------------------------------------------------------------------------------------- | +| 0 | DIO2 is free to be used as an IRQ | +| 1 | DIO2 is selected to be used to control an RF switch. In this case: DIO2 = 0 in SLEEP , STDBY\_RX, STDBY\_XOSC, FS and RX modes, DIO2 = 1 in TX mode | + +## 13.3.6 SetDIO3AsTCXOCtrl + +This command is used to configure the chip for an external TCXO reference voltage controlled by DIO3. + +| Byte | 0 | 1 | 2-4 | +| -------------- | ------------- | ----------- | ----------- | +| Data from host | Opcode = 0x97 | tcxoVoltage | delay(23:0) | + +When this command is used, the device now controls the TCXO itself through DIO3. When needed (in mode STDBY\_XOSC, FS, TX and RX), the internal state machine will set DIO3 to a predefined output voltage (control through the parameter tcxoVoltage ). Internally, the clock controller will wait for the 32 MHz to appear before releasing the internal state machine. + +The time needed for the 32 MHz to appear and stabilize can be controlled through the parameter delay(23:0) . If the 32 MHz from the TCXO is not detected internally at the end the delay period, the error XOSC\_START\_ERR will be flagged in the error controller. + +The XOSC\_START\_ERR flag will be raised at POR or at wake-up from Sleep mode in a cold-start condition, when a TCXO is used. It is an expected behaviour since the chip is not yet aware of being clocked by a TCXO. The user should simply clear this flag with the ClearDeviceErrors command. + +The tcxoVoltage byte definition is given in as follows: + +Table 13-35: tcxoVoltage Configuration Definition + +| tcxoVoltage | Description | +| ----------- | ------------------------------------- | +| 0x00 | DIO3 outputs 1.6 V to supply the TCXO | +| 0x01 | DIO3 outputs 1.7 V to supply the TCXO | +| 0x02 | DIO3 outputs 1.8 V to supply the TCXO | +| 0x03 | DIO3 outputs 2.2 V to supply the TCXO | +| 0x04 | DIO3 outputs 2.4 V to supply the TCXO | +| 0x05 | DIO3 outputs 2.7 V to supply the TCXO | +| 0x06 | DIO3 outputs 3.0 V to supply the TCXO | +| 0x07 | DIO3 outputs 3.3 V to supply the TCXO | + +The power regulation for tcxoVoltage is configured to be 200 mV below the supply voltage. This means that even if tcxoVoltage is configured above the supply voltage, the supply voltage will be limited by: VDDop > VTCXO + 200 mV + +The timeout duration is defined by + + + +Most TCXO will not be immediately ready at the desired frequency and will suffer from an initial setup time where the frequency is gently drifting toward the wanted frequency. This setup time is different from one TCXO to another and is also dependent on the TCXO manufacturer. To ensure this setup time does not have any effect on the modulation or packets, the delay value will internally gate the 32 MHz coming from the TCXO to give enough time for this initial drift to stabilize. At the end of the delay period, the internal block will stop gating the clock and the radio will carry on to the next step. + +## Note: + +The user should take the delay period into account when going into Tx or Rx mode from STDBY\_RC mode. Indeed, the time needed to switch modes will increase with the duration of delay. To avoid increasing the switching mode time, the user can first set the device in STDBY\_XOSC which will switch on the TCXO and wait for the delay period. Then, the user can set the device into Tx or Rx mode without suffering from any delay additional to the internal processing. + +## 13.4 RF Modulation and Packet-Related Functions + +## 13.4.1 SetRfFrequency + +The command SetRfFrequency(...) is used to set the frequency of the RF frequency mode. + +## Table 13-36: SetRfFrequency SPI Transaction + +Table 13-38: PacketType Definition + +| Byte | 0 | 1-4 | +| -------------- | ------------- | ------------ | +| Data from host | Opcode = 0x86 | RfFreq(31:0) | + +The LSB of Freq is equal to the PLL step which is: + + + +SetRfFrequency(...) defines the chip frequency in FS, TX and RX modes. In RX, the required IF frequency offset is automatically configured. + +## 13.4.2 SetPacketType + +The command SetPacketType(...) sets the LLCC68 radio in LoRa® or in FSK mode. The command SetPacketType(...) must be the first of the radio configuration sequence. The parameter for this command is PacketType . + +Table 13-37: SetPacketType SPI Transaction + +| Byte | 0 | 1 | +| -------------- | ------------- | ---------- | +| Data from host | Opcode = 0x8A | PacketType | + +| PacketType | Value | Modem Mode of Operation | +| ------------------ | ----- | ----------------------- | +| PACKET\_TYPE\_GFSK | 0x00 | GFSK packet type | +| PACKET\_TYPE\_LORA | 0x01 | LORA mode | + +Changing from one mode of operation to another is done using the command SetPacketType(...) . The parameters from the previous mode are not kept internally. The switch from one frame to another must be done in STDBY\_RC mode. + +## 13.4.3 GetPacketType + +The command GetPacketType() returns the current operating packet type of the radio. + +Table 13-39: GetPacketType SPI Transaction + +| Byte | 0 | 1 | 2 | +| -------------- | ------------- | ------ | ---------- | +| Data from host | Opcode = 0x11 | NOP | NOP | +| Data to host | RFU | Status | packetType | + +## 13.4.4 SetTxParams + +This command sets the TX output power by using the parameter power and the TX ramping time by using the parameter RampTime . This command is available for all protocols selected. + +Table 13-40: SetTxParams SPI Transaction + +| Byte | 0 | 1 | 2 | +| -------------- | ------------- | ----- | -------- | +| Data from host | Opcode = 0x8E | power | RampTime | + +The output power is defined as power in dBm in a range of - 9 (0xF7) to +22 (0x16) dBm by step of 1 dB. + +The power ramp time is defined by the parameter RampTime as defined in the following table: + +Table 13-41: RampTime Definition + +| RampTime | Value | RampTime (µs) | +| ---------------- | ----- | ------------- | +| SET\_RAMP\_10U | 0x00 | 10 | +| SET\_RAMP\_20U | 0x01 | 20 | +| SET\_RAMP\_ 40U | 0x02 | 40 | +| SET\_RAMP\_80U | 0x03 | 80 | +| SET\_RAMP\_200U | 0x04 | 200 | +| SET\_RAMP\_800U | 0x05 | 800 | +| SET\_RAMP\_1700U | 0x06 | 1700 | +| SET\_RAMP\_3400U | 0x07 | 3400 | + +## 13.4.5 SetModulationParams + +The command SetModulationParams(...) is used to configure the modulation parameters of the radio. Depending on the packet type selected prior to calling this function, the parameters will be interpreted differently by the chip. + +Table 13-42: SetModulationParams SPI Transaction + +| Byte | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | +| ------------------------------------- | ------------ | ---------- | ---------- | ---------- | ---------- | ---------- | ---------- | ---------- | ---------- | +| Data from host for Modulation Params | Opcode 0x8B | Mod Param1 | Mod Param2 | Mod Param3 | Mod Param4 | Mod Param5 | Mod Param6 | Mod Param7 | Mod Param8 | + +The meaning of the parameter depends on the selected protocol. + +In FSK bitrate (BR) and Frequency Deviation (Fdev) are used for the transmission or reception. Bandwidth is used for reception purpose. The pulse represents the Gaussian filter used to filter the modulation stream on the transmitter side. + +In LoRa® packet type, SF corresponds to the Spreading Factor used for the LoRa® modulation. SF is defined by the parameter Param[1] . BW corresponds to the bandwidth onto which the LoRa® signal is spread. BW in LoRa® is defined by the parameter Param[2] . + +The LoRa® payload is fit with a forward error correcting mechanism which has several levels of encoding. The Coding Rate (CR) is defined by the parameter Param[3] in LoRa®. + +The parameter LdOpt corresponds to the Low Data Rate Optimization (LDRO). This parameter is usually set when the LoRa® symbol time is equal or above 16.38 ms, but can be used if necessary in other situations. See Section 6.1.1.4 "Low Data Rate Optimization" on page 34. + +## 13.4.5.1 GFSK Modulation Parameters + +The tables below provide more details on the GFSK modulation parameters: + +Table 13-43: GFSK ModParam1, ModParam2 & ModParam3 - br + +| BR(23:0) | Description | +| -------------------- | -------------------------- | +| 0x000001 to 0xFFFFFF | br = 32 * Fxtal / bit rate | + +The bit rate is entered with the parameter br which is related to the frequency of the main oscillator (32 MHz). The bit rate range is from 600 b/s up to 300 kb/s with a default value at 4.8 kb/s. + +Table 13-44: GFSK ModParam4 - PulseShape + +| PulseShape | Description | +| ---------- | ----------------- | +| 0x00 | No Filter applied | +| 0x08 | Gaussian BT 0.3 | +| 0x09 | Gaussian BT 0.5 | +| 0x0A | Gaussian BT 0.7 | +| 0x0B | Gaussian BT 1 | + +Table 13-45: GFSK ModParam5 - BandwidthTable 13-46: GFSK ModParam6, ModParam7 & ModParam8 - Fdev + +| Bandwidth | Description | +| --------- | ------------------------------ | +| 0x1F | RX\_BW\_4800 (4.8 kHz DSB) | +| 0x17 | RX\_BW\_5800 (5.8 kHz DSB) | +| 0x0F | RX\_BW\_7300 (7.3 kHz DSB) | +| 0x1E | RX\_BW\_9700 (9.7 kHz DSB) | +| 0x16 | RX\_BW\_11700 (11.7 kHz DSB) | +| 0x0E | RX\_BW\_14600 (14.6 kHz DSB) | +| 0x1D | RX\_BW\_19500 (19.5 kHz DSB) | +| 0x15 | RX\_BW\_23400 (23.4 kHz DSB) | +| 0x0D | RX\_BW\_29300 (29.3 kHz DSB) | +| 0x1C | RX\_BW\_39000 (39 kHz DSB) | +| 0x14 | RX\_BW\_46900 (46.9 kHz DSB) | +| 0x0C | RX\_BW\_58600 (58.6 kHz DSB) | +| 0x1B | RX\_BW\_78200 (78.2 kHz DSB) | +| 0x13 | RX\_BW\_93800 (93.8 kHz DSB) | +| 0x0B | RX\_BW\_117300 (117.3 kHz DSB) | +| 0x1A | RX\_BW\_156200 (156.2 kHz DSB) | +| 0x12 | RX\_BW\_187200 (187.2 kHz DSB) | +| 0x0A | RX\_BW\_234300 (232.3 kHz DSB) | +| 0x19 | RX\_BW\_312000 (312 kHz DSB) | +| 0x11 | RX\_BW\_373600 (373.6 kHz DSB) | +| 0x09 | RX\_BW\_467000 (467 kHz DSB) | + +| Fdev(23:0) | Description | +| -------------------- | ------------------------------------------- | +| 0x000000 to 0xFFFFFF | Fdev = (Frequency Deviation * 2^25) / Fxtal | + + + +## 13.4.5.2 LoRa® Modulation Parameters + +The tables below provide more details on the LoRa® modulation parameters: + +Table 13-47: LoRa® ModParam1- SFTable 13-48: LoRa® ModParam2 - BW + +| SF | Description | +| ---- | ----------- | +| 0x05 | SF5 | +| 0x06 | SF6 | +| 0x07 | SF7 | +| 0x08 | SF8 | +| 0x09 | SF9 | +| 0x0A | SF10 | +| 0x0B | SF11 | + +Table 13-49: LoRa® ModParam3 - CR + +| BW | Description | +| ---- | ---------------------------- | +| 0x04 | LORA\_BW\_125 (125 kHz real) | +| 0x05 | LORA\_BW\_250 (250 kHz real) | +| 0x06 | LORA\_BW\_500 (500 kHz real) | + +Table 13-50: LoRa® ModParam4 - LowDataRateOptimize + +| CR | Description | +| ---- | -------------- | +| 0x01 | LORA\_CR\_4\_5 | +| 0x02 | LORA\_CR\_4\_6 | +| 0x03 | LORA\_CR\_4\_7 | +| 0x04 | LORA\_CR\_4\_8 | + +| LowDataRateOptimize | Description | +| ------------------- | ----------------------- | +| 0x00 | LowDataRateOptimize OFF | +| 0x01 | LowDataRateOptimize ON | + +## 13.4.6 SetPacketParams + +This command is used to set the parameters of the packet handling block. + +Table 13-51: SetPacketParams SPI Transaction + +| Byte | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | +| -------------------------------- | ------------- | ------------- | ------------- | ------------- | ------------- | -------------- | ------------- | ------------- | ------------- | ------------- | +| Data from host for packet type | Opcode = 0x8C | packet Param1 | packet Param2 | packet Param3 | packet Param4 | packet Param5 | packet Param6 | packet Param7 | packet Param8 | packet Param9 | + +## 13.4.6.1 GFSK Packet Parameters + +The tables below provide more details on the GFSK packets parameters: + +Table 13-52: GFSK PacketParam1 & PacketParam2 - PreambleLength + +| PreambleLength (15:0) | Description | +| --------------------- | ------------------------------------------------------------ | +| 0x0001 to 0xFFFF | Transmitted preamble length: number of bits sent as preamble | + +The preamble length is a 16-bit value which represents the number of bytes which will be sent by the radio. Each preamble byte represents an alternate of 0 and 1 and each byte is coded as 0x55. + +Table 13-53: GFSK PacketParam3 - PreambleDetectorLength + +| PreambleDetector | Description | +| ---------------- | -------------------------------- | +| 0x00 | Preamble detector length off | +| 0x04 | Preamble detector length 8 bits | +| 0x05 | Preamble detector length 16 bits | +| 0x06 | Preamble detector length 24 bits | +| 0x07 | Preamble detector length 32 bits | + +The preamble detector acts as a gate to the packet controller, when different from 0x00 (preamble detector length off), the packet controller will only become active if a certain number of preamble bits have been successfully received by the radio. + +Table 13-54: GFSK PacketParam4 - SyncWordLength + +| SyncWordLength | Description | +| -------------- | -------------------------------------------------- | +| 0x00 to 0x40 | Sync Word length in bits (going from 0 to 8 bytes) | + +The Sync Word is directly programmed into the device through simple register access. The table below provide the addresses to program the Sync Word value. + +Table 13-55: Sync Word Programming + +| Sync Word | Register Address | +| --------- | ---------------- | +| Byte 0 | 0x06C0 | +| Byte 1 | 0x06C1 | +| Byte 2 | 0x06C2 | +| Byte 3 | 0x06C3 | +| Byte 4 | 0x06C4 | +| Byte 5 | 0x06C5 | +| Byte 6 | 0x06C6 | +| Byte 7 | 0x06C7 | + +## Table 13-56: GFSK PacketParam5 - AddrComp + +Table 13-58: Broadcast Address Programming + +| AddrComp | Description | +| -------- | ----------------------------------------------------------- | +| 0x00 | Address Filtering Disable | +| 0x01 | Address Filtering activated on Node address | +| 0x02 | Address Filtering activated on Node and broadcast addresses | + +The node address and the broadcast address are directly programmed into the device through simple register access. The tables below provide the addresses to program the values. + +Table 13-57: Node Address Programming + +| | Register Address | Default value | +| ----------- | ---------------- | ------------- | +| NodeAddrReg | 0x06CD | 0x00 | + +| | Register Address | Default value | +| ------------ | ---------------- | ------------- | +| BroadcastReg | 0x06CE | 0x00 | + +Table 13-59: GFSK PacketParam6 - PacketType + +| PacketType | Description | +| ---------- | --------------------------------------------------------------------------------------------- | +| 0x00 | The packet length is known on both sides, the size of the payload is not added to the packet | +| 0x01 | The packet is on variable size, the first byte of the payload will be the size of the packet | + +## Table 13-60: GFSK PacketParam7 - PayloadLength + +Table 13-61: GFSK PacketParam8 - CRCType + +| AddrComp | Description | +| ------------ | -------------------------------------------------------------------------------------------------------- | +| 0x00 to 0xFF | Size of the payload (in bytes) to transmit or maximum size of the payload that the receiver can accept. | + +Table 13-62: CRC Initial Value Programming + +| CRCType | Description | +| ------- | ------------------------------------------------------ | +| 0x01 | CRC\_OFF (No CRC) | +| 0x00 | CRC\_1\_BYTE (CRC computed on 1 byte) | +| 0x02 | CRC\_2\_BYTE(CRC computed on 2 byte) | +| 0x04 | CRC\_1\_BYTE\_INV(CRC computed on 1 byte and inverted) | +| 0x06 | CRC\_2\_BYTE\_INV(CRC computed on 2 byte and inverted) | + +In the LLCC68, the CRC can be fully configured and the polynomial used, as well as the initial values can be entered directly through register access. + +| | Register Address | Default Value | +| ---------------------------- | ---------------- | ------------- | +| CRC MSB Initial Value [15:8] | 0x06BC | 0x1D | +| CRC LSB Initial Value [7:0] | 0x06BD | 0x0F | + +## Table 13-63: CRC Polynomial Programming + +Table 13-67: LoRa® PacketParam3 - HeaderType + +| | Register Address | Default Value | +| ------------------------------- | ---------------- | ------------- | +| CRC MSB polynomial value [15:8] | 0x06BE | 0x10 | +| CRC LSB polynomial value [7:0] | 0x06BF | 0x21 | + +## Table 13-64: GFSK PacketParam9 - Whitening + +| AddrComp | Description | +| -------- | ---------------- | +| 0x00 | No encoding | +| 0x01 | Whitening enable | + +## Table 13-65: Whitening Initial Value + +| Whitening initial value | Register Address | Default Value | +| --------------------------- | ---------------- | ------------- | +| Whitening initial value MSB | 0x06B8 | 0x01 | +| Whitening initial value LSB | 0x06B9 | 0x00 | + +## 13.4.6.2 LoRa® Packet Parameters + +The tables below provide more details on the LoRa® packets parameters: + +## Table 13-66: LoRa® PacketParam1 & PacketParam2 - PreambleLength + +| PreambleLength (15:0) | Description | +| --------------------- | --------------------------------------------------- | +| 0x0001 to 0xFFFF | preamble length: number of symbols sent as preamble | + +The preamble length is a 16-bit value which represents the number of LoRa® symbols which will be sent by the radio. + +| HeaderType | Description | +| ---------- | ---------------------------------------- | +| 0x00 | Variable length packet (explicit header) | +| 0x01 | Fixed length packet (implicit header) | + +When the byte headerType is at 0x00, the payload length, coding rate and the header CRC will be added to the LoRa® header and transported to the receiver. + +Table 13-68: LoRa® PacketParam4 - PayloadLength + +| Payloadlength | Description | +| ------------- | -------------------------------------------------------------------------------------------------------- | +| 0x00 to 0xFF | Size of the payload (in bytes) to transmit or maximum size of the payload that the receiver can accept. | + +## Table 13-69: LoRa® PacketParam5 - CRCType + +Table 13-72: CAD Number of Symbol Definition + +| CRCType | Description | +| ------- | ----------- | +| 0x00 | CRC OFF | +| 0x01 | CRC ON | + +## Table 13-70: LoRa® PacketParam6 - InvertIQ + +| AddrComp | Description | +| -------- | ----------------- | +| 0x00 | Standard IQ setup | +| 0x01 | Inverted IQ setup | + +## 13.4.7 SetCadParams + +The command SetCadConfig(...) defines the number of symbols on which CAD operates. + +Table 13-71: SetCadParams SPI Transaction + +| Byte | 0 | 1 | 2 | 3 | 4 | 5-7 | +| --------------- | ------------- | ------------ | ---------- | --------- | ----------- | ---------------- | +| Data from host | Opcode = 0x88 | cadSymbolNum | cadDetPeak | cadDetMin | cadExitMode | cadTimeout(23:0) | + +The number of symbols used is defined in the following table. + +| cadSymbolNum | Value | Number of Symbols used for CAD | +| ---------------- | ----- | ------------------------------ | +| CAD\_ON\_1\_SYMB | 0x00 | 1 | +| CAD\_ON\_2\_SYMB | 0x01 | 2 | +| CAD\_ON\_4\_SYMB | 0x02 | 4 | + +Table 13-72: CAD Number of Symbol Definition + +| cadSymbolNum | Value | Number of Symbols used for CAD | +| ----------------- | ----- | ------------------------------ | +| CAD\_ON\_8\_SYMB | 0x03 | 8 | +| CAD\_ON\_16\_SYMB | 0x04 | 16 | + +Parameters cadDetPeak and cadDetMin define the sensitivity of the LoRa modem when trying to correlate to actual LoRa preamble symbols. These two settings depend on the LoRa spreading factor and Bandwidth, but also depend on the number of symbols used to validate or not the detection. + +Choosing the right value is not easy and the values selected must be carefully tested to ensure a good detection at sensitivity level, and also to limit the number of false detections. Application note AN1200.48 provides guidance for the selection of these parameters. + +The parameter cadExitMode defines the action to be done after a CAD operation. This is optional. + +Table 13-73: CAD Exit Mode Definition + +| cadExitMode | Value | Operation | +| ----------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| CAD\_ONLY | 0x00 | The chip performs the CAD operation in LoRa®. Once done and whatever the activity on the channel, the chip goes back to STBY\_RC mode. | +| CAD\_RX | 0x01 | The chip performs a CAD operation and if an activity is detected, it stays in RX until a packet is detected or the timer reaches the timeout defined by cadTimeout * 15.625 us | + +The parameter cadTimeout is only used when the CAD is performed with cadExitMode = CAD\_RX. Here, the cadTimeout indicates the time the device will stay in Rx following a successful CAD. + + + +## 13.4.8 SetBufferBaseAddress + +This command sets the base addresses in the data buffer in all modes of operations for the packet handing operation in TX and RX mode. The usage and definition of those parameters are described in the different packet type sections. + +Table 13-74: SetBufferBaseAddress SPI Transaction + +| Byte | 0 | 1 | 2 | +| -------------- | ------------- | --------------- | --------------- | +| Data from host | Opcode = 0x8F | TX base address | RX base address | + +## 13.4.9 SetLoRaSymbNumTimeout + +This command sets the number of symbols used by the modem to validate a successful reception. + +Table 13-75: SetLoRaSymbNumTimeout SPI Transaction + +| Byte | 0 | 1 | +| -------------- | ------------- | ------- | +| Data from host | Opcode = 0xA0 | SymbNum | + +In LoRa® mode, when going into Rx, the modem will lock as soon as a LoRa® symbol has been detected which may lead to false detection. This phenomena is quite rare but nevertheless possible. To avoid this, the command SetLoRaSymbNumTimeout can be used to define the number of symbols which will be used to validate the correct reception of a packet. + +When the SymbNum parameter is set the 0, the modem will validate the reception as soon as a LoRa® Symbol has been detected. + +When SymbNum is different from 0, the modem will wait for a total of SymbNum LoRa® symbol to validate, or not, the correct detection of a LoRa® packet. If the various states of the demodulator are not lock at this moment, the radio will generate the RxTimeout IRQ. + +## 13.5 Communication Status Information + +These commands return the information about the chip status, and received packet such a packet length, received power during packet, several flags indicating if the packet as been correctly received. The returned parameters differ for the LoRa® protocol. + +## 13.5.1 GetStatus + +The host can retrieve chip status directly through the command GetStatus() : this command can be issued at any time and the device returns the status of the device. The command GetStatus() is not strictly necessary since device returns status information also on command bytes. The status byte returned is described in Table 13-76. + +## Table 13-76: Status Bytes Definition + +Table 13-77: GetStatus SPI Transaction + +| 7 | 6:4 | 3:1 | 0 | +| -------- | --------------- | --------------------------------- | -------- | +| Reserved | Chip mode | Command status | Reserved | +| - | 0x0: Unused | 0x0: Reserved | - | +| - | RFU | RFU | - | +| - | 0x2: STBY\_RC | 0x2: Data is available to host 1 | - | +| - | 0x3: STBY\_XOSC | 0x3: Command timeout 2 | - | +| - | 0x4: FS | 0x4: Command processing error 3 | - | +| - | 0x5: RX | 0x5: Failure to execute command 4 | - | +| - | 0x6: TX | 0x6: Command TX done 5 | - | + +- 1. A packet has been successfully received and data can be retrieved +- 2. A transaction from host took too long to complete and triggered an internal watchdog. The watchdog mechanism can be disabled by host; it is meant to ensure all outcomes are flagged to the host MCU. +- 3. Processor was unable to process command either because of an invalid opcode or because an incorrect number of parameters has been provided. +- 4. The command was successfully processed, however the chip could not execute the command; for instance it was unable to enter the specified device mode or send the requested data, +- 5. The transmission of the current packet has terminated + +The SPI transaction for the command GetStatus() is given in the following table. + +| Byte | 0 | 1 | +| -------------- | ------------- | ------ | +| Data from host | Opcode = 0xC0 | NOP | +| Data to host | RFU | Status | + +## 13.5.2 GetRxBufferStatus + +This command returns the length of the last received packet (PayloadLengthRx) and the address of the first byte received (RxStartBufferPointer). It is applicable to all modems. The address is an offset relative to the first byte of the data buffer. + +## Table 13-78: GetRxBufferStatus SPI Transaction + +Table 13-80: Status Fields + +| Byte | 0 | 1 | 2 | 3 | +| -------------- | ------------- | ------ | --------------- | -------------------- | +| Data from host | Opcode = 0x13 | NOP | NOP | NOP | +| Data to host | RFU | Status | PayloadLengthRx | RxStartBufferPointer | + +## 13.5.3 GetPacketStatus + +## Table 13-79: GetPacketStatus SPI Transaction + +| Byte | 0 | 1 | 2 | 3 | 4 | +| --------------------------------- | -------------- | ------ | -------- | -------- | ------------- | +| Data from host | Opcode = 0x14 | NOP | NOP | NOP | NOP | +| Data to host for FSK packet type | RFU | Status | RxStatus | RssiSync | RssiAvg | +| Data to host for LORA packet type | RFU | Status | RssiPkt | SnrPkt | SignalRssiPkt | + +The next table gives the description of the different RSSI and SNR available on the chip depending on the packet type. + +| RSSI | Description | +| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| RxStatus FSK | bit 7: preamble err bit 6: sync err bit 5: adrs err bit 4: crc err bit 3: length err bit 2: abort err bit 1: pkt received | +| RssiSync FSK | RSSI value latched upon the detection of the sync address. [negated, dBm, fixdt(0,8,1)] Actual signal power is -RssiSync/2 (dBm) | +| RssiAvg FSK | RSSI average value over the payload of the received packet. Latched upon the pkt\_done IRQ. [negated, dBm, fixdt(0,8,1)] Actual signal power is -RssiAvg/2 (dBm) | +| RssiPkt LoRa® | Average over last packet received of RSSI Actual signal power is -RssiPkt/2 (dBm) | + +## Table 13-80: Status Fields + +Table 13-82: GetStats SPI Transaction + +| RSSI | Description | +| ------------- | ------------------------------------------------------------------------------------- | +| SnrPkt | Estimation of SNR on last packet received in two's compliment format multiplied by 4. | +| LoRa® | Actual SNR in dB =SnrPkt/4 | +| SignalRssiPkt | Estimation of RSSI of the LoRa® signal (after despreading) on last packet received. | +| LoRa® | Actual Rssi in dB = -SignalRssiPkt/2 | + +## 13.5.4 GetRssiInst + +This command returns the instantaneous RSSI value during reception of the packet. The command is valid for all protocols. + +Table 13-81: GetRssiInst SPI Transaction + +| Byte | 0 | 1 | 2 | +| -------------- | ------------- | ------ | ------------------------------------------------ | +| Data from host | Opcode = 0x15 | NOP | NOP | +| Data to host | RFU | Status | RssiInst Signal power in dBm = -RssiInst/2 (dBm) | + +## 13.5.5 GetStats + +This command returns the number of informations received on a few last packets. The command is valid for all protocols. + +| Byte | 0 | 1 | 2-3 | 4-5 | 6-7 | +| ---------------------------------- | -------------- | ------ | ------------------- | ------------------- | ---------------------- | +| Data from host | Opcode = 0x10 | NOP | NOP | NOP | NOP | +| Data to host in GFSK packet type | RFU | Status | NbPktReceived(15:0) | NbPktCrcError(15:0) | NbPktLengthError(15:0) | +| Data to host in LoRa® packet type | RFU | Status | NbPktReceived(15:0) | NbPktCrcError(15:0) | NbPktHeaderErr(15:0) | + +## 13.5.6 ResetStats + +This command resets the value read by the command GetStats. To execute this command, the opCode is 0x00 followed by 6 zeros (so 7 zeros in total). + +Table 13-83: ResetStats SPI Transaction + +| Byte | 0 | 1-6 | +| -------------- | ------------- | ---- | +| Data from host | opCode = 0x00 | 0x00 | + +## 13.6 Miscellaneous + +## 13.6.1 GetDeviceErrors + +This commands returns possible errors flag that could occur during different chip operation as described below. + +Table 13-84: GetDeviceErrors SPI Transaction + +| Byte | 0 | 1 | 2-3 | +| -------------- | ------------ | ------ | ------------- | +| Data from host | Opcode= 0x17 | NOP | NOP | +| Data to host | RFU | Status | OpError(15:0) | + +The following table gives the meaning of each OpError. + +Table 13-85: OpError Bits + +| OpError | 0 | 1 | +| -------- | ----------------- | ------------------------ | +| bit 0 | RC64K\_CALIB\_ERR | RC64k calibration failed | +| bit 1 | RC13M\_CALIB\_ERR | RC13M calibration failed | +| bit 2 | PLL\_CALIB\_ERR | PLL calibration failed | +| bit 3 | ADC\_CALIB\_ERR | ADC calibration failed | +| bit 4 | IMG\_CALIB\_ERR | IMG calibration failed | +| bit 5 | XOSC\_START\_ERR | XOSC failed to start | +| bit 6 | PLL\_LOCK\_ERR | PLL failed to lock | +| bit 7 | RFU | RFU | +| bit 8 | PA\_RAMP\_ERR | PA ramping failed | +| bit 15:9 | RFU | RFU | + +## 13.6.2 ClearDeviceErrors + +This commands clears all the errors recorded in the device. The errors can not be cleared independently. + +Table 13-86: ClearDeviceErrors SPI Transaction + +| Byte | 0 | 1 | 2 | +| -------------- | ------------ | ------ | ------ | +| Data from host | Opcode= 0x07 | 0x00 | 0x00 | +| Data to host | RFU | Status | Status | + +## 14. Application + +## 14.1 HOST API Basic Read Write Function + +The communication with the LLCC68 is organized around generic functions which allow the user to control the device behavior. Each function is based on an Operational Command (refer throughout this document as 'Opcode'), which is then followed by a set of parameters. The LLCC68 use the BUSY pin to indicate the status of the chip. In the following chapters, it is assumed that host micro-controller has an SPI and access to it via spi.write(data). Data is an 8-bit word. The SPI chip select is defined by NSS, active low. + +## 14.2 Circuit Configuration for Basic Tx Operation + +This chapter describes the sequence of operations needed to send or receive a frame starting from a power up. + +After power up (battery insertion or hard reset) the chip runs automatically a calibration procedure and goes to STDBY\_RC mode. This is indicated by a low state on BUSY pin. From this state the steps are: + +- 1. If not in STDBY\_RC mode, then go to this mode with the command SetStandby(...) +- 2. Define the protocol (LoRa® or FSK) with the command SetPacketType(...) +- 3. Define the RF frequency with the command SetRfFrequency(...) +- 4. Define the Power Amplifier configuration with the command SetPaConfig(...) +- 5. Define output power and ramping time with the command SetTxParams(...) +- 6. Define where the data payload will be stored with the command SetBufferBaseAddress(...) +- 7. Send the payload to the data buffer with the command WriteBuffer(...) +- 8. Define the modulation parameter according to the chosen protocol with the command SetModulationParams(...) 1 +- 9. Define the frame format to be used with the command SetPacketParams(...) 2 +- 10. Configure DIO and IRQ: use the command SetDioIrqParams(...) to select TxDone IRQ and map this IRQ to a DIO (DIO1, DIO2 or DIO3) +- 11. Define Sync Word value: use the command WriteReg(...) to write the value of the register via direct register access +- 12. Set the circuit in transmitter mode to start transmission with the command SetTx() . Use the parameter to enable Timeout +- 13. Wait for the IRQ TxDone or Timeout : once the packet has been sent the chip goes automatically to STDBY\_RC mode +- 14. Clear the IRQ TxDone flag + +## 14.3 Circuit Configuration for Basic Rx Operation + +This chapter describes the sequence of operations needed to receive a frame starting from a power up. This sequence is valid for all protocols. + +After power up (battery insertion or hard reset) the chip run automatically a calibration procedure and goes to STDBY\_RC mode. This is indicated by a low state on BUSY pin. From this state the steps are: + +- 1. If not in STDBY\_RC mode, then set the circuit in this mode with the command SetStandby() +- 2. Define the protocol (LoRa® or FSK) with the command SetPacketType(...) +- 3. Define the RF frequency with the command SetRfFrequency(...) +- 4. Define where the data will be stored inside the data buffer in Rx with the command SetBufferBaseAddress(...) +- 5. Define the modulation parameter according to the chosen protocol with the command SetModulationParams(...) 1 +- 6. Define the frame format to be used with the command SetPacketParams(...) +- 7. Configure DIO and IRQ: use the command SetDioIrqParams(...) to select the IRQ RxDone and map this IRQ to a DIO (DIO1 or DIO2 or DIO3), set IRQ Timeout as well. +- 8. Define Sync Word value: use the command WriteReg(...) to write the value of the register via direct register access. +- 9. Set the circuit in reception mode: use the command SetRx() . Set the parameter to enable timeout or continuous mode +- 10. Wait for IRQ RxDone 2 or Timeout : the chip will stay in Rx and look for a new packet if the continuous mode is selected otherwise it will goes to STDBY\_RC mode. +- 11. In case of the IRQ RxDone , check the status to ensure CRC is correct: use the command GetIrqStatus() + +## Note: + +The IRQ RxDone means that a packet has been received but the CRC could be wrong: the user must check the CRC before validating the packet. + +- 12. Clear IRQ flag RxDone or Timeout : use the command ClearIrqStatus() . In case of a valid packet (CRC OK), get the packet length and address of the first byte of the received payload by using the command GetRxBufferStatus(...) +- 13. In case of a valid packet (CRC OK), start reading the packet + +## 14.4 Issuing Commands in the Right Order + +Most of the commands can be sent in any order except for the radio configuration commands which will set the radio in the proper operating mode. Indeed, it is mandatory to set the radio protocol using the command SetPacketType(...) as a first step before issuing any other radio configuration commands. In a second step, the user should define the modulation parameter according to the chosen protocol with the command SetModulationParams(...). Finally, the user should then select the packet format with the command SetPacketParams(...). + +## Note: + +If this order is not respected, the behavior of the device could be unexpected. + +1. + +Please refer to Section 15.4 + +2. + +Please refer to Section 15.3 + +## 14.5 Application Schematics + +## 14.5.1 Application Design of the LLCC68 with RF Switch + +Figure 14-1: Application Schematic of the LLCC68 with RF Switch + + + +## Note: + +The application schematics presented here are for information only. + +Always refer to the latest reference designs posted on www.semtech.com . + +## Note: + +Recommendations for heat dissipation techniques to be applied to the PCB designs are given in detail in the application note AN1200.37 'Recommendations for Best Performance' on www.semtech.com . + +In miniaturized design implementations where heat dissipations techniques cannot be implemented or the use of the LowDataRateOptimize is not supported, the use of a TCXO will provide a more stable clock reference. + +## 15. Known Limitations + +This section summarizes the known limitations of the LLCC68 chip, and the related workarounds. + +## 15.1 Modulation Quality with 500 kHz LoRa® Bandwidth + +## 15.1.1 Description + +Some sensitivity degradation may be observed on any LoRa device, when receiving signals transmitted by the LLCC68 with a LoRa BW of 500 kHz. + +## 15.1.2 Workaround + +Before any packet transmission, bit #2 at address 0x0889 shall be set to: + +- · 0 if the LoRa BW = 500 kHz +- · 1 for any other LoRa BW +- · 1 for any (G)FSK configuration + +The following pseudo-code can be used before each packet transmission , to properly configure the chip: + +## 15.2 Better Resistance of the LLCC68 Tx to Antenna Mismatch + +## 15.2.1 Description + +The LLCC68 platform embeds a Power Amplifier (PA) clamping mechanism, backing-off the power when over-voltage conditions are detected internally. This method is put in place to protect the internal devices and ensure long-term reliability of the chip. Considering a high-power operation of the LLCC68 (supporting +22dBm on-chip), these 'clamping' devices are overly protective, causing the chip to back-down its output power when even a reasonable mismatch is detected at the PA output. The observation is typically 5 to 6 dB less output power than the expected. + +Note: using the described workaround will improve the chip functionality, but is not required to ensure long-term reliability, which is guaranteed with or without workaround. + +## 15.2.2 Workaround + +During chip initialization, the register TxClampConfig should be modified to optimize the PA clamping threshold. Bits 4-1 must be set to '1111' (default value '0100'). + +This register modification must be done after a Power On Reset, or a wake-up from cold Start. + +The following pseudo-code can be used as a reference to implement the fix: + +## 15.3 Implicit Header Mode Timeout Behavior + +## 15.3.1 Description + +When receiving LoRa® packets in Rx mode with Timeout active, and no header (Implicit Mode), the timer responsible for generating the Timeout (based on the RTC timer) is not stopped on RxDone event. Therefore, it may trigger an unexpected timeout in any subsequent mode where the RTC isn't re-invoked, and therefore reset and re-programmed. + +## 15.3.2 Workaround + +It is advised to add the following commands after ANY Rx with Timeout active sequence, which stop the RTC and clear the timeout event, if any. The register at address 0x0902 will be used to stop the counter, while the register at address 0x0944 will clear the potential event. + +The following pseudo-code can be used as a reference to implement the fix: + +## 15.4 Optimizing the Inverted IQ Operation + +## 15.4.1 Description + +When exchanging LoRa® packets with inverted IQ polarity, some packet losses may be observed for longer packets. + +## 15.4.2 Workaround + +Bit 2 at address 0x0736 must be set to: + +- · '0' when using inverted IQ polarity (see the SetPacketParam(...) command) +- · '1' when using standard IQ polarity + +The following pseudo-code can be used as a reference to implement the fix: + +## 16. Packaging Information + +## 16.1 Package Outline Drawing + +The transceiver is delivered in a 4x4mm QFN package with 0.5 mm pitch: + +Figure 16-1: QFN 4x4 Package Outline Drawing + + + +## 16.2 Package Marking + +Figure 16-2: LLCC68 Marking + + + +Marking for the 4 x 4 mm MLPQ 24 Package + +nnnnnn = Part Number + +yyww = Date Code + +xxxxx = Semtech Lot No. + +## 16.3 Land Pattern + +The recommended land pattern is as follows: + + + +Figure 16-3: QFN 4x4mm Land Pattern + +## 16.4 Reflow Profiles + +Reflow process instructions are available from the Semtech website, at the following address: + +http://www.semtech.com/quality/ir\_reflow\_profiles.html + +The transceiver uses a QFN24 4x4 mm package, also named MLP package. + +## Glossary + +## List of Acronyms and their Meaning + +| Acronym | Meaning | +| ------- | --------------------------------------------------- | +| ACR | Adjacent Channel Rejection | +| ADC | Analog-to-Digital Converter | +| API | Application Programming Interface | +| β | Modulation Index | +| BER | Bit Error Rate | +| BR | Bit Rate | +| BT | Bandwidth-Time bit period product | +| BW | BandWidth | +| CAD | Channel Activity Detection | +| CPOL | Clock Polarity | +| CPHA | Clock Phase | +| CR | Coding Rate | +| CRC | Cyclical Redundancy Check | +| CW | Continuous Wave | +| DC-DC | Direct Current to Direct Current converter | +| DIO | Digital Input / Output | +| DSB | Double Side Band | +| ECO | Engineering Change Order | +| FDA | Frequency Deviation | +| FEC | Forward Error Correction | +| FIFO | First In First Out | +| FSK | Frequency Shift Keying | +| GFSK | Gaussian Frequency Shift Keying | +| GMSK | Gaussian Minimum Shift Keying | +| GDPW | Gross Die Per Wafer | +| IF | Intermediate Frequencies | +| IRQ | Interrupt Request | +| ISM | Industrial, Scientific and Medical (radio spectrum) | +| LDO | Low-Dropout | + +## List of Acronyms and their Meaning + +| Acronym | Meaning | +| ------- | -------------------------------------------------------------------------------------------- | +| LDRO | Low Data Rate Optimization | +| LFSR | Linear-Feedback Shift Register | +| LNA | Low-Noise Amplifier | +| LO | Local Oscillator | +| LoRa® | Long Range Communication the LoRa® Mark is a registered trademark of the Semtech Corporation | +| LSB | Least Significant Bit | +| MISO | Master Input Slave Output | +| MOSI | Master Output Slave Input | +| MSB | Most Significant Bit | +| MSK | Minimum-Shift Keying | +| NOP | No Operation (0x00) | +| NRZ | Non-Return-to-Zero | +| NSS | Slave Select active low | +| OCP | Over Current Protection | +| PA | Power Amplifier | +| PER | Packet Error Rate | +| PHY | Physical Layer | +| PID | Product Identification | +| PLL | Phase-Locked Loop | +| POR | Power On Reset | +| RC13M | 13 MHz Resistance-Capacitance Oscillator | +| RC64k | 64 kHz Resistance-Capacitance Oscillator | +| RFO | Radio Frequency Output | +| RFU | Reserved for Future Use | +| RTC | Real-Time Clock | +| SCK | Serial Clock | +| SF | Spreading Factor | +| SN | Sequence Number | +| SNR | Signal to Noise Ratio | +| SPI | Serial Peripheral Interface | +| SSB | Single Side Bandwidth | + +## List of Acronyms and their Meaning + +| Acronym | Meaning | +| ------- | ------------------------------------------ | +| STDBY | Standby | +| TCXO | Temperature-Compensated Crystal Oscillator | +| XOSC | Crystal Oscillator | diff --git a/docs/DS_LLCC68_V1.0.pdf b/docs/DS_LLCC68_V1.0.pdf new file mode 100644 index 0000000..1d24c41 --- /dev/null +++ b/docs/DS_LLCC68_V1.0.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cd5ad5e647b0e0e58e3bd90fdb663173ef0974dfbd478dd8a79d4101f4f46e17 +size 2194155 diff --git a/docs/DS_SX1261-2_V2_1.pdf b/docs/DS_SX1261-2_V2_1.pdf new file mode 100644 index 0000000..e3b316d --- /dev/null +++ b/docs/DS_SX1261-2_V2_1.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3a82d71d6096a1a6a489a956dc5f9899c305567ae5f62d9c6fb8012f4003b73f +size 4130184 diff --git a/inc/hal_error.hpp b/inc/hal_error.hpp new file mode 100644 index 0000000..95b3db8 --- /dev/null +++ b/inc/hal_error.hpp @@ -0,0 +1,105 @@ +#ifndef D4D45A87_A012_494D_90D2_B4AE0DDE2487 +#define D4D45A87_A012_494D_90D2_B4AE0DDE2487 +#include +#include + +#define APP_ERR_TBL_IT(err) {err, #err} + +namespace app::driver::hal::error { +using t = int; + +// same definition as esp_err.h +// https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/error-codes.html +constexpr t OK = 0; +constexpr t FAILED = -1; + +constexpr t INVALID_ARG = 0x102; /*!< Invalid argument */ +constexpr t INVALID_STATE = 0x103; /*!< Invalid state */ +constexpr t INVALID_SIZE = 0x104; /*!< Invalid size */ +constexpr t NOT_FOUND = 0x105; /*!< Requested resource not found */ +constexpr t NOT_SUPPORTED = 0x106; /*!< Operation or feature not supported */ +constexpr t TIMEOUT = 0x107; /*!< Operation timed out */ +constexpr t INVALID_RESPONSE = 0x108; /*!< Received response was invalid */ +constexpr t INVALID_CRC = 0x109; /*!< CRC or checksum was invalid */ +constexpr t INVALID_VERSION = 0x10A; /*!< Version was invalid */ +constexpr t INVALID_MAC = 0x10B; /*!< MAC address was invalid */ +constexpr t NOT_FINISHED = 0x10C; /*!< Operation has not fully completed */ +constexpr t NOT_ALLOWED = 0x10D; /*!< Operation is not allowed */ + +// new defined generic error codes +constexpr t GENERIC_ERR_BASE = 0x120; +constexpr t AGAIN = GENERIC_ERR_BASE + 1; /*!< Operation failed, retry */ +constexpr t BUSY = GENERIC_ERR_BASE + 2; /*!< Busy */ + +constexpr t SPI_ERR_BASE = 0x1'2000; +// A transaction from host took too long to complete and triggered an internal watchdog. +// The watchdog mechanism can be disabled by host; it is meant to ensure all outcomes are flagged to the host MCU. +constexpr t SPI_TIMEOUT = SPI_ERR_BASE + 2; +// Processor was unable to process command either because of an invalid opcode or +// because an incorrect number of parameters has been provided. +constexpr t SPI_CMD_INVALID = SPI_ERR_BASE + 3; +// The command was successfully processed, however the chip could not execute the command; +// for instance it was unable to enter the specified device mode or send the requested data +constexpr t SPI_CMD_FAILED = SPI_ERR_BASE + 4; + +constexpr t RADIO_ERR_BASE = 0x1'3000; +constexpr t RADIO_CHIP_NOT_FOUND = RADIO_ERR_BASE + 1; +constexpr t RADIO_INVALID_TCXO_VOLTAGE = RADIO_ERR_BASE + 2; +constexpr t RADIO_INVALID_CODING_RATE = RADIO_ERR_BASE + 3; +constexpr t RADIO_INVALID_SPREADING_FACTOR = RADIO_ERR_BASE + 4; +constexpr t RADIO_INVALID_BANDWIDTH = RADIO_ERR_BASE + 5; +constexpr t RADIO_INVALID_FREQUENCY = RADIO_ERR_BASE + 6; +constexpr t RADIO_INVALID_OUTPUT_POWER = RADIO_ERR_BASE + 7; +constexpr t RADIO_INVALID_CAD_RESULT = RADIO_ERR_BASE + 8; +constexpr t RADIO_WRONG_MODERN = RADIO_ERR_BASE + 9; +constexpr t RADIO_RX_TIMEOUT = RADIO_ERR_BASE + 10; +constexpr t RADIO_CRC_MISMATCH = RADIO_ERR_BASE + 11; +constexpr t RADIO_BUSY_TX = RADIO_ERR_BASE + 12; + +constexpr auto error_table = std::to_array>( + { + APP_ERR_TBL_IT(OK), + APP_ERR_TBL_IT(FAILED), + APP_ERR_TBL_IT(INVALID_ARG), + APP_ERR_TBL_IT(INVALID_STATE), + APP_ERR_TBL_IT(INVALID_SIZE), + APP_ERR_TBL_IT(NOT_FOUND), + APP_ERR_TBL_IT(NOT_SUPPORTED), + APP_ERR_TBL_IT(TIMEOUT), + APP_ERR_TBL_IT(INVALID_RESPONSE), + APP_ERR_TBL_IT(INVALID_CRC), + APP_ERR_TBL_IT(INVALID_VERSION), + + APP_ERR_TBL_IT(AGAIN), + APP_ERR_TBL_IT(BUSY), + + APP_ERR_TBL_IT(SPI_TIMEOUT), + APP_ERR_TBL_IT(SPI_CMD_INVALID), + APP_ERR_TBL_IT(SPI_CMD_FAILED), + + APP_ERR_TBL_IT(RADIO_CHIP_NOT_FOUND), + APP_ERR_TBL_IT(RADIO_INVALID_TCXO_VOLTAGE), + APP_ERR_TBL_IT(RADIO_INVALID_CODING_RATE), + APP_ERR_TBL_IT(RADIO_INVALID_SPREADING_FACTOR), + APP_ERR_TBL_IT(RADIO_INVALID_BANDWIDTH), + APP_ERR_TBL_IT(RADIO_INVALID_FREQUENCY), + APP_ERR_TBL_IT(RADIO_INVALID_OUTPUT_POWER), + APP_ERR_TBL_IT(RADIO_INVALID_CAD_RESULT), + APP_ERR_TBL_IT(RADIO_WRONG_MODERN), + APP_ERR_TBL_IT(RADIO_RX_TIMEOUT), + APP_ERR_TBL_IT(RADIO_CRC_MISMATCH), + APP_ERR_TBL_IT(RADIO_BUSY_TX), + }); + +inline const char *err_to_str(t err) { + for (const auto &[code, name] : error_table) { + if (code == err) { + return name; + } + } + return "UNKNOWN"; +} +} + +#undef APP_ERR_TBL_IT +#endif /* D4D45A87_A012_494D_90D2_B4AE0DDE2487 */ diff --git a/inc/hal_gpio.hpp b/inc/hal_gpio.hpp new file mode 100644 index 0000000..81e3991 --- /dev/null +++ b/inc/hal_gpio.hpp @@ -0,0 +1,33 @@ +// +// Created by Kurosu Chan on 2024/1/15. +// + +#ifndef B0AACD4A_5B6F_438C_B35C_C49E28A07356 +#define B0AACD4A_5B6F_438C_B35C_C49E28A07356 + +#include + +namespace app::driver::hal::gpio { +using pin_t = gpio_num_t; +using mode_t = gpio_mode_t; + +enum class Mode { + INPUT = GPIO_MODE_INPUT, + OUTPUT = GPIO_MODE_OUTPUT, +}; + +constexpr bool HIGH = true; +constexpr bool LOW = false; + +inline void digital_write(const pin_t pin, const bool val) { + gpio_set_level(pin, val); +} + +inline bool digital_read(const pin_t pin) { return gpio_get_level(pin); } + +inline void set_mode(const pin_t pin, const Mode mode) { + gpio_set_direction(pin, static_cast(mode)); +} +} // namespace app::driver::hal::gpio + +#endif /* B0AACD4A_5B6F_438C_B35C_C49E28A07356 */ diff --git a/inc/hal_spi.hpp b/inc/hal_spi.hpp new file mode 100644 index 0000000..583afc8 --- /dev/null +++ b/inc/hal_spi.hpp @@ -0,0 +1,118 @@ +// +// Created by Kurosu Chan on 2024/1/17. +// +#ifndef B7431E31_4075_4B09_B4B3_EAD0234EFB40 +#define B7431E31_4075_4B09_B4B3_EAD0234EFB40 +#include +#include +#include "radiolib_definitions.hpp" +#include "llcc68_definitions.hpp" +#include "utils/app_result.hpp" +#include "hal_error.hpp" +#include "app_const_llcc68.hpp" + + +namespace app::driver::hal::spi { +template +using Result = app::utils::Result; +using Unit = app::utils::Unit; +constexpr auto TAG = "spi"; + +constexpr auto MOSI_PIN = llcc68::MOSI_PIN; +constexpr auto MISO_PIN = llcc68::MISO_PIN; +constexpr auto SCLK_PIN = llcc68::SCLK_PIN; +constexpr auto BUSY_PIN = llcc68::BUSY_PIN; +constexpr auto CS_PIN = llcc68::CS_PIN; +using error_t = error::t; +using ue_t = app::utils::unexpected; + +constexpr uint8_t SPI_READ_COMMAND = RADIOLIB_SX126X_CMD_READ_REGISTER; +constexpr uint8_t SPI_WRITE_COMMAND = RADIOLIB_SX126X_CMD_WRITE_REGISTER; +constexpr uint8_t SPI_NOP_COMMAND = RADIOLIB_SX126X_CMD_NOP; +constexpr uint8_t SPI_WRITE_BUFFER_CMD = RADIOLIB_SX126X_CMD_WRITE_BUFFER; +constexpr uint8_t SPI_READ_BUFFER_CMD = RADIOLIB_SX126X_CMD_READ_BUFFER; + +constexpr size_t DEFAULT_TIMEOUT_MS = 1000; + +std::span mut_shared_buffer(); + +/*! + \brief Initialize the SPI interface. +*/ +void init(); + + +inline error_t status_to_err(const uint8_t status) { + const auto st = *reinterpret_cast(&status); + switch (st.command_status) { + case llcc68::CommandStatus::COMMAND_TIMEOUT: + return error::SPI_TIMEOUT; + case llcc68::CommandStatus::FAILURE_TO_EXECUTE_COMMAND: + return error::SPI_CMD_FAILED; + case llcc68::CommandStatus::COMMAND_PROCESSING_ERROR: +#ifdef APP_SPI_DISABLE_INVALID_STATUS_CHECK + return error::OK; +#else + return error::SPI_CMD_INVALID; +#endif + default: + return error::OK; + } +} + +/*! + See also LLCC68 chapter 10 Host Controller Interface +*/ + +/*! Common API patterns */ + +/*! + READ (no parameters, only OPCODE, but has data and status) + + OPCODE() -> (status, data) +*/ +Result +read_stream(uint8_t cmd, std::span data, const size_t timeout_ms = DEFAULT_TIMEOUT_MS); + +/*! + WRITE (having parameters, no excessive return values but status) + + OPCODE(parameters) -> status +*/ +Result +write_stream(uint8_t cmd, std::span data, const size_t timeout_ms = DEFAULT_TIMEOUT_MS); + +/*! + READ REGISTER + + READ_REGISTER(register, offset) -> (status, data) +*/ +Result +read_register(uint16_t reg, std::span data, const size_t timeout_ms = DEFAULT_TIMEOUT_MS); + +/*! + WRITE REGISTER + + WRITE_REGISTER(register, offset, data) -> status +*/ +Result +write_register(uint16_t offset, std::span data, const size_t timeout_ms = DEFAULT_TIMEOUT_MS); + +/*! + WRITE BUFFER + + WRITE_BUFFER(offset, data) -> status +*/ +Result +write_buffer(uint8_t offset, std::span data_from_host, const size_t timeout_ms = DEFAULT_TIMEOUT_MS); + +/*! + READ BUFFER + + READ_BUFFER(offset) -> (status, data) +*/ +Result, error_t> +read_buffer(uint8_t offset, uint8_t size, const size_t timeout_ms = DEFAULT_TIMEOUT_MS); +} + +#endif /* B7431E31_4075_4B09_B4B3_EAD0234EFB40 */ diff --git a/inc/llcc68.hpp b/inc/llcc68.hpp new file mode 100644 index 0000000..163d2c9 --- /dev/null +++ b/inc/llcc68.hpp @@ -0,0 +1,1263 @@ +// +// Created by Kurosu Chan on 2024/1/17. +// + +#ifndef D07297EB_4033_481B_BCFA_1D40899340D0 +#define D07297EB_4033_481B_BCFA_1D40899340D0 +#include +#include +#include +#include +#include +#include +#include +#include "hal_gpio.hpp" +#include "app_const_llcc68.hpp" +#include "radiolib_definitions.hpp" +#include "utils/app_utils.hpp" +#include "utils/app_result.hpp" +#include "utils/app_instant.hpp" +#include "llcc68_definitions.hpp" +#include "hal_spi.hpp" +#include "hal_error.hpp" + +/** + * Both, RxBaseAddr and TxBaseAddr are set using the command SetBufferBaseAddresses(...). + * + * By default RxBaseAddr and TxBaseAddr are initialized at address 0x00. + * + * Due to the contiguous nature of the data buffer, the base addresses for Tx and Rx are fully configurable across the 256-byte + * memory area. Each pointer can be set independently anywhere within the buffer. To exploit the maximum data buffer size + * in transmit or receive mode, the whole data buffer can be used in each mode by setting the base addresses TxBaseAddr and + * RxBaseAddr at the bottom of the memory (0x00). + * The data buffer is cleared when the device is put into Sleep mode (implying no access). The data is retained in all other + * modes of operation. + * + * The data buffer is acceded via the command WriteBuffer(...) and ReadBuffer(...). In this function the parameter offset defines + * the address pointer of the first data to be written or read. Offset zero defines the first position of the data buffer. + * Before any read or write operation it is hence necessary to initialize this offset to the corresponding beginning of the buffer. + * Upon reading or writing to the data buffer the address pointer will then increment automatically. + * + * Two possibilities exist to obtain the offset value: + * + * - First is to use the RxBaseAddr value since the user defines it before receiving a payload. + * - Second, offset can be initialized with the value of RxStartBufferPointer returned by GetRxbufferStatus(...) command. + */ +constexpr auto DEFAULT_TX_BUFFER_ADDRESS = 0x00; +constexpr auto DEFAULT_RX_BUFFER_ADDRESS = 0x80; +static_assert(DEFAULT_RX_BUFFER_ADDRESS < 0xff, "DEFAULT_RX_BUFFER_ADDRESS must be less than 0xff"); +constexpr auto MAX_RX_BUFFER_SIZE = 0xff - DEFAULT_RX_BUFFER_ADDRESS; + +/** + * \brief return if STATEVAR has value, otherwise return the error + * \param STATEVAR the variable to check + * \note this macro is used to return the error from a function + * \sa https://en.cppreference.com/w/cpp/utility/expected/operator_bool + */ +#define APP_RADIO_RETURN_ERR(STATEVAR) \ + { \ + if (not(STATEVAR.has_value())) { \ + return (ue_t{STATEVAR.error()}); \ + } \ + } + + +/** + * \brief return if STATEVAR has value, otherwise return the error + * \param STATEVAR the variable to check + * \param ctx the context of the error + * \param ... the arguments to pass to the error message + * \note this macro is used to return the error from a function and log the error message + */ +#define APP_RADIO_RETURN_ERR_CTX(STATEVAR, ctx, ...) \ + { \ + if (not(STATEVAR.has_value())) { \ + ESP_LOGE(TAG, ctx, __VA_ARGS__); \ + return (ue_t{STATEVAR.error()}); \ + } \ + } + +namespace app::driver::llcc68 { +constexpr auto TAG = "lora"; +using namespace app::driver::hal; + +template +using Instant = app::utils::Instant; + +template +using Result = app::utils::Result; +using Unit = app::utils::Unit; + +using millisecond = std::chrono::duration; + +/** + * \brief Whether to use only LDO regulator (true) or DC-DC regulator (false) + */ +constexpr bool USE_REGULATOR_LDO = false; +/*! + \brief Whether the module has an XTAL (true) or TCXO (false) + + TCXO (Temperature Compensated Crystal Oscillator) will be disabled if this is set to true +*/ +constexpr bool USE_XTAL = true; +constexpr llcc68::TcxoVoltage DEFAULT_TCXO_VOLTAGE = TcxoVoltage::V_1_6; + +using error_t = spi::error_t; +using ue_t = spi::ue_t; + +/** + * \brief maintaining the transmission state for async transmission + */ +struct transmit_state_t { + bool is_transmitting = false; + Instant<> start{}; + uint16_t expected_duration_ms{}; +}; + +/** + * @brief private namespace; Don't use anything in this namespace outside of this file + */ +namespace details { + extern uint32_t __tcxo_delay__; +} + +void init_exti(); + +constexpr auto delay_ms = app::utils::delay_ms; + +/*! + \brief Sets the module to standby mode. +*/ +inline Result standby() { + // 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). + constexpr uint8_t data[] = {RADIOLIB_SX126X_STANDBY_RC}; + return spi::write_stream(RADIOLIB_SX126X_CMD_SET_STANDBY, data); +} + +inline Result +reset() { + if (RST_PIN == GPIO_NUM_NC) { + return {}; + } + gpio::set_mode(RST_PIN, gpio::Mode::OUTPUT); + gpio::digital_write(RST_PIN, false); + delay_ms(10); + gpio::digital_write(RST_PIN, true); + delay_ms(10); + const auto instant = Instant<>{}; + constexpr auto INTERVAL = std::chrono::duration{spi::DEFAULT_TIMEOUT_MS}; + decltype(standby()) res = ue_t{error_t{error::FAILED}}; + while (not res && instant.elapsed() < INTERVAL) { + res = standby(); + if (res.has_value()) { + break; + } + // don't spam the module + delay_ms(100); + } + if (!res) { + return res; + } else { + return {}; + } +}; + +inline Result +read_register(const uint16_t addr, std::span data) { + return spi::read_register(addr, data); +} + +inline Result +write_register(const uint16_t addr, std::span data) { + return spi::write_register(addr, data); +} + +Result inline write_buffer(std::span data, const uint8_t offset = 0x00) { + return spi::write_buffer(offset, data); +} + +inline Result +get_irq_status() { + uint8_t data[] = {0x00, 0x00}; + const auto res = spi::read_stream(RADIOLIB_SX126X_CMD_GET_IRQ_STATUS, data); + APP_RADIO_RETURN_ERR(res); + return static_cast(data[0] << 8) | static_cast(data[1]); +} + +inline Result +clear_irq_status(const uint16_t mask = RADIOLIB_SX126X_IRQ_ALL) { + const uint8_t data[] = {static_cast((mask >> 8) & 0xff), static_cast(mask & 0xff)}; + return spi::write_stream(RADIOLIB_SX126X_CMD_CLEAR_IRQ_STATUS, data); +} + +/** + * \brief set RF frequency by writing the raw value to registers + * \param frf the raw value to write to registers + * \note don't use this function directly, use `set_frequency` instead + * \sa set_frequency + * \sa set_frequency_raw + */ +inline Result +set_rf_frequency(const uint32_t frf) { + const uint8_t data[] = {static_cast((frf >> 24) & 0xFF), static_cast((frf >> 16) & 0xFF), static_cast((frf >> 8) & 0xFF), static_cast(frf & 0xFF)}; + return spi::write_stream(RADIOLIB_SX126X_CMD_SET_RF_FREQUENCY, data); +} + +inline Result +set_buffer_base_address(const uint8_t txBaseAddress = DEFAULT_TX_BUFFER_ADDRESS, const uint8_t rxBaseAddress = DEFAULT_RX_BUFFER_ADDRESS) { + const uint8_t data[] = {txBaseAddress, rxBaseAddress}; + return spi::write_stream(RADIOLIB_SX126X_CMD_SET_BUFFER_BASE_ADDRESS, data); +} + +inline Result +set_regulator_mode(const uint8_t mode) { + const uint8_t data[] = {mode}; + return spi::write_stream(RADIOLIB_SX126X_CMD_SET_REGULATOR_MODE, data); +} + +inline Result +get_status() { + uint8_t data = 0; + const auto res = spi::read_stream(RADIOLIB_SX126X_CMD_GET_STATUS, std::span{&data, 1}); + APP_RADIO_RETURN_ERR(res); + return *reinterpret_cast(&data); +} + +inline Result +get_packet_status() { + uint8_t data[3] = {0, 0, 0}; + const auto res = spi::read_stream(RADIOLIB_SX126X_CMD_GET_PACKET_STATUS, data, 3); + APP_RADIO_RETURN_ERR(res); + return packet_status_t{ + .raw = {data[0], data[1], data[2]}}; +} + +inline Result +get_device_errors() { + uint8_t data[] = {0x00, 0x00}; + const auto res = spi::read_stream(RADIOLIB_SX126X_CMD_GET_DEVICE_ERRORS, data); + APP_RADIO_RETURN_ERR(res); + // assuming little endian (as most microcontrollers are) + uint16_t opError_le = data[0] << 8 | data[1]; + auto opError = *reinterpret_cast(&opError_le); + return opError; +} + +/** + * \brief This commands clears all the errors recorded in the device. The errors can not be cleared independently. + */ +inline Result +clear_device_errors() { + constexpr uint8_t data[] = {RADIOLIB_SX126X_CMD_NOP, RADIOLIB_SX126X_CMD_NOP}; + return spi::write_stream(RADIOLIB_SX126X_CMD_CLEAR_DEVICE_ERRORS, data); +} + +/** + * \brief Set the RF frequency in MHz + * \param freq the frequency to set + * \sa set_frequency + * + * \note \text{value}=\frac{f_{\text{XTAL}}}{2^{25}}\cdot f_{\text{RF}} + */ +inline constexpr uint32_t frequency_raw(const freq_t freq) { + uint32_t frf = (freq * (uint32_t(1) << RADIOLIB_SX126X_DIV_EXPONENT)) / RADIOLIB_SX126X_CRYSTAL_FREQ; + return frf; +} + +/** + * \brief Set the RF frequency in MHz, with necessary calibration and checks + * \param freq the frequency to set, which must be in range [150, 960] + * \param calibrate whether to calibrate the image + * \sa set_frequency_raw + */ +inline Result +set_frequency(freq_t freq, const bool calibrate = true) { + using f_t = decltype(freq); + if (!valid_freq(freq)) { + return ue_t{error_t{error::RADIO_INVALID_FREQUENCY}}; + } + if (calibrate) { + uint8_t data[2]; + if (freq > f_t{900}) { + data[0] = RADIOLIB_SX126X_CAL_IMG_902_MHZ_1; + data[1] = RADIOLIB_SX126X_CAL_IMG_902_MHZ_2; + } else if (freq > f_t{850.0}) { + data[0] = RADIOLIB_SX126X_CAL_IMG_863_MHZ_1; + data[1] = RADIOLIB_SX126X_CAL_IMG_863_MHZ_2; + } else if (freq > f_t{770.0}) { + data[0] = RADIOLIB_SX126X_CAL_IMG_779_MHZ_1; + data[1] = RADIOLIB_SX126X_CAL_IMG_779_MHZ_2; + } else if (freq > f_t{460.0}) { + data[0] = RADIOLIB_SX126X_CAL_IMG_470_MHZ_1; + data[1] = RADIOLIB_SX126X_CAL_IMG_470_MHZ_2; + } else { + data[0] = RADIOLIB_SX126X_CAL_IMG_430_MHZ_1; + data[1] = RADIOLIB_SX126X_CAL_IMG_430_MHZ_2; + } + auto res = spi::write_stream(RADIOLIB_SX126X_CMD_CALIBRATE_IMAGE, data); + APP_RADIO_RETURN_ERR(res); + delay_ms(5); + } + + static freq_t last_freq = 0; + static uint32_t raw_last_freq = 0; + if (last_freq != freq) { + last_freq = freq; + raw_last_freq = frequency_raw(freq); + } + return set_rf_frequency(raw_last_freq); +} + +inline Result +get_packet_type() { + uint8_t data = 0; + const auto res = spi::read_stream(RADIOLIB_SX126X_CMD_GET_PACKET_TYPE, std::span{&data, 1}); + APP_RADIO_RETURN_ERR(res); + return data; +} + +/** + * \brief Some sensitivity degradation may be observed on any LoRa device, when receiving signals transmitted by the LLCC68 with +a LoRa BW of 500 kHz. + * \note should be used before each packet transmission, to properly configure the chip + * \param bw the bandwidth + * \sa SX1262/SX1268 datasheet, chapter 15 Known Limitations, section 15.1 for details + */ +inline Result +fix_sensitivity(const uint8_t bw) { + uint8_t sensitivityConfig = 0; + read_register(RADIOLIB_SX126X_REG_SENSITIVITY_CONFIG, std::span{&sensitivityConfig, 1}); + + // bit 2 + constexpr auto HACK_MASK = 0x04; + + if (bw == RADIOLIB_SX126X_LORA_BW_500_0) { + sensitivityConfig &= ~HACK_MASK; + } else { + sensitivityConfig |= HACK_MASK; + } + return write_register(RADIOLIB_SX126X_REG_SENSITIVITY_CONFIG, std::span{&sensitivityConfig, 1}); +} + +/** + * @brief fixes overly eager PA clamping + * + * fixes overly eager PA clamping + * see SX1262/SX1268 datasheet, chapter 15 Known Limitations, section 15.2 for details + * + * The LLCC68 platform embeds a Power Amplifier (PA) clamping mechanism, + * backing-off the power when over-voltage conditions are detected internally. + * This method is put in place to protect the internal devices and ensure + * long-term reliability of the chip. Considering a high-power operation of the + * LLCC68 (supporting +22dBm on-chip), these "clamping" devices are overly + * protective, causing the chip to back-down its output power when even a + * reasonable mismatch is detected at the PA output. The observation is + * typically 5 to 6 dB less output power than the expected. + * + * @param enable + * @return Result + */ +inline Result +fix_pa_clamping(const bool enable = true) { + uint8_t clampConfig = 0; + read_register(RADIOLIB_SX126X_REG_TX_CLAMP_CONFIG, std::span{&clampConfig, 1}); + + // apply or undo workaround + if (enable) { + clampConfig |= 0x1E; + } else { + clampConfig = (clampConfig & ~0x1E) | 0x08; + } + + return write_register(RADIOLIB_SX126X_REG_TX_CLAMP_CONFIG, std::span{&clampConfig, 1}); +} + +/** + * \brief fixes inverted IQ on SX1262 rev. B + * \param iq_config RADIOLIB_SX126X_LORA_IQ_STANDARD or RADIOLIB_SX126X_LORA_IQ_INVERTED + * \see SX1262/SX1268 datasheet, chapter 15 Known Limitations, section 15.4 for details + */ +inline Result +fix_inverted_iq(const uint8_t iq_config) { + // read current IQ configuration + uint8_t iqConfig = 0; + const auto res = read_register(RADIOLIB_SX126X_REG_IQ_CONFIG, std::span{&iqConfig, 1}); + APP_RADIO_RETURN_ERR(res); + + // Bit 2 at address 0x0736 must be set to + // 0 when using inverted IQ + // 1 when using standard IQ + if (iqConfig == RADIOLIB_SX126X_LORA_IQ_INVERTED) { + iqConfig &= ~0x04; + } else { + iqConfig |= 0x04; + } + + return write_register(RADIOLIB_SX126X_REG_IQ_CONFIG, std::span{&iqConfig, 1}); +} + +struct irq_params_t { + // The IrqMask masks or unmasks the IRQ which can be triggered by the device. By default, all IRQ are masked (all ‘0’) and the + // user can enable them one by one (or several at a time) by setting the corresponding mask to ‘1’. + uint16_t irqMask = RADIOLIB_SX126X_IRQ_NONE; + // The interrupt causes a DIO to be set if the corresponding bit in DioxMask + // and the IrqMask are set. As an example, if bit 0 of IrqMask is set to 1 + // and bit 0 of DIO1Mask is set to 1 then, a rising edge of IRQ source + // TxDone will be logged in the IRQ register and will appear at the same + // time on DIO1. + // + // One IRQ can be mapped to all DIOs, one DIO can be mapped to all IRQs (an + // OR operation is done) but some IRQ sources will be available only on + // certain modes of operation and frames. + uint16_t dio1Mask = RADIOLIB_SX126X_IRQ_NONE; + uint16_t dio2Mask = RADIOLIB_SX126X_IRQ_NONE; + uint16_t dio3Mask = RADIOLIB_SX126X_IRQ_NONE; +}; + +/** + * @brief The IrqMask masks or unmasks the IRQ which can be triggered by the device. + * + * By default, all IRQ are masked (all ‘0’) and the user can enable them one by one (or several at a time) by setting the corresponding mask to ‘1’. + */ +inline Result +set_dio_irq_params(const irq_params_t &irq_params) { + const auto irqMask = irq_params.irqMask; + const auto dio1Mask = irq_params.dio1Mask; + const auto dio2Mask = irq_params.dio2Mask; + const auto dio3Mask = irq_params.dio3Mask; + const uint8_t data[8] = {static_cast((irqMask >> 8) & 0xFF), static_cast(irqMask & 0xFF), + static_cast((dio1Mask >> 8) & 0xFF), static_cast(dio1Mask & 0xFF), + static_cast((dio2Mask >> 8) & 0xFF), static_cast(dio2Mask & 0xFF), + static_cast((dio3Mask >> 8) & 0xFF), static_cast(dio3Mask & 0xFF)}; + return spi::write_stream(RADIOLIB_SX126X_CMD_SET_DIO_IRQ_PARAMS, data); +} + +/*! + \brief Set DIO2 to function as RF switch (default in Semtech example designs). +*/ +inline Result +set_dio2_as_rf_switch(const bool en) { + const uint8_t data[] = {en ? static_cast(RADIOLIB_SX126X_DIO2_AS_RF_SWITCH) : static_cast(RADIOLIB_SX126X_DIO2_AS_IRQ)}; + return spi::write_stream(RADIOLIB_SX126X_CMD_SET_DIO2_AS_RF_SWITCH_CTRL, data); +} + +/** + * \brief set packet type and do the calibration + */ +inline Result config_packet_type(const parameters_t ¶ms) { + auto res = set_buffer_base_address(); + APP_RADIO_RETURN_ERR(res); + constexpr auto mod = RADIOLIB_SX126X_PACKET_TYPE_LORA; + uint8_t data[7]; + data[0] = mod; + res = spi::write_stream(RADIOLIB_SX126X_CMD_SET_PACKET_TYPE, std::span{data, 1}); + APP_RADIO_RETURN_ERR(res); + data[0] = RADIOLIB_SX126X_RX_TX_FALLBACK_MODE_STDBY_RC; + res = spi::write_stream(RADIOLIB_SX126X_CMD_SET_RX_TX_FALLBACK_MODE, std::span{data, 1}); + APP_RADIO_RETURN_ERR(res); + + data[0] = RADIOLIB_SX126X_CAD_ON_8_SYMB; + data[1] = params.sf + 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; + res = spi::write_stream(RADIOLIB_SX126X_CMD_SET_CAD_PARAMS, std::span{data, 7}); + APP_RADIO_RETURN_ERR(res); + + clear_irq_status(); + // default init to be non-interrupt + constexpr auto irq_params = irq_params_t{}; + res = set_dio_irq_params(irq_params); + APP_RADIO_RETURN_ERR(res); + + data[0] = RADIOLIB_SX126X_CALIBRATE_ALL; + res = spi::write_stream(RADIOLIB_SX126X_CMD_CALIBRATE, std::span{data, 1}); + APP_RADIO_RETURN_ERR(res); + + // The total calibration time if all blocks are calibrated is 3.5 ms. The + // calibration must be launched in STDBY_RC mode and the BUSY pins will be + // high during the calibration process. A falling edge of BUSY indicates the + // end of the procedure. + delay_ms(5); + while (gpio::digital_read(BUSY_PIN) == gpio::HIGH) {} + return {}; +} + +/*! + \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. +*/ +constexpr uint32_t +calc_time_on_air(const size_t len, const calc_time_on_air_t params) { + // 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 + const auto bw_ = float{bw_khz(params.bw)}; + const auto ubw = static_cast(bw_ * 10); + const auto sf = params.sf; + const uint32_t symbolLength_us = (static_cast(1000 * 10) << sf) / ubw; + uint8_t sfCoeff1_x4 = 17; // (4.25 * 4) + uint8_t sfCoeff2 = 8; + if (sf == 5 || sf == 6) { + sfCoeff1_x4 = 25; // 6.25 * 4 + sfCoeff2 = 0; + } + uint8_t sfDivisor = 4 * sf; + if (symbolLength_us >= 16000) { + sfDivisor = 4 * (sf - 2); + } + constexpr int8_t bitsPerCrc = 16; + constexpr int8_t N_symbol_header = params.header_type == 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 = static_cast(8) * len + params.crc_type * bitsPerCrc - 4 * sf + sfCoeff2 + N_symbol_header; + if (bitCount < 0) { + bitCount = 0; + } + // add (sfDivisor) - 1 to the numerator to give integer CEIL(...) + const uint16_t nPreCodedSymbols = (bitCount + (sfDivisor - 1)) / (sfDivisor); + + const auto de = std::get<1>(cr_to_ratio(params.cr)); + // preamble can be 65k, therefore nSymbol_x4 needs to be 32 bit + const uint32_t nSymbol_x4 = (params.preamble_length + 8) * 4 + sfCoeff1_x4 + nPreCodedSymbols * de * 4; + + return symbolLength_us * nSymbol_x4 / 4; +} + +/** + * \brief + * \param preamble_length LoRa preamble length in symbols. Allowed values range from 1 to 65535 + * \param payload_length implicit header length; 0xff for explicit header + * \param crc_type RADIOLIB_SX126X_LORA_CRC_ON or RADIOLIB_SX126X_LORA_CRC_OFF + * \param hdr_type RADIOLIB_SX126X_LORA_HEADER_EXPLICIT or RADIOLIB_SX126X_LORA_HEADER_IMPLICIT + * \note This is for LoRA packet. setModulationParamsFSK (which is for GFSK) won't be ported. + */ +inline Result +set_packet_params(const uint16_t preamble_length = 8, + const uint8_t payload_length = 0xff, + const uint8_t crc_type = RADIOLIB_SX126X_LORA_CRC_ON, + const uint8_t hdr_type = RADIOLIB_SX126X_LORA_HEADER_EXPLICIT) { + if constexpr (DEFAULT_IQ_TYPE == RADIOLIB_SX126X_LORA_IQ_INVERTED) { + const auto res = fix_inverted_iq(RADIOLIB_SX126X_LORA_IQ_INVERTED); + APP_RADIO_RETURN_ERR(res); + } + const uint8_t data[] = {static_cast((preamble_length >> 8) & 0xff), + static_cast(preamble_length & 0xff), + crc_type, payload_length, + hdr_type, DEFAULT_IQ_TYPE}; + return spi::write_stream(RADIOLIB_SX126X_CMD_SET_PACKET_PARAMS, data); +} + +/** + * \brief frequency synthesizer calibration + */ +inline Result fs() { + // default constructor of std::span is empty + return spi::write_stream(RADIOLIB_SX126X_CMD_SET_FS, std::span()); +} + +/** + * \param timeout no idea why you need a timeout for TX + */ +inline Result tx(const uint32_t timeout = 0) { + const uint8_t data[] = {static_cast((timeout >> 16) & 0xFF), static_cast((timeout >> 8) & 0xFF), static_cast(timeout & 0xFF)}; + return spi::write_stream(RADIOLIB_SX126X_CMD_SET_TX, data); +} + +/** + \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. + + \note 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. + */ +inline Result rx(const uint32_t timeout = RADIOLIB_SX126X_RX_TIMEOUT_INF) { + const uint8_t data[] = {static_cast((timeout >> 16) & 0xFF), static_cast((timeout >> 8) & 0xFF), static_cast(timeout & 0xFF)}; + return spi::write_stream(RADIOLIB_SX126X_CMD_SET_RX, data); +} + +enum CAD_EXIT_MODE : uint8_t { + CAD_ONLY = 0x00, + CAD_RX = 0x01, +}; + +struct cad_params_t { + uint8_t symbol_num; + uint8_t det_peak; + uint8_t det_min; + CAD_EXIT_MODE exit_mode; + // 24-bit + // + // The parameter cadTimeout is only used when the CAD is performed with + // cadExitMode = CAD_RX. Here, the cadTimeout indicates the time the device + // will stay in Rx following a successful CAD. + // Rx Timeout = cadTimeout * 15.625us + uint32_t timeout; +}; + +/** + * \brief Calculate default CAD parameters for a given spreading factor + * \param sf Spreading factor + * \param params CAD parameters + * \note will mutate `params.symbol_num`, `params.det_peak`, and `params.det_min` + * \return true if the sf is valid and the parameters are set, false otherwise + */ +inline bool calculate_default_cad_params(const uint8_t sf, cad_params_t ¶ms) { + // Parameters `cadDetPeak` and `cadDetMin` define the sensitivity of the LoRa + // modem when trying to correlate to actual LoRa preamble symbols. These + // two settings depend on the LoRa spreading factor and Bandwidth, but + // also depend on the number of symbols used to validate or not the detection. + // + // Choosing the right value is not easy and the values selected must be + // carefully tested to ensure a good detection at sensitivity level, and + // also to limit the number of false detections. Application note AN1200.48 + // provides guidance for the selection of these parameters. + + // default CAD parameters are shown in Semtech AN1200.48, page 41. + const std::tuple det_peak_map[6] = {{7, 22}, {8, 22}, {9, 24}, {10, 25}, {11, 26}, {12, 30}}; + std::optional det_peak; + for (const auto &[r_sf, r_det_peak] : det_peak_map) { + if (r_sf == sf) { + det_peak = r_det_peak; + break; + } + } + if (not det_peak) { + return false; + } + + // CAD parameters aren't available for SF-6. Just to be safe. + params.symbol_num = RADIOLIB_SX126X_CAD_ON_2_SYMB; + params.det_peak = det_peak.value(); + params.det_min = RADIOLIB_SX126X_CAD_PARAM_DET_MIN; + return true; +} + +/** + * \brief Channel Activity Detection (CAD) method + */ +inline Result +set_cad_params(const cad_params_t ¶ms) { + uint8_t data[7]; + data[0] = params.symbol_num; + data[1] = params.det_peak; + data[2] = params.det_min; + data[3] = params.exit_mode; + data[4] = static_cast((params.timeout >> 16) & 0xFF); + data[5] = static_cast((params.timeout >> 8) & 0xFF); + data[6] = static_cast(params.timeout & 0xFF); + + return spi::write_stream(RADIOLIB_SX126X_CMD_SET_CAD_PARAMS, data); +} + +/** + * \brief Set the CAD mode + * + * The command SetCAD() can be used only in LoRa packet type. The Channel + * Activity Detection is a LoRa specific mode of operation where the device + * searches for the presence of a LoRa preamble signal. After the search has + * completed, the device returns in STDBY_RC mode. The length of the search is + * configured via the command SetCadParams(...). At the end of the search + * period, the device triggers the IRQ CAD DONE if it has been enabled. If a + * valid signal has been detected it also generates the IRQ CAD DETECTED. + * + * The time taken for the channel activity detection is dependent upon the LoRa® + * modulation settings used. For a given configuration (SF/BW) the typical CAD + * detection time can be selected to be either 1, 2, 4, 8 or 16 symbols. Once + * the duration of the selected number of symbols has been done, the radio will + * remains for around half a symbol in Rx to post-process the measurement. + */ +inline Result +set_cad() { + uint8_t data[] = {spi::SPI_NOP_COMMAND}; + return spi::write_stream(RADIOLIB_SX126X_CMD_SET_CAD, data); +} + +/** + * \brief Set the PA configuration + * \param pa_duty_cycle paDutyCycle controls the duty cycle (conduction angle) of both PAs (SX1261 and SX1262). The maximum output power, the + * power consumption, and the harmonics will drastically change with paDutyCycle. The values given across this datasheet + * are the recommended settings to achieve the best efficiency of the PA. Changing the paDutyCycle will affect the + * distribution of the power in the harmonics and should thus be selected to work in conjunction of a given matching + * network. + * \param hp_max hpMax selects the size of the PA in the SX1262, this value has no influence on the SX1261. The maximum output power can + * be reduced by reducing the value of hpMax. The valid range is between 0x00 and 0x07 and 0x07 is the maximum supported + * value for the SX1262 to achieve +22 dBm output power. Increasing hpMax above 0x07 could cause early aging of the device + * of could damage the device when used in extreme temperatures. + * \param device_sel PA device selection; 0: SX1262, 1: SX1261 + */ +inline Result +set_pa_config(const uint8_t pa_duty_cycle, + const uint8_t hp_max = RADIOLIB_SX126X_PA_CONFIG_HP_MAX, + const uint8_t device_sel = RADIOLIB_SX126X_PA_CONFIG_SX1262) { + // paLut reserved and always 0x01 + const uint8_t data[] = {pa_duty_cycle, hp_max, device_sel, RADIOLIB_SX126X_PA_CONFIG_PA_LUT}; + return spi::write_stream(RADIOLIB_SX126X_CMD_SET_PA_CONFIG, data); +} + +/** + * \brief get status from IRQ status register + * \return true if channel is free, false if channel is busy, error otherwise + */ +inline Result +channel_scan_result() { + const auto res = get_irq_status(); + APP_RADIO_RETURN_ERR(res); + if (*res & RADIOLIB_SX126X_IRQ_CAD_DETECTED) { + return false; + } else if (*res & RADIOLIB_SX126X_IRQ_CAD_DONE) { + return true; + } + return ue_t{error_t{error::RADIO_INVALID_CAD_RESULT}}; +} + +template +constexpr bool __in_precision(const float v) { + using v_t = decltype(v); + constexpr auto precision = v_t{0.001}; + return v_t{abs(v - v_t{target})} <= precision; +} + +/*! + \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. + \param XTAL Set to true to use XTAL instead of TCXO. Defaults to false. + + \note will return immediately if XTAL is true; DIO3_AS_TCXO_CTRL; +*/ +inline Result +set_TCXO(const TcxoVoltage voltage, const uint32_t delay = 5000, const bool XTAL = false) { + if (XTAL) { + return ue_t{error_t{error::RADIO_INVALID_TCXO_VOLTAGE}}; + } + auto res = standby(); + APP_RADIO_RETURN_ERR(res); + auto err_ = get_device_errors(); + APP_RADIO_RETURN_ERR(err_); + if (const auto err = *err_; err.XOSC_START_ERR) { + clear_device_errors(); + } + + uint8_t data[4]; + data[0] = static_cast(voltage); + uint32_t delay_val = static_cast(delay / 15.625); + data[1] = static_cast((delay_val >> 16) & 0xFF); + data[2] = static_cast((delay_val >> 8) & 0xFF); + data[3] = static_cast(delay_val & 0xFF); + + res = spi::write_stream(RADIOLIB_SX126X_CMD_SET_DIO3_AS_TCXO_CTRL, data); + APP_RADIO_RETURN_ERR(res); + + return {delay_val}; +} + + +/** + * \brief if the symbol (length := 2^sf / bw_khz) > 16, then we're using LDR + */ +template +bool static_is_use_ldr() { + constexpr auto khz = bw_khz(bw); + constexpr auto symbol_length = static_cast(std::pow(2, sf) / khz); + return symbol_length > 16; +} + +/** + * \brief set LoRa modulation parameters + * \param sf spread factor (5-12) + * \param bw RAW bandwidth (RADIOLIB_SX126X_LORA_BW_*) + * \param cr coding rate (RADIOLIB_SX126X_LORA_CR_*) + * \param ldro RADIOLIB_SX126X_LORA_LOW_DATA_RATE_OPTIMIZE_ON or RADIOLIB_SX126X_LORA_LOW_DATA_RATE_OPTIMIZE_OFF + */ +inline Result +set_modulation_params(const uint8_t sf, + const uint8_t bw = RADIOLIB_SX126X_LORA_BW_500_0, + const uint8_t cr = RADIOLIB_SX126X_LORA_CR_4_5, + const uint8_t ldro = RADIOLIB_SX126X_LORA_LOW_DATA_RATE_OPTIMIZE_OFF) { + if (not valid_bw(bw)) { + return ue_t{error_t{error::RADIO_INVALID_BANDWIDTH}}; + } + if (not valid_sf(bw, sf)) { + return ue_t{error_t{error::RADIO_INVALID_SPREADING_FACTOR}}; + } + if (not valid_cr(cr)) { + return ue_t{error_t{error::RADIO_INVALID_CODING_RATE}}; + } + + const uint8_t data[] = {sf, bw, cr, ldro}; + return spi::write_stream(RADIOLIB_SX126X_CMD_SET_MODULATION_PARAMS, data); +} + +inline Result +set_tx_params(const uint8_t pwr, const uint8_t ramp_time = RADIOLIB_SX126X_PA_RAMP_200U) { + const uint8_t data[] = {pwr, ramp_time}; + return spi::write_stream(RADIOLIB_SX126X_CMD_SET_TX_PARAMS, data); +} + +/*! + \brief Sets LoRa sync word. + \param sync_word LoRa sync word to be set. + \param control_bits Undocumented control bits, required for compatibility purposes. +*/ +inline Result set_sync_word(const uint8_t sync_word = RADIOLIB_SX126X_SYNC_WORD_PRIVATE, const uint8_t control_bits = 0x44) { + const auto pkt_type = get_packet_type(); + APP_RADIO_RETURN_ERR(pkt_type); + if (*pkt_type != RADIOLIB_SX126X_PACKET_TYPE_LORA) { + return ue_t{error_t{error::RADIO_WRONG_MODERN}}; + } + const uint8_t data[2] = {static_cast((sync_word & 0xF0) | ((control_bits & 0xF0) >> 4)), + static_cast(((sync_word & 0x0F) << 4) | (control_bits & 0x0F))}; + return write_register(RADIOLIB_SX126X_REG_LORA_SYNC_WORD_MSB, data); +} + +inline Result +set_packet_type(const PacketType packet_type) { + const auto data = std::array{ + RADIOLIB_SX126X_CMD_SET_PACKET_TYPE, + static_cast(packet_type), + }; + return spi::write_stream(RADIOLIB_SX126X_CMD_SET_PACKET_TYPE, data); +} + + +inline Result +set_output_power(const int8_t power) { + if (not in_range(power, -9, 22)) { + return ue_t{error_t{error::RADIO_INVALID_OUTPUT_POWER}}; + } + uint8_t ocp = 0; + auto res = read_register(RADIOLIB_SX126X_REG_OCP_CONFIGURATION, std::span{&ocp, 1}); + APP_RADIO_RETURN_ERR(res); + + res = set_pa_config(0x04); + APP_RADIO_RETURN_ERR(res); + res = set_tx_params(power); + APP_RADIO_RETURN_ERR(res); + // restore OCP configuration + return write_register(RADIOLIB_SX126X_REG_OCP_CONFIGURATION, std::span{&ocp, 1}); +} + +inline Result +set_regulator_ldo() { + return set_regulator_mode(RADIOLIB_SX126X_REGULATOR_LDO); +} + +inline Result +set_regulator_dc_dc() { + return set_regulator_mode(RADIOLIB_SX126X_REGULATOR_DC_DC); +} + +/** + * \brief checks if BUSY pin is low + * \return true if BUSY pin is low, false otherwise + */ +inline bool busy_low() { + return gpio::digital_read(spi::BUSY_PIN) == gpio::LOW; +} + +/** + * \brief blocks until BUSY pin is low + */ +inline void until_busy_low() { + while (!busy_low()) {} +} + +template +Result +set_current_limit() { + static_assert(in_range(limit, 0, 140), "limit must be in range [0, 140]"); + constexpr uint8_t raw = static_cast(limit / 2.5); + return write_register(RADIOLIB_SX126X_REG_OCP_CONFIGURATION, std::span{&raw, 1}); +} + +inline int chip_type_to_max_tx_power(const ChipType &chip) { + switch (chip) { + case ChipType::LLCC68: + return 22; + case ChipType::SX1261: + return 15; + case ChipType::SX1262: + return 22; + default: + return 0; + } +} + +constexpr auto MAX_CHIP_CHECK_ATTEMPTS = 10; +inline std::tuple find_chip() { + uint8_t i = 0; + bool ok = false; + ChipType chip = ChipType::Unknown; + while ((i < MAX_CHIP_CHECK_ATTEMPTS) && !ok) { + constexpr auto SX1262_CHIP_TYPE = "SX1262"; + constexpr auto LLCC68_CHIP_TYPE = "LLCC68"; + constexpr auto SX1261_CHIP_TYPE = "SX1261"; + + char version[16]{}; + auto version_buf = std::span{reinterpret_cast(version), std::size(version)}; + spi::read_register(RADIOLIB_SX126X_REG_VERSION_STRING, version_buf); + if (strncmp(version, LLCC68_CHIP_TYPE, 6) == 0) { + chip = ChipType::LLCC68; + } else if (strncmp(version, SX1261_CHIP_TYPE, 6) == 0) { + chip = ChipType::SX1261; + } else if (strncmp(version, SX1262_CHIP_TYPE, 6) == 0) { + chip = ChipType::SX1262; + } + if (chip != ChipType::Unknown) { + ESP_LOGI(TAG, "found chip: %s (%s)", version, chip_type_str(chip)); + ok = true; + break; + } + delay_ms(10); + i++; + } + return {ok, chip}; +} + +inline Result, error_t> +get_rx_buffer_status() { + uint8_t rx_buf_status[] = {0, 0}; + auto res = spi::read_stream(RADIOLIB_SX126X_CMD_GET_RX_BUFFER_STATUS, rx_buf_status); + APP_RADIO_RETURN_ERR(res); + return std::make_tuple(rx_buf_status[0], rx_buf_status[1]); +} + +struct read_result_t { + uint16_t irq_status; + std::span data; + + bool is_crc_err() const { + return (irq_status & RADIOLIB_SX126X_IRQ_CRC_ERR) != 0; + } + + bool is_header_err() const { + return (irq_status & RADIOLIB_SX126X_IRQ_HEADER_ERR) != 0; + } + + /*! + * \brief user should call this function after reading data (since it will override the internal buffer) + */ + static Result post_procedure() { + auto res = set_buffer_base_address(); + APP_RADIO_RETURN_ERR(res); + res = clear_irq_status(); + APP_RADIO_RETURN_ERR(res); + return {}; + } +}; + +inline Result +read_data_internal() { + const auto irq_ = get_irq_status(); + APP_RADIO_RETURN_ERR(irq_); + const auto st_ = get_status(); + APP_RADIO_RETURN_ERR(st_); + const auto irq = std::move(*irq_); + const auto st = std::move(*st_); + if (irq & RADIOLIB_SX126X_IRQ_TIMEOUT || st.command_status == CommandStatus::COMMAND_TIMEOUT) { + return ue_t{error_t{error::RADIO_RX_TIMEOUT}}; + } + + const auto tuple_ = get_rx_buffer_status(); + APP_RADIO_RETURN_ERR(tuple_); + + /** + * @see 7.2 Data Buffer in Receive Mode + * @see 7.1 Principle of Operation + * + * In receive mode RxBaseAddr specifies the buffer offset in memory at which + * the received packet payload data will be written. The buffer offset of + * the last byte written in receive mode is then stored in RxDataPointer + * which is initialized to the value of RxBaseAddr at the beginning of the + * reception. + * + * The pointer to the first byte of the last packet received and the packet + * length can be read with the command GetRxbufferStatus(). + * + * In single mode, RxDataPointer is automatically initialized to RxBaseAddr + * each time the transceiver enters Rx mode. In continuous mode the pointer + * is incremented starting from the previous position. + */ + const auto [sz, ptr] = std::move(*tuple_); + ESP_LOGD(TAG, "sz=%d, ptr=0x%02x", sz, ptr); + auto res = hal::spi::read_buffer(ptr - sz, sz); + APP_RADIO_RETURN_ERR(res); + return read_result_t{irq, *res}; +} + +/** + Circuit Configuration for Basic Rx Operation + + After power up (battery insertion or hard reset) the chip run automatically a calibration procedure and goes to STDBY_RC + mode. This is indicated by a low state on BUSY pin. From this state the steps are: + 1. If not in STDBY_RC mode, then set the circuit in this mode with the command SetStandby() + 2. Define the protocol (LoRa or FSK) with the command SetPacketType(...) + 3. Define the RF frequency with the command SetRfFrequency(...) + 4. Define where the data will be stored inside the data buffer in Rx with the command SetBufferBaseAddress(...) + 5. Define the modulation parameter according to the chosen protocol with the command SetModulationParams(...)1 + 6. Define the frame format to be used with the command SetPacketParams(...) + 7. Configure DIO and IRQ: use the command SetDioIrqParams(...) to select the IRQ RxDone and map this IRQ to a DIO (DIO1 or DIO2 or DIO3), set IRQ Timeout as well. + 8. Define Sync Word value: use the command WriteReg(...) to write the value of the register via direct register access. + 9. Set the circuit in reception mode: use the command SetRx(). Set the parameter to enable timeout or continuous mode + 10. Wait for IRQ RxDone2 or Timeout: the chip will stay in Rx and look for a new packet if the continuous mode is selected + otherwise it will goes to STDBY_RC mode. + 11. In case of the IRQ RxDone, check the status to ensure CRC is correct: use the command GetIrqStatus() + 12. Clear IRQ flag RxDone or Timeout: use the command ClearIrqStatus(). In case of a valid packet (CRC OK), get the packet + length and address of the first byte of the received payload by using the command GetRxBufferStatus(...) + 13. In case of a valid packet (CRC OK), start reading the packet + + \note The IRQ RxDone means that a packet has been received but the CRC could be wrong: the user must check the CRC before validating the packet. + */ +inline Result +kick_inf_rx() { + Result res; + res = standby(); + + // res = set_packet_type(PacketType::LORA); + // APP_RADIO_RETURN_ERR_CTX(res, "failed to set packet type"); + + res = set_frequency(DEFAULT_FREQUENCY, false); + APP_RADIO_RETURN_ERR_CTX(res, "failed to set frequency"); + + res = set_buffer_base_address(); + APP_RADIO_RETURN_ERR_CTX(res, "failed to set buffer base address"); + + res = set_modulation_params(DEFAULT_SF, DEFAULT_BW, DEFAULT_CR, DEFAULT_LDR_OPTIMIZE); + APP_RADIO_RETURN_ERR_CTX(res, "failed to set modulation params"); + + res = set_packet_params(DEFAULT_PREAMBLE_LEN, 0xff, DEFAULT_CRC_TYPE, DEFAULT_HEADER_TYPE); + APP_RADIO_RETURN_ERR_CTX(res, "failed to set packet params"); + + constexpr auto irq_mask = RADIOLIB_SX126X_IRQ_RX_DEFAULT; + constexpr auto irq_params = irq_params_t{ + irq_mask, + DIO1_PIN == NC_PIN ? RADIOLIB_SX126X_IRQ_NONE : irq_mask, + DIO2_PIN == NC_PIN ? RADIOLIB_SX126X_IRQ_NONE : irq_mask, + DIO3_PIN == NC_PIN ? RADIOLIB_SX126X_IRQ_NONE : irq_mask, + }; + res = set_dio_irq_params(irq_params); + APP_RADIO_RETURN_ERR_CTX(res, "failed to set dio irq params"); + res = clear_irq_status(); + APP_RADIO_RETURN_ERR_CTX(res, "failed to clear irq status"); + res = rx(RADIOLIB_SX126X_RX_TIMEOUT_INF); + APP_RADIO_RETURN_ERR_CTX(res, "failed to set rx"); + return {}; +} + + +enum class AFTER_TX_BEHAVIOR { + STDBY, + RX, +}; + +/** + * \brief will be called after each `sync_transmit` + * \note Application writer (i.e. myself) should manually enable the receiver after transmission + */ +inline void after_tx(const AFTER_TX_BEHAVIOR behavior = AFTER_TX_BEHAVIOR::RX) { + clear_irq_status(); + switch (behavior) { + case AFTER_TX_BEHAVIOR::STDBY: + standby(); + break; + case AFTER_TX_BEHAVIOR::RX: + kick_inf_rx(); + break; + } +} + +/** + After power up (battery insertion or hard reset) the chip runs automatically a calibration procedure and goes to STDBY_RC + mode. This is indicated by a low state on BUSY pin. From this state the steps are: + 1. If not in STDBY_RC mode, then go to this mode with the command SetStandby(...) + 2. Define the protocol (LoRa or FSK) with the command SetPacketType(...) + 3. Define the RF frequency with the command SetRfFrequency(...) + 4. Define the Power Amplifier configuration with the command SetPaConfig(...) + 5. Define output power and ramping time with the command SetTxParams(...) + 6. Define where the data payload will be stored with the command SetBufferBaseAddress(...) + 7. Send the payload to the data buffer with the command WriteBuffer(...) + 8. Define the modulation parameter according to the chosen protocol with the command SetModulationParams(...)1 + 9. Define the frame format to be used with the command SetPacketParams(...)2 + 10. Configure DIO and IRQ: use the command SetDioIrqParams(...) to select TxDone IRQ and map this IRQ to a DIO (DIO1, DIO2 or DIO3) + 11. Define Sync Word value: use the command WriteReg(...) to write the value of the register via direct register access + 12. Set the circuit in transmitter mode to start transmission with the command SetTx(). Use the parameter to enable Timeout + 13. Wait for the IRQ TxDone or Timeout: once the packet has been sent the chip goes automatically to STDBY_RC mode + 14. Clear the IRQ TxDone flag + */ +inline Result +sync_transmit(const uint8_t *data, const size_t len, const calc_time_on_air_t ¶ms) { + // unimplemented + std::unreachable(); + return {}; +} + +/** + * \brief async transmit + * \param data the data to transmit + * \param len the length of data + * \param params the parameters for calculating time on air + * \param tx_state current transmit state + * \return new transmit state + * \see poll_tx_state + */ +inline Result +async_transmit(const uint8_t *data, const size_t len, + const calc_time_on_air_t ¶ms, + const transmit_state_t &tx_state) { + if (tx_state.is_transmitting) { + return ue_t{error_t{error::RADIO_BUSY_TX}}; + } + if (len > RADIOLIB_SX126X_MAX_PACKET_LENGTH) { + return ue_t{error_t{error::INVALID_SIZE}}; + } + + Result res; + res = standby(); + APP_RADIO_RETURN_ERR_CTX(res, "failed to standby"); + + res = set_packet_params(params.preamble_length, len, params.crc_type, params.header_type); + APP_RADIO_RETURN_ERR_CTX(res, "failed to set packet params"); + + res = set_buffer_base_address(); + APP_RADIO_RETURN_ERR_CTX(res, "failed to set buffer base address"); + + res = write_buffer(std::span{data, len}); + APP_RADIO_RETURN_ERR_CTX(res, "failed to write buffer"); + + res = fix_sensitivity(params.bw); + APP_RADIO_RETURN_ERR_CTX(res, "failed to fix sensitivity"); + + constexpr auto irq_mask = RADIOLIB_SX126X_IRQ_TX_DONE | RADIOLIB_SX126X_IRQ_TIMEOUT; + constexpr auto irq_params = irq_params_t{ + irq_mask, + DIO1_PIN == NC_PIN ? RADIOLIB_SX126X_IRQ_NONE : irq_mask, + DIO2_PIN == NC_PIN ? RADIOLIB_SX126X_IRQ_NONE : irq_mask, + DIO3_PIN == NC_PIN ? RADIOLIB_SX126X_IRQ_NONE : irq_mask, + }; + res = set_dio_irq_params(irq_params); + APP_RADIO_RETURN_ERR_CTX(res, "failed to set dio irq params"); + clear_irq_status(); + APP_RADIO_RETURN_ERR_CTX(res, "failed to clear irq status"); + + res = tx(); + APP_RADIO_RETURN_ERR_CTX(res, "failed to transmit"); + + const auto timeout_us = calc_time_on_air(len, params) * 11u / 10u; + const uint16_t timeout_ms = timeout_us / 1000; + auto instant = Instant<>{}; + return transmit_state_t{ + .is_transmitting = true, + .start = std::move(instant), + .expected_duration_ms = timeout_ms, + }; +} + +inline Result +async_transmit(const std::span data, + const calc_time_on_air_t ¶ms, + const transmit_state_t &tx_state) { + return async_transmit(data.data(), data.size(), params, tx_state); +} + + +static constexpr auto init_pins = [] { + if (RST_PIN != GPIO_NUM_NC) { + gpio::set_mode(RST_PIN, gpio::Mode::OUTPUT); + } + init_exti(); +}; + +static constexpr auto begin = [](const parameters_t ¶ms) -> Result { + /** + * Most of the commands can be sent in any order except for the radio configuration commands which will set the radio in + * the proper operating mode. Indeed, it is mandatory to set the radio protocol using the command SetPacketType(...) as a first + * step before issuing any other radio configuration commands. In a second step, the user should define the modulation + * parameter according to the chosen protocol with the command SetModulationParams(...). Finally, the user should then + * select the packet format with the command SetPacketParams(...). + * + * \note + * If this order is not respected, the behavior of the device could be unexpected. + **/ + Result res; + + /* res = reset(); */ + /* explicit not reset the chip by pin, due to unexpected side effect */ + + res = standby(); + APP_RADIO_RETURN_ERR_CTX(res, "failed to standby"); + + const auto [ok, chip] = find_chip(); + if (not ok) { + return ue_t{error_t{error::RADIO_CHIP_NOT_FOUND}}; + } + + if (not USE_XTAL) { + const auto voltage_ = set_TCXO(DEFAULT_TCXO_VOLTAGE); + details::__tcxo_delay__ = *voltage_; + APP_RADIO_RETURN_ERR_CTX(res, "failed to set TCXO"); + } + + // SetPacketType + res = config_packet_type(params); + APP_RADIO_RETURN_ERR_CTX(res, "failed to config packet type"); + + // SetModulationParams + res = set_modulation_params(params.sf, + params.bw, + params.cr, + params.ldr_optimize); + APP_RADIO_RETURN_ERR_CTX(res, "failed to set modulation params"); + + res = set_sync_word(params.sync_word); + APP_RADIO_RETURN_ERR_CTX(res, "failed to set sync word"); + + if constexpr (USE_REGULATOR_LDO) { + res = set_regulator_ldo(); + APP_RADIO_RETURN_ERR_CTX(res, "failed to set regulator LDO"); + } else { + res = set_regulator_dc_dc(); + APP_RADIO_RETURN_ERR_CTX(res, "failed to set regulator DC-DC"); + } + + res = set_current_limit<60.0f>(); + APP_RADIO_RETURN_ERR_CTX(res, "failed to set current limit"); + + res = set_dio2_as_rf_switch(false); + APP_RADIO_RETURN_ERR_CTX(res, "failed to set dio2 as rf switch"); + + res = set_frequency(params.frequency); + APP_RADIO_RETURN_ERR_CTX(res, "failed to set frequency"); + + // SetPacketParams + res = set_packet_params(params.preamble_len, + 0x00, + DEFAULT_CRC_TYPE, + DEFAULT_HEADER_TYPE); + APP_RADIO_RETURN_ERR_CTX(res, "failed to set packet params"); + + res = set_output_power(chip_type_to_max_tx_power(chip)); + APP_RADIO_RETURN_ERR_CTX(res, "failed to set output power"); + + res = fix_pa_clamping(); + APP_RADIO_RETURN_ERR_CTX(res, "failed to fix pa clamping"); + + res = standby(); + APP_RADIO_RETURN_ERR_CTX(res, "failed to standby"); + return {}; +}; +} + +#undef APP_RADIO_RETURN_ERR +#endif /* D07297EB_4033_481B_BCFA_1D40899340D0 */ diff --git a/inc/llcc68_definitions.hpp b/inc/llcc68_definitions.hpp new file mode 100644 index 0000000..8d3a38f --- /dev/null +++ b/inc/llcc68_definitions.hpp @@ -0,0 +1,391 @@ +// +// Created by Kurosu Chan on 2024/1/26. +// + +#ifndef LLCC68_DEFINITIONS_H +#define LLCC68_DEFINITIONS_H +#include +#include +#include +#include +#include "radiolib_definitions.hpp" + +namespace app::driver::llcc68 { +using freq_t = float; +constexpr uint8_t DEFAULT_SYNC_WORD = RADIOLIB_SX126X_SYNC_WORD_PRIVATE; +constexpr uint16_t DEFAULT_PREAMBLE_LEN = 6; // recommended is 8 symbols though +constexpr uint8_t DEFAULT_CRC_TYPE = RADIOLIB_SX126X_LORA_CRC_OFF; +constexpr uint8_t DEFAULT_HEADER_TYPE = RADIOLIB_SX126X_LORA_HEADER_EXPLICIT; +constexpr uint8_t DEFAULT_IQ_TYPE = RADIOLIB_SX126X_LORA_IQ_STANDARD; +constexpr uint8_t PACKET_TYPE = RADIOLIB_SX126X_PACKET_TYPE_LORA; + +constexpr auto DEFAULT_BW = RADIOLIB_SX126X_LORA_BW_125_0; +constexpr auto DEFAULT_SF = 9; +constexpr auto DEFAULT_CR = RADIOLIB_SX126X_LORA_CR_4_6; +constexpr auto DEFAULT_LDR_OPTIMIZE = RADIOLIB_SX126X_LORA_LOW_DATA_RATE_OPTIMIZE_ON; +constexpr auto DEFAULT_FREQUENCY = freq_t{433.2}; + +enum class ChipType { + Unknown, + LLCC68, + SX1261, + SX1262, +}; + +inline const char *chip_type_str(const ChipType &chip) { + switch (chip) { + case ChipType::LLCC68: + return "LLCC68"; + case ChipType::SX1261: + return "SX1261"; + case ChipType::SX1262: + return "SX1262"; + default: + return "Unknown"; + } +} + + +/// @note suitable SX1262/LLCC68 +enum class PaOptimalPresetHigh : uint8_t { + DBM_14, + DBM_17, + DBM_20, + DBM_22, +}; + +/// @note suitable SX1261 +enum class PaOptimalPresetLow : uint8_t { + DBM_10, + DBM_14, + DBM_15, +}; + +using PaOptimalPreset = std::variant; + +struct pa_config_t { + uint8_t pa_duty_cycle; + uint8_t hp_max; + uint8_t device_sel; +}; + +enum class PacketType : uint8_t { + FSK = RADIOLIB_SX126X_PACKET_TYPE_GFSK, + LORA = RADIOLIB_SX126X_PACKET_TYPE_LORA, + LR_FHSS = RADIOLIB_SX126X_PACKET_TYPE_LR_FHSS, +}; + + +enum class TcxoVoltage : uint8_t { + V_1_6 = 0x00, + V_1_7 = 0x01, + V_1_8 = 0x02, + V_2_2 = 0x03, + V_2_4 = 0x04, + V_2_7 = 0x05, + V_3_0 = 0x06, + V_3_3 = 0x07, +}; + +enum class CommandStatus : uint8_t { + RESERVED = 0x00, // 0b000 + RFU = 0x01, // 0b001 + DATA_AVAILABLE = 0x02, // 0b010 + COMMAND_TIMEOUT = 0x03, // 0b011 i.e. TIMEOUT + COMMAND_PROCESSING_ERROR = 0x04, // 0b100 i.e. INVALID + FAILURE_TO_EXECUTE_COMMAND = 0x05, // 0b101 i.e. FAILED + COMMAND_TX_DONE = 0x06, // 0b110 +}; + +enum class ChipMode : uint8_t { + UNUSED = 0x00, // 0b000 + RFU = 0x01, // 0b001 + STBY_RC = 0x02, // 0b010 + STBY_XOSC = 0x03, // 0b011 + FS = 0x04, // 0b100 + RX = 0x05, // 0b101 + TX = 0x06, // 0b110 +}; + + +struct __attribute__((packed)) status_t { + uint8_t reserved_1 : 1; + CommandStatus command_status : 3; + ChipMode chip_mode : 3; + uint8_t reserved_2 : 1; +}; +static_assert(sizeof(status_t) == 1); + +struct parameters_t { + uint8_t bw; + uint8_t sf; + uint8_t cr; + uint8_t ldr_optimize; + uint16_t preamble_len; + uint8_t sync_word; + freq_t frequency; + + static parameters_t Default() { + return parameters_t{ + .bw = DEFAULT_BW, + .sf = DEFAULT_SF, + .cr = DEFAULT_CR, + .ldr_optimize = DEFAULT_LDR_OPTIMIZE, + .preamble_len = DEFAULT_PREAMBLE_LEN, + .sync_word = DEFAULT_SYNC_WORD, + .frequency = DEFAULT_FREQUENCY, + }; + } + + constexpr static size_t size() { + return sizeof(parameters_t); + } +} __attribute__((packed)); + + +struct __attribute__((packed)) lora_packet_status_t { + // Average over last packet received of RSSI + // Actual signal power is –RssiPkt/2 (dBm) + uint8_t rssi_pkt; + // Estimation of SNR on last packet received in two’s compliment format multiplied by 4. + // Actual SNR in dB =SnrPkt/4 + uint8_t snr_pkt; + // Estimation of RSSI of the LoRa® signal (after despreading) on last packet received. + // Actual Rssi in dB = -SignalRssiPkt/2 + uint8_t signal_rssi_pkt; + + float rssi_pkt_dbm() const { + return -rssi_pkt / 2.0f; + } + + float snr_pkt_db() const { + return snr_pkt / 4.0f; + } + + float signal_rssi_pkt_dbm() const { + return -signal_rssi_pkt / 2.0f; + } +}; + +struct __attribute__((packed)) fsk_packet_status_t { + // bit 7: preamble err + // bit 6: sync err + // bit 5: adrs err + // bit 4: crc err + // bit 3: length err + // bit 2: abort err + // bit 1: pkt received + // bit 0: pkt sent + uint8_t rx_status; + // RSSI value latched upon the detection of the sync address. + // [negated, dBm, fixdt(0,8,1)] + // Actual signal power is –RssiSync/2 (dBm) + uint8_t rssi_sync; + // RSSI average value over the payload of the received packet. Latched upon the pkt_done IRQ. + // [negated, dBm, fixdt(0,8,1)] + // Actual signal power is –RssiAvg/2 (dBm) + uint8_t rssi_avg; +}; + +union packet_status_t { + uint8_t raw[3]; + lora_packet_status_t lora; + fsk_packet_status_t fsk; +}; + + +struct __attribute__((packed)) op_error_t { + bool RC64K_CALIB_ERR : 1; // RC64k calibration failed + bool RC13M_CALIB_ERR : 1; // RC13M calibration failed + bool PLL_CALIB_ERR : 1; // PLL calibration failed + bool ADC_CALIB_ERR : 1; // ADC calibration failed + bool IMG_CALIB_ERR : 1; // IMG calibration failed + bool XOSC_START_ERR : 1; // XOSC failed to start + bool PLL_LOCK_ERR : 1; // PLL failed to lock + uint8_t rfu_1 : 1; // RFU + bool PA_RAMP_ERR : 1; // PA ramping failed + uint8_t rfu_2 : 7; // RFU +}; + +static_assert(sizeof(op_error_t) == 2); + +struct calc_time_on_air_t { + uint8_t bw; + uint8_t sf; + uint8_t cr; + uint16_t preamble_length; + static constexpr uint8_t ldro = DEFAULT_LDR_OPTIMIZE; + static constexpr uint8_t crc_type = DEFAULT_CRC_TYPE; + static constexpr uint8_t header_type = DEFAULT_HEADER_TYPE; + static constexpr uint8_t iq_type = DEFAULT_IQ_TYPE; + static constexpr uint8_t sync_word = DEFAULT_SYNC_WORD; + static calc_time_on_air_t from_parameters(const parameters_t ¶ms) { + return calc_time_on_air_t{ + .bw = params.bw, + .sf = params.sf, + .cr = params.cr, + .preamble_length = params.preamble_len}; + } +}; + +constexpr bool in_range(const auto v, const auto min, const auto max) { + return v >= min && v <= max; +} + +constexpr bool valid_cr(const uint8_t cr) { + switch (cr) { + case RADIOLIB_SX126X_LORA_CR_4_5: + case RADIOLIB_SX126X_LORA_CR_4_6: + case RADIOLIB_SX126X_LORA_CR_4_7: + case RADIOLIB_SX126X_LORA_CR_4_8: + return true; + default: + return false; + } +} + +constexpr std::tuple +cr_to_ratio(const uint8_t cr) { + switch (cr) { + case RADIOLIB_SX126X_LORA_CR_4_5: + return {4, 5}; + case RADIOLIB_SX126X_LORA_CR_4_6: + return {4, 6}; + case RADIOLIB_SX126X_LORA_CR_4_7: + return {4, 7}; + case RADIOLIB_SX126X_LORA_CR_4_8: + return {4, 8}; + default: + return {0, 1}; + } +} + +constexpr bool valid_ldr_optimize(const uint8_t ldr_optimize) { + if (ldr_optimize > RADIOLIB_SX126X_LORA_LOW_DATA_RATE_OPTIMIZE_ON) { + return false; + } + return true; +} + +constexpr bool valid_bw(const uint8_t bw) { + switch (bw) { + case RADIOLIB_SX126X_LORA_BW_7_8: + case RADIOLIB_SX126X_LORA_BW_10_4: + case RADIOLIB_SX126X_LORA_BW_15_6: + case RADIOLIB_SX126X_LORA_BW_20_8: + case RADIOLIB_SX126X_LORA_BW_31_25: + case RADIOLIB_SX126X_LORA_BW_41_7: + case RADIOLIB_SX126X_LORA_BW_62_5: + case RADIOLIB_SX126X_LORA_BW_125_0: + case RADIOLIB_SX126X_LORA_BW_250_0: + case RADIOLIB_SX126X_LORA_BW_500_0: + return true; + default: + return false; + } +} + +using bw_t = float; +constexpr bw_t bw_khz(const uint8_t bw) { + switch (bw) { + case RADIOLIB_SX126X_LORA_BW_7_8: + return bw_t{7.8f}; + case RADIOLIB_SX126X_LORA_BW_10_4: + return bw_t{10.4f}; + case RADIOLIB_SX126X_LORA_BW_15_6: + return bw_t{15.6f}; + case RADIOLIB_SX126X_LORA_BW_20_8: + return bw_t{20.8f}; + case RADIOLIB_SX126X_LORA_BW_31_25: + return bw_t{31.25f}; + case RADIOLIB_SX126X_LORA_BW_41_7: + return bw_t{41.7f}; + case RADIOLIB_SX126X_LORA_BW_62_5: + return bw_t{62.5f}; + case RADIOLIB_SX126X_LORA_BW_125_0: + return bw_t{125.0f}; + case RADIOLIB_SX126X_LORA_BW_250_0: + return bw_t{250.0f}; + case RADIOLIB_SX126X_LORA_BW_500_0: + return bw_t{500.0f}; + default: + return bw_t{0}; + } +} + +constexpr bool valid_sf(const uint8_t bw, const uint8_t sf) { + if (const bool ok = valid_bw(bw); not ok) { + return false; + } + switch (bw) { + case RADIOLIB_SX126X_LORA_BW_125_0: + return in_range(sf, 5, 9); + case RADIOLIB_SX126X_LORA_BW_250_0: + return in_range(sf, 5, 10); + case RADIOLIB_SX126X_LORA_BW_500_0: + return in_range(sf, 5, 11); + default: + return in_range(sf, 5, 12); + } +} + +constexpr bool valid_freq(const freq_t freq) { + return in_range(freq, freq_t{150.0}, freq_t{960.0}); +} + +/** + * \brief check if the parameters are valid. if not, set them to default. + * \param params the parameters to check + * \return if the parameters are valid, and the modified parameters + */ +constexpr std::tuple +check_fix_params(parameters_t params) { + bool ok = true; + + if (not valid_bw(params.bw)) { + params.bw = DEFAULT_BW; + ok = false; + } + if (not valid_sf(params.bw, params.sf)) { + params.sf = DEFAULT_SF; + ok = false; + } + if (not valid_cr(params.cr)) { + params.cr = DEFAULT_CR; + ok = false; + } + if (not valid_freq(params.frequency)) { + params.frequency = DEFAULT_FREQUENCY; + ok = false; + } + if (not valid_ldr_optimize(params.ldr_optimize)) { + params.ldr_optimize = DEFAULT_LDR_OPTIMIZE; + ok = false; + } + return {ok, params}; +} + +/** + * \brief only do the check + */ +constexpr bool check_params(const parameters_t ¶ms) { + if (not valid_bw(params.bw)) { + return false; + } + if (not valid_sf(params.bw, params.sf)) { + return false; + } + if (not valid_cr(params.cr)) { + return false; + } + if (not valid_freq(params.frequency)) { + return false; + } + if (not valid_ldr_optimize(params.ldr_optimize)) { + return false; + } + return true; +} +} + +#endif // LLCC68_DEFINITIONS_H diff --git a/inc/radiolib_definitions.hpp b/inc/radiolib_definitions.hpp new file mode 100644 index 0000000..dc2e63a --- /dev/null +++ b/inc/radiolib_definitions.hpp @@ -0,0 +1,439 @@ +// +// Created by Kurosu Chan on 2024/1/18. +// + +#ifndef ADC87656_291B_49BB_A611_52533D666023 +#define ADC87656_291B_49BB_A611_52533D666023 +#include + +// SX126X physical layer properties +constexpr auto RADIOLIB_SX126X_FREQUENCY_STEP_SIZE = 0.9536743164; +constexpr auto RADIOLIB_SX126X_MAX_PACKET_LENGTH = 255; +constexpr auto RADIOLIB_SX126X_CRYSTAL_FREQ = 32.0; +constexpr auto RADIOLIB_SX126X_DIV_EXPONENT = 25; + +// SX126X SPI commands +// operational modes commands +constexpr uint8_t RADIOLIB_SX126X_CMD_NOP = 0x00; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_SLEEP = 0x84; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_STANDBY = 0x80; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_FS = 0xC1; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_TX = 0x83; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_RX = 0x82; +constexpr uint8_t RADIOLIB_SX126X_CMD_STOP_TIMER_ON_PREAMBLE = 0x9F; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_RX_DUTY_CYCLE = 0x94; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_CAD = 0xC5; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_TX_CONTINUOUS_WAVE = 0xD1; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_TX_INFINITE_PREAMBLE = 0xD2; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_REGULATOR_MODE = 0x96; +constexpr uint8_t RADIOLIB_SX126X_CMD_CALIBRATE = 0x89; +constexpr uint8_t RADIOLIB_SX126X_CMD_CALIBRATE_IMAGE = 0x98; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_PA_CONFIG = 0x95; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_RX_TX_FALLBACK_MODE = 0x93; + +// register and buffer access commands +constexpr uint8_t RADIOLIB_SX126X_CMD_WRITE_REGISTER = 0x0D; +constexpr uint8_t RADIOLIB_SX126X_CMD_READ_REGISTER = 0x1D; +constexpr uint8_t RADIOLIB_SX126X_CMD_WRITE_BUFFER = 0x0E; +constexpr uint8_t RADIOLIB_SX126X_CMD_READ_BUFFER = 0x1E; + +// DIO and IRQ control +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_DIO_IRQ_PARAMS = 0x08; +constexpr uint8_t RADIOLIB_SX126X_CMD_GET_IRQ_STATUS = 0x12; +constexpr uint8_t RADIOLIB_SX126X_CMD_CLEAR_IRQ_STATUS = 0x02; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_DIO2_AS_RF_SWITCH_CTRL = 0x9D; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_DIO3_AS_TCXO_CTRL = 0x97; + +// RF, modulation and packet commands +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_RF_FREQUENCY = 0x86; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_PACKET_TYPE = 0x8A; +constexpr uint8_t RADIOLIB_SX126X_CMD_GET_PACKET_TYPE = 0x11; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_TX_PARAMS = 0x8E; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_MODULATION_PARAMS = 0x8B; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_PACKET_PARAMS = 0x8C; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_CAD_PARAMS = 0x88; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_BUFFER_BASE_ADDRESS = 0x8F; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_LORA_SYMB_NUM_TIMEOUT = 0x0A; + +// status commands +constexpr uint8_t RADIOLIB_SX126X_CMD_GET_STATUS = 0xC0; +constexpr uint8_t RADIOLIB_SX126X_CMD_GET_RSSI_INST = 0x15; +constexpr uint8_t RADIOLIB_SX126X_CMD_GET_RX_BUFFER_STATUS = 0x13; +constexpr uint8_t RADIOLIB_SX126X_CMD_GET_PACKET_STATUS = 0x14; +constexpr uint8_t RADIOLIB_SX126X_CMD_GET_DEVICE_ERRORS = 0x17; +constexpr uint8_t RADIOLIB_SX126X_CMD_CLEAR_DEVICE_ERRORS = 0x07; +constexpr uint8_t RADIOLIB_SX126X_CMD_GET_STATS = 0x10; +constexpr uint8_t RADIOLIB_SX126X_CMD_RESET_STATS = 0x00; + +constexpr uint8_t RADIOLIB_SX126X_CMD_PRAM_UPDATE = 0xD9; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_LBT_SCAN_PARAMS = 0x9A; +constexpr uint8_t RADIOLIB_SX126X_CMD_SET_SPECTR_SCAN_PARAMS = 0x9B; + +#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) + +// SX126X register map +constexpr uint16_t RADIOLIB_SX126X_REG_RX_GAIN_RETENTION_0 = 0x029F; // SX1268 datasheet v1.1, section 9.6 +constexpr uint16_t RADIOLIB_SX126X_REG_RX_GAIN_RETENTION_1 = 0x02A0; // SX1268 datasheet v1.1, section 9.6 +constexpr uint16_t RADIOLIB_SX126X_REG_RX_GAIN_RETENTION_2 = 0x02A1; // SX1268 datasheet v1.1, section 9.6 +constexpr uint16_t RADIOLIB_SX126X_REG_VERSION_STRING = 0x0320; +constexpr uint16_t RADIOLIB_SX126X_REG_HOPPING_ENABLE = 0x0385; +constexpr uint16_t RADIOLIB_SX126X_REG_LR_FHSS_PACKET_LENGTH = 0x0386; +constexpr uint16_t RADIOLIB_SX126X_REG_LR_FHSS_NUM_HOPPING_BLOCKS = 0x0387; +constexpr uint16_t RADIOLIB_SX126X_REG_SPECTRAL_SCAN_RESULT = 0x0401; +constexpr uint16_t RADIOLIB_SX126X_REG_DIOX_OUT_ENABLE = 0x0580; +constexpr uint16_t RADIOLIB_SX126X_REG_DIOX_DRIVE_STRENGTH = 0x0582; +constexpr uint16_t RADIOLIB_SX126X_REG_DIOX_IN_ENABLE = 0x0583; +constexpr uint16_t RADIOLIB_SX126X_REG_DIOX_PULL_UP_CTRL = 0x0584; +constexpr uint16_t RADIOLIB_SX126X_REG_DIOX_PULL_DOWN_CTRL = 0x0585; +constexpr uint16_t RADIOLIB_SX126X_REG_TX_BITBANG_ENABLE_0 = 0x0587; +constexpr uint16_t RADIOLIB_SX126X_REG_PATCH_UPDATE_ENABLE = 0x0610; +constexpr uint16_t RADIOLIB_SX126X_REG_TX_BITBANG_ENABLE_1 = 0x0680; +constexpr uint16_t RADIOLIB_SX126X_REG_WHITENING_INITIAL_MSB = 0x06B8; +constexpr uint16_t RADIOLIB_SX126X_REG_WHITENING_INITIAL_LSB = 0x06B9; +constexpr uint16_t RADIOLIB_SX126X_REG_RX_TX_PLD_LEN = 0x06BB; +constexpr uint16_t RADIOLIB_SX126X_REG_CRC_INITIAL_MSB = 0x06BC; +constexpr uint16_t RADIOLIB_SX126X_REG_CRC_INITIAL_LSB = 0x06BD; +constexpr uint16_t RADIOLIB_SX126X_REG_CRC_POLYNOMIAL_MSB = 0x06BE; +constexpr uint16_t RADIOLIB_SX126X_REG_CRC_POLYNOMIAL_LSB = 0x06BF; +constexpr uint16_t RADIOLIB_SX126X_REG_SYNC_WORD_0 = 0x06C0; +constexpr uint16_t RADIOLIB_SX126X_REG_SYNC_WORD_1 = 0x06C1; +constexpr uint16_t RADIOLIB_SX126X_REG_SYNC_WORD_2 = 0x06C2; +constexpr uint16_t RADIOLIB_SX126X_REG_SYNC_WORD_3 = 0x06C3; +constexpr uint16_t RADIOLIB_SX126X_REG_SYNC_WORD_4 = 0x06C4; +constexpr uint16_t RADIOLIB_SX126X_REG_SYNC_WORD_5 = 0x06C5; +constexpr uint16_t RADIOLIB_SX126X_REG_SYNC_WORD_6 = 0x06C6; +constexpr uint16_t RADIOLIB_SX126X_REG_SYNC_WORD_7 = 0x06C7; +constexpr uint16_t RADIOLIB_SX126X_REG_NODE_ADDRESS = 0x06CD; +constexpr uint16_t RADIOLIB_SX126X_REG_BROADCAST_ADDRESS = 0x06CE; +constexpr uint16_t RADIOLIB_SX126X_REG_PAYLOAD_LENGTH = 0x0702; +constexpr uint16_t RADIOLIB_SX126X_REG_PACKET_PARAMS = 0x0704; +constexpr uint16_t RADIOLIB_SX126X_REG_LORA_SYNC_TIMEOUT = 0x0706; +constexpr uint16_t RADIOLIB_SX126X_REG_IQ_CONFIG = 0x0736; +constexpr uint16_t RADIOLIB_SX126X_REG_LORA_SYNC_WORD_MSB = 0x0740; +constexpr uint16_t RADIOLIB_SX126X_REG_LORA_SYNC_WORD_LSB = 0x0741; +constexpr uint16_t RADIOLIB_SX126X_REG_FREQ_ERROR = 0x076B; +constexpr uint16_t RADIOLIB_SX126X_REG_SPECTRAL_SCAN_STATUS = 0x07CD; +constexpr uint16_t RADIOLIB_SX126X_REG_RX_ADDR_PTR = 0x0803; +constexpr uint16_t RADIOLIB_SX126X_REG_RANDOM_NUMBER_0 = 0x0819; +constexpr uint16_t RADIOLIB_SX126X_REG_RANDOM_NUMBER_1 = 0x081A; +constexpr uint16_t RADIOLIB_SX126X_REG_RANDOM_NUMBER_2 = 0x081B; +constexpr uint16_t RADIOLIB_SX126X_REG_RANDOM_NUMBER_3 = 0x081C; +constexpr uint16_t RADIOLIB_SX126X_REG_SENSITIVITY_CONFIG = 0x0889; // SX1268 datasheet v1.1, section 15.1 +constexpr uint16_t RADIOLIB_SX126X_REG_RF_FREQUENCY_0 = 0x088B; +constexpr uint16_t RADIOLIB_SX126X_REG_RF_FREQUENCY_1 = 0x088C; +constexpr uint16_t RADIOLIB_SX126X_REG_RF_FREQUENCY_2 = 0x088D; +constexpr uint16_t RADIOLIB_SX126X_REG_RF_FREQUENCY_3 = 0x088E; +constexpr uint16_t RADIOLIB_SX126X_REG_RSSI_AVG_WINDOW = 0x089B; +constexpr uint16_t RADIOLIB_SX126X_REG_RX_GAIN = 0x08AC; +constexpr uint16_t RADIOLIB_SX126X_REG_TX_CLAMP_CONFIG = 0x08D8; +constexpr uint16_t RADIOLIB_SX126X_REG_ANA_LNA = 0x08E2; +constexpr uint16_t RADIOLIB_SX126X_REG_LNA_CAP_TUNE_N = 0x08E3; +constexpr uint16_t RADIOLIB_SX126X_REG_LNA_CAP_TUNE_P = 0x08E4; +constexpr uint16_t RADIOLIB_SX126X_REG_ANA_MIXER = 0x08E5; +constexpr uint16_t RADIOLIB_SX126X_REG_OCP_CONFIGURATION = 0x08E7; +constexpr uint16_t RADIOLIB_SX126X_REG_RTC_CTRL = 0x0902; +constexpr uint16_t RADIOLIB_SX126X_REG_XTA_TRIM = 0x0911; +constexpr uint16_t RADIOLIB_SX126X_REG_XTB_TRIM = 0x0912; +constexpr uint16_t RADIOLIB_SX126X_REG_DIO3_OUT_VOLTAGE_CTRL = 0x0920; +constexpr uint16_t RADIOLIB_SX126X_REG_EVENT_MASK = 0x0944; +constexpr uint16_t 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 + +// 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_RADIOLIB_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_RX_DEFAULT 0b0000001001100010 // 14 0 default for Rx (RX_DONE, TIMEOUT, CRC_ERR and HEADER_ERR) +#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 +constexpr uint8_t RADIOLIB_SX126X_PACKET_TYPE_GFSK = 0x00; // 7 0 packet type: GFSK +constexpr uint8_t RADIOLIB_SX126X_PACKET_TYPE_LORA = 0x01; // 7 0 LoRa +constexpr uint8_t 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 +constexpr uint8_t RADIOLIB_SX126X_GFSK_FILTER_NONE = 0x00; // 7 0 GFSK filter: none +constexpr uint8_t RADIOLIB_SX126X_GFSK_FILTER_GAUSS_0_3 = 0x08; // 7 0 Gaussian, BT = 0.3 +constexpr uint8_t RADIOLIB_SX126X_GFSK_FILTER_GAUSS_0_5 = 0x09; // 7 0 Gaussian, BT = 0.5 +constexpr uint8_t RADIOLIB_SX126X_GFSK_FILTER_GAUSS_0_7 = 0x0A; // 7 0 Gaussian, BT = 0.7 +constexpr uint8_t RADIOLIB_SX126X_GFSK_FILTER_GAUSS_1 = 0x0B; // 7 0 Gaussian, BT = 1 +constexpr uint8_t RADIOLIB_SX126X_GFSK_RX_BW_4_8 = 0x1F; // 7 0 GFSK Rx bandwidth: 4.8 kHz +constexpr uint8_t RADIOLIB_SX126X_GFSK_RX_BW_5_8 = 0x17; // 7 0 5.8 kHz +constexpr uint8_t RADIOLIB_SX126X_GFSK_RX_BW_7_3 = 0x0F; // 7 0 7.3 kHz +constexpr uint8_t RADIOLIB_SX126X_GFSK_RX_BW_9_7 = 0x1E; // 7 0 9.7 kHz +constexpr uint8_t RADIOLIB_SX126X_GFSK_RX_BW_11_7 = 0x16; // 7 0 11.7 kHz +constexpr uint8_t RADIOLIB_SX126X_GFSK_RX_BW_14_6 = 0x0E; // 7 0 14.6 kHz +constexpr uint8_t RADIOLIB_SX126X_GFSK_RX_BW_19_5 = 0x1D; // 7 0 19.5 kHz +constexpr uint8_t RADIOLIB_SX126X_GFSK_RX_BW_23_4 = 0x15; // 7 0 23.4 kHz +constexpr uint8_t RADIOLIB_SX126X_GFSK_RX_BW_29_3 = 0x0D; // 7 0 29.3 kHz +constexpr uint8_t RADIOLIB_SX126X_GFSK_RX_BW_39_0 = 0x1C; // 7 0 39.0 kHz +constexpr uint8_t RADIOLIB_SX126X_GFSK_RX_BW_46_9 = 0x14; // 7 0 46.9 kHz +constexpr uint8_t RADIOLIB_SX126X_GFSK_RX_BW_58_6 = 0x0C; // 7 0 58.6 kHz +constexpr uint8_t RADIOLIB_SX126X_GFSK_RX_BW_78_2 = 0x1B; // 7 0 78.2 kHz +constexpr uint8_t RADIOLIB_SX126X_GFSK_RX_BW_93_8 = 0x13; // 7 0 93.8 kHz +constexpr uint8_t RADIOLIB_SX126X_GFSK_RX_BW_117_3 = 0x0B; // 7 0 117.3 kHz +constexpr uint8_t RADIOLIB_SX126X_GFSK_RX_BW_156_2 = 0x1A; // 7 0 156.2 kHz +constexpr uint8_t RADIOLIB_SX126X_GFSK_RX_BW_187_2 = 0x12; // 7 0 187.2 kHz +constexpr uint8_t RADIOLIB_SX126X_GFSK_RX_BW_234_3 = 0x0A; // 7 0 234.3 kHz +constexpr uint8_t RADIOLIB_SX126X_GFSK_RX_BW_312_0 = 0x19; // 7 0 312.0 kHz +constexpr uint8_t RADIOLIB_SX126X_GFSK_RX_BW_373_6 = 0x11; // 7 0 373.6 kHz +constexpr uint8_t RADIOLIB_SX126X_GFSK_RX_BW_467_0 = 0x09; // 7 0 467.0 kHz +constexpr uint8_t RADIOLIB_SX126X_LORA_BW_7_8 = 0x00; // 7 0 LoRa bandwidth: 7.8 kHz +constexpr uint8_t RADIOLIB_SX126X_LORA_BW_10_4 = 0x08; // 7 0 10.4 kHz +constexpr uint8_t RADIOLIB_SX126X_LORA_BW_15_6 = 0x01; // 7 0 15.6 kHz +constexpr uint8_t RADIOLIB_SX126X_LORA_BW_20_8 = 0x09; // 7 0 20.8 kHz +constexpr uint8_t RADIOLIB_SX126X_LORA_BW_31_25 = 0x02; // 7 0 31.25 kHz +constexpr uint8_t RADIOLIB_SX126X_LORA_BW_41_7 = 0x0A; // 7 0 41.7 kHz +constexpr uint8_t RADIOLIB_SX126X_LORA_BW_62_5 = 0x03; // 7 0 62.5 kHz +constexpr uint8_t RADIOLIB_SX126X_LORA_BW_125_0 = 0x04; // 7 0 125.0 kHz +constexpr uint8_t RADIOLIB_SX126X_LORA_BW_250_0 = 0x05; // 7 0 250.0 kHz +constexpr uint8_t RADIOLIB_SX126X_LORA_BW_500_0 = 0x06; // 7 0 500.0 kHz +constexpr uint8_t RADIOLIB_SX126X_LORA_CR_4_5 = 0x01; // 7 0 LoRa coding rate: 4/5 +constexpr uint8_t RADIOLIB_SX126X_LORA_CR_4_6 = 0x02; // 7 0 4/6 +constexpr uint8_t RADIOLIB_SX126X_LORA_CR_4_7 = 0x03; // 7 0 4/7 +constexpr uint8_t RADIOLIB_SX126X_LORA_CR_4_8 = 0x04; // 7 0 4/8 +constexpr uint8_t RADIOLIB_SX126X_LORA_LOW_DATA_RATE_OPTIMIZE_OFF = 0x00; // 7 0 LoRa low data rate optimization: disabled +constexpr uint8_t RADIOLIB_SX126X_LORA_LOW_DATA_RATE_OPTIMIZE_ON = 0x01; // 7 0 enabled + +// RADIOLIB_SX126X_CMD_SET_PACKET_PARAMS +constexpr uint8_t RADIOLIB_SX126X_GFSK_PREAMBLE_DETECT_OFF = 0x00; // 7 0 GFSK minimum preamble length before reception starts: detector disabled +constexpr uint8_t RADIOLIB_SX126X_GFSK_PREAMBLE_DETECT_8 = 0x04; // 7 0 8 bits +constexpr uint8_t RADIOLIB_SX126X_GFSK_PREAMBLE_DETECT_16 = 0x05; // 7 0 16 bits +constexpr uint8_t RADIOLIB_SX126X_GFSK_PREAMBLE_DETECT_24 = 0x06; // 7 0 24 bits +constexpr uint8_t RADIOLIB_SX126X_GFSK_PREAMBLE_DETECT_32 = 0x07; // 7 0 32 bits +constexpr uint8_t RADIOLIB_SX126X_GFSK_ADDRESS_FILT_OFF = 0x00; // 7 0 GFSK address filtering: disabled +constexpr uint8_t RADIOLIB_SX126X_GFSK_ADDRESS_FILT_NODE = 0x01; // 7 0 node only +constexpr uint8_t RADIOLIB_SX126X_GFSK_ADDRESS_FILT_NODE_BROADCAST = 0x02; // 7 0 node and broadcast +constexpr uint8_t RADIOLIB_SX126X_GFSK_PACKET_FIXED = 0x00; // 7 0 GFSK packet type: fixed (payload length known in advance to both sides) +constexpr uint8_t RADIOLIB_SX126X_GFSK_PACKET_VARIABLE = 0x01; // 7 0 variable (payload length added to packet) +constexpr uint8_t RADIOLIB_SX126X_GFSK_CRC_OFF = 0x01; // 7 0 GFSK packet CRC: disabled +constexpr uint8_t RADIOLIB_SX126X_GFSK_CRC_1_BYTE = 0x00; // 7 0 1 byte +constexpr uint8_t RADIOLIB_SX126X_GFSK_CRC_2_BYTE = 0x02; // 7 0 2 byte +constexpr uint8_t RADIOLIB_SX126X_GFSK_CRC_1_BYTE_INV = 0x04; // 7 0 1 byte, inverted +constexpr uint8_t RADIOLIB_SX126X_GFSK_CRC_2_BYTE_INV = 0x06; // 7 0 2 byte, inverted +constexpr uint8_t RADIOLIB_SX126X_GFSK_WHITENING_OFF = 0x00; // 7 0 GFSK data whitening: disabled +constexpr uint8_t RADIOLIB_SX126X_GFSK_WHITENING_ON = 0x01; // 7 0 enabled +constexpr uint8_t RADIOLIB_SX126X_LORA_HEADER_EXPLICIT = 0x00; // 7 0 LoRa header mode: explicit +constexpr uint8_t RADIOLIB_SX126X_LORA_HEADER_IMPLICIT = 0x01; // 7 0 implicit +constexpr uint8_t RADIOLIB_SX126X_LORA_CRC_OFF = 0x00; // 7 0 LoRa CRC mode: disabled +constexpr uint8_t RADIOLIB_SX126X_LORA_CRC_ON = 0x01; // 7 0 enabled +constexpr uint8_t RADIOLIB_SX126X_LORA_IQ_STANDARD = 0x00; // 7 0 LoRa IQ setup: standard +constexpr uint8_t 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 +constexpr uint8_t 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. +constexpr uint8_t 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) + +// RADIOLIB_SX126X_CMD_SET_PA_CONFIG +#define RADIOLIB_SX126X_PA_CONFIG_SX1262 0x00 + +#endif /* ADC87656_291B_49BB_A611_52533D666023 */ diff --git a/inc/template/app_const_llcc68_template.hpp b/inc/template/app_const_llcc68_template.hpp new file mode 100644 index 0000000..067ca1b --- /dev/null +++ b/inc/template/app_const_llcc68_template.hpp @@ -0,0 +1,31 @@ +#ifndef B0CD865F_D860_44B7_B289_4F512C770D2B +#define B0CD865F_D860_44B7_B289_4F512C770D2B + +/** a hack prevent `clangd` to complain about the error + * while prevent this file from being included by mistake. + * + * one needs to define `__CLANGD__` manually, you could + * do that with `.clangd` configuration file + */ +#ifndef __CLANGD__ +#error "this file is a template; copy it to `app_const_llcc68.hpp` and modify the values; then remove this error" +#endif + +#include +namespace app::driver::llcc68 { +/// @brief special pin number for no connection +constexpr auto NC_PIN = GPIO_NUM_NC; + +constexpr auto MOSI_PIN = GPIO_NUM_NC; +constexpr auto MISO_PIN = GPIO_NUM_NC; +constexpr auto SCLK_PIN = GPIO_NUM_NC; +constexpr auto BUSY_PIN = GPIO_NUM_NC; +constexpr auto CS_PIN = GPIO_NUM_NC; +constexpr auto RST_PIN = NC_PIN; + +constexpr auto DIO1_PIN = GPIO_NUM_NC; +constexpr auto DIO2_PIN = NC_PIN; +constexpr auto DIO3_PIN = NC_PIN; +} + +#endif /* B0CD865F_D860_44B7_B289_4F512C770D2B */ diff --git a/src/hal_spi.cpp b/src/hal_spi.cpp new file mode 100644 index 0000000..3bd13cd --- /dev/null +++ b/src/hal_spi.cpp @@ -0,0 +1,413 @@ +// +// Created by Kurosu Chan on 2024/1/18. +// + +#include +#include +#include +#include "esp_err.h" +#include "hal_error.hpp" +#include "hal_gpio.hpp" +#include "hal_spi.hpp" +#include "utils/app_utils.hpp" +#include "utils/app_instant.hpp" +// https://docs.espressif.com/projects/esp-idf/en/v5.3.2/esp32h2/api-reference/peripherals/spi_master.html +// https://github.com/espressif/esp-idf/blob/v5.3.2/examples/peripherals/spi_master/lcd/main/spi_master_example_main.c +namespace app::driver::hal::spi { +constexpr auto SPI_CMD_BIT_SIZE = 8; +constexpr auto SPI_BUFFER_OFFSET_BIT_SIZE = 8; +constexpr auto SPI_REGISTER_ADDR_BIT_SIZE = 16; +constexpr auto MAX_BUFFER_SIZE = 256; + +namespace details { + static bool is_initialized = false; + static spi_device_handle_t spi_device; + + /// should satisfy most of the control register write/read + /// without overlap with the data buffer + constexpr auto DATA_BUFFER_OFFSET = 16; + static_assert(DATA_BUFFER_OFFSET < MAX_BUFFER_SIZE, + "DATA_BUFFER_OFFSET must be less than MAX_BUFFER_SIZE"); + static uint8_t _shared_buffer[MAX_BUFFER_SIZE]; + /** + * @brief used in generic stream io + */ + static std::span shared_buffer = std::span{_shared_buffer}; + /** + * @brief only used in read/write buffer io + */ + static std::span data_buffer = std::span{_shared_buffer + DATA_BUFFER_OFFSET, MAX_BUFFER_SIZE - DATA_BUFFER_OFFSET}; +} + + +static constexpr auto verify_statuses = [](std::span statuses) { + uint8_t i = 0; + for (const auto st : statuses) { + if (const auto err = status_to_err(st); err != error::OK) { + ESP_LOGE(TAG, "failed to verify status 0x%02x at byte %u", st, i); + return err; + } + i += 1; + } + return error::OK; +}; + +std::span mut_shared_buffer() { + return std::span{details::shared_buffer}; +} + +void init() { + if (details::is_initialized) { + return; + } + + // Configure SPI bus + spi_bus_config_t bus_config = { + .mosi_io_num = MOSI_PIN, + .miso_io_num = MISO_PIN, + .sclk_io_num = SCLK_PIN, + .quadwp_io_num = -1, + .quadhd_io_num = -1, + .data4_io_num = -1, + .data5_io_num = -1, + .data6_io_num = -1, + .data7_io_num = -1, + .max_transfer_sz = 0, + .flags = SPICOMMON_BUSFLAG_MASTER | SPICOMMON_BUSFLAG_GPIO_PINS, + .intr_flags = 0, + }; + + // Configure SPI device + // https://github.com/nopnop2002/esp-idf-sx126x/blob/main/components/ra01s/ra01s.c + // + // SPI_DEVICE_HALFDUPLEX flag will turn the device into half-duplex + // See also the phase + // https://docs.espressif.com/projects/esp-idf/en/v5.3.2/esp32/api-reference/peripherals/spi_master.html#spi-transactions + spi_device_interface_config_t dev_config = { + .command_bits = 8, + .address_bits = 0, + .dummy_bits = 0, + .mode = 0, + .duty_cycle_pos = 128, + .clock_speed_hz = 10'000'000, + .spics_io_num = CS_PIN, + .flags = SPI_DEVICE_NO_DUMMY, + .queue_size = 1, + }; + + ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &bus_config, SPI_DMA_CH_AUTO)); + ESP_ERROR_CHECK(spi_bus_add_device(SPI2_HOST, &dev_config, &details::spi_device)); + + + gpio_config_t io_conf = { + .pin_bit_mask = (1ULL << BUSY_PIN), + .mode = GPIO_MODE_INPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE, + }; + ESP_ERROR_CHECK(gpio_config(&io_conf)); + details::is_initialized = true; +} + +// https://docs.espressif.com/projects/esp-idf/en/release-v5.3/esp32/api-reference/peripherals/spi_master.html +// https://github.com/espressif/arduino-esp32/blob/5ba4c21a990f46b61ae8c7913b81e15064b2a8ef/cores/esp32/esp32-hal-spi.c#L836-L843 +// https://github.com/espressif/esp-idf/issues/5223 + +/*! + \brief wait for busy pin to go down + \return true if the pin goes down, return false if timeout + \note The LLCC68 uses the pin BUSY to indicate the status of the chip and + its ability (or not) to receive another command while internal processing + occurs. Prior to executing one of the generic functions, it is thus + necessary to check the status of BUSY to make sure the chip is in a state + where it can process another function. +*/ +inline bool wait_for_not_busy(const size_t timeout_ms) { + if constexpr (BUSY_PIN == GPIO_NUM_NC) { + app::utils::delay_ms(timeout_ms); + return true; + } else { + const auto io_inst = app::utils::Instant<>{}; + while (hal::gpio::digital_read(BUSY_PIN) == hal::gpio::HIGH) { + if (io_inst.elapsed_ms() > timeout_ms) { + return false; + } + } + return true; + } +} + +// The communication with the LLCC68 is organized around generic functions which +// allow the user to control the device behavior. Each function is based on an +// Operational Command (refer throughout this document as “Opcode”), which is +// then followed by a set of parameters. The LLCC68 use the BUSY pin to indicate +// the status of the chip. In the following chapters, it is assumed that host +// micro-controller has an SPI and access to it via spi.write(data). Data is an +// 8-bit word. The SPI chip select is defined by NSS, active low. + + +// Through the SPI interface, the host can issue commands to the chip or access +// the data memory space to directly retrieve or write data. In normal +// operation, a reduced number of direct data write operations is required +// except when accessing the data buffer. The user interacts with the circuit +// through an API (instruction set). + +/*! READ */ + +Result +read_stream(uint8_t cmd, std::span data, const size_t timeout_ms) { + if (not wait_for_not_busy(timeout_ms)) { + return ue_t{error::TIMEOUT}; + } + + if (data.size() > MAX_BUFFER_SIZE - 1) { + return ue_t{error::INVALID_SIZE}; + } + + auto rx_buffer = details::shared_buffer.subspan(0, data.size() + 1); + + spi_transaction_ext_t transaction; + transaction = spi_transaction_ext_t{ + .base = { + .flags = SPI_TRANS_VARIABLE_CMD, + .cmd = cmd, + .length = static_cast(rx_buffer.size() * 8), + .rxlength = static_cast(rx_buffer.size() * 8), + .tx_buffer = nullptr, + .rx_buffer = rx_buffer.data(), + }, + .command_bits = static_cast(SPI_CMD_BIT_SIZE), + .address_bits = 0, + .dummy_bits = 0, + }; + esp_err_t err = spi_device_transmit(details::spi_device, &transaction.base); + if (err != ESP_OK) { + ESP_LOGE(TAG, "failed to transfer %s (%d)", esp_err_to_name(err), err); + return ue_t{error::FAILED}; + } + + auto status = rx_buffer[0]; + if (const auto err = status_to_err(status); err != error::OK) { + return ue_t{err}; + } + + std::copy(rx_buffer.begin() + 1, rx_buffer.end(), data.begin()); + return {}; +} + + +/*! WRITE */ + +Result +write_stream(uint8_t cmd, std::span data, const size_t timeout_ms) { + if (not wait_for_not_busy(timeout_ms)) { + return ue_t{error::TIMEOUT}; + } + + if (data.size() > MAX_BUFFER_SIZE) { + return ue_t{error::INVALID_SIZE}; + } + + const auto rx_buffer = details::shared_buffer.subspan(0, data.size()); + spi_transaction_ext_t transaction; + transaction = spi_transaction_ext_t{ + .base = { + .flags = SPI_TRANS_VARIABLE_CMD, + .cmd = cmd, + .length = static_cast(data.size() * 8), + .rxlength = static_cast(rx_buffer.size() * 8), + .tx_buffer = data.data(), + .rx_buffer = rx_buffer.data(), + }, + .command_bits = static_cast(SPI_CMD_BIT_SIZE), + .address_bits = 0, + .dummy_bits = 0, + }; + esp_err_t err = spi_device_transmit(details::spi_device, &transaction.base); + if (err != ESP_OK) { + ESP_LOGE(TAG, "failed to transfer %s (%d)", esp_err_to_name(err), err); + return ue_t{error::FAILED}; + } + + if (const auto err = verify_statuses(rx_buffer); err != error::OK) { + return ue_t{err}; + } + + return {}; +} + + +/*! READ REGISTER */ + +Result +read_register(uint16_t reg, std::span data, const size_t timeout_ms) { + if (not wait_for_not_busy(timeout_ms)) { + return ue_t{error::TIMEOUT}; + } + + if (data.size() > MAX_BUFFER_SIZE - 1) { + return ue_t{error::INVALID_SIZE}; + } + + // Note that the host has to send an NOP after sending the 2 + // bytes of address to start receiving data bytes on the next NOP sent. + // + // NOTE: I'm not sure if ESP32 would send 0x00 (NOP) after MOSI phase + // if the tx_buffer is nullptr. + spi_transaction_ext_t transaction; + + auto rx_buffer = details::shared_buffer.subspan(0, data.size() + 1); + transaction = spi_transaction_ext_t{ + .base = { + .flags = SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR, + .cmd = SPI_READ_COMMAND, + .addr = reg, + .length = static_cast(rx_buffer.size() * 8), + .rxlength = static_cast(rx_buffer.size() * 8), + .tx_buffer = nullptr, + .rx_buffer = rx_buffer.data(), + }, + .command_bits = static_cast(SPI_CMD_BIT_SIZE), + .address_bits = static_cast(SPI_REGISTER_ADDR_BIT_SIZE), + .dummy_bits = 0, + }; + esp_err_t err = spi_device_transmit(details::spi_device, &transaction.base); + if (err != ESP_OK) { + ESP_LOGE(TAG, "failed to transfer %s (%d)", esp_err_to_name(err), err); + return ue_t{error::FAILED}; + } + + auto status = rx_buffer[0]; + if (const auto err = status_to_err(status); err != error::OK) { + return ue_t{err}; + } + std::copy(rx_buffer.begin() + 1, rx_buffer.end(), data.begin()); + return {}; +} + +/*! WRITE REGISTER */ + +Result +write_register(uint16_t offset, std::span data, const size_t timeout_ms) { + if (not wait_for_not_busy(timeout_ms)) { + return ue_t{error::TIMEOUT}; + } + + if (data.size() > MAX_BUFFER_SIZE) { + return ue_t{error::INVALID_SIZE}; + } + + auto rx_buffer = details::shared_buffer.subspan(0, data.size()); + + spi_transaction_ext_t transaction; + transaction = spi_transaction_ext_t{ + .base = { + .flags = SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR, + .cmd = SPI_WRITE_COMMAND, + .addr = offset, + .length = static_cast(data.size() * 8), + .rxlength = static_cast(rx_buffer.size() * 8), + .tx_buffer = data.data(), + .rx_buffer = rx_buffer.data(), + }, + .command_bits = static_cast(SPI_CMD_BIT_SIZE), + .address_bits = static_cast(SPI_REGISTER_ADDR_BIT_SIZE), + .dummy_bits = 0, + }; + + esp_err_t err = spi_device_transmit(details::spi_device, &transaction.base); + if (err != ESP_OK) { + ESP_LOGE(TAG, "failed to transfer %s (%d)", esp_err_to_name(err), err); + return ue_t{error::FAILED}; + } + + if (const auto err = verify_statuses(rx_buffer); err != error::OK) { + return ue_t{err}; + } + + return {}; +} + + +/*! READ BUFFER */ +Result, error_t> +read_buffer(uint8_t offset, uint8_t size, const size_t timeout_ms) { + const auto size_required = static_cast(size) + 1; + if (size_required > details::data_buffer.size()) { + return ue_t{error::INVALID_SIZE}; + } + + auto rx_buffer = details::data_buffer.subspan(0, size_required); + + spi_transaction_ext_t transaction; + transaction = spi_transaction_ext_t{ + .base = { + .flags = SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR, + .cmd = SPI_READ_BUFFER_CMD, + .addr = offset, + .length = static_cast(rx_buffer.size() * 8), + .rxlength = static_cast(rx_buffer.size() * 8), + .tx_buffer = nullptr, + .rx_buffer = rx_buffer.data(), + }, + .command_bits = static_cast(SPI_CMD_BIT_SIZE), + .address_bits = static_cast(SPI_BUFFER_OFFSET_BIT_SIZE), + .dummy_bits = 0, + }; + esp_err_t err = spi_device_polling_transmit(details::spi_device, &transaction.base); + if (err != ESP_OK) { + ESP_LOGE(TAG, "failed to transfer %s (%d)", esp_err_to_name(err), err); + return ue_t{error::FAILED}; + } + + const auto status = rx_buffer[0]; + if (const auto err = status_to_err(status); err != error::OK) { + return ue_t{err}; + } + + return std::span{rx_buffer.begin() + 1, rx_buffer.end()}; +}; + +/*! WRITE BUFFER */ + +Result +write_buffer(uint8_t offset, std::span data_from_host, const size_t timeout_ms) { + if (not wait_for_not_busy(timeout_ms)) { + return ue_t{error::TIMEOUT}; + } + + if (data_from_host.size() > details::data_buffer.size()) { + return ue_t{error::INVALID_SIZE}; + } + auto rx_buffer = details::data_buffer.subspan(0, data_from_host.size()); + + spi_transaction_ext_t transaction; + transaction = spi_transaction_ext_t{ + .base = { + .flags = SPI_TRANS_VARIABLE_CMD | SPI_TRANS_VARIABLE_ADDR, + .cmd = SPI_WRITE_BUFFER_CMD, + .addr = offset, + .length = static_cast(data_from_host.size() * 8), + .rxlength = static_cast(rx_buffer.size() * 8), + .tx_buffer = data_from_host.data(), + .rx_buffer = rx_buffer.data(), + }, + .command_bits = static_cast(SPI_CMD_BIT_SIZE), + .address_bits = static_cast(SPI_BUFFER_OFFSET_BIT_SIZE), + .dummy_bits = 0, + }; + + esp_err_t err = spi_device_transmit(details::spi_device, &transaction.base); + if (err != ESP_OK) { + ESP_LOGE(TAG, "failed to transfer %s (%d)", esp_err_to_name(err), err); + return ue_t{error::FAILED}; + } + + if (const auto err = verify_statuses(rx_buffer); err != error::OK) { + return ue_t{err}; + } + + return {}; +} +} diff --git a/src/llcc68.cpp b/src/llcc68.cpp new file mode 100644 index 0000000..87990cf --- /dev/null +++ b/src/llcc68.cpp @@ -0,0 +1,36 @@ +// +// Created by Kurosu Chan on 2024/1/17. +// +#include "llcc68.hpp" +#include "driver/gpio.h" +#include "app_const_llcc68.hpp" + +constexpr auto ESP_INTR_FLAG_DEFAULT = 0; + +namespace app::driver::llcc68 { +namespace details { + uint32_t __tcxo_delay__; + transmit_state_t __tx_state__; + // TODO: use freeRTOS queue to handle received data + bool __dio_flag__ = false; +} + +void init_exti() { + constexpr auto isr = [](void *param) { + details::__dio_flag__ = true; + }; + + + gpio_config_t io_conf = { + .pin_bit_mask = (1ULL << DIO2_PIN), + .mode = GPIO_MODE_INPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_POSEDGE, + }; + // https://github.com/espressif/esp-idf/blob/v5.3.2/examples/peripherals/gpio/generic_gpio/main/gpio_example_main.c + ESP_ERROR_CHECK(gpio_config(&io_conf)); + gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT); + gpio_isr_handler_add(DIO2_PIN, isr, nullptr); +} +}