Arduino library for LoRa communication with Semtech SX126x chips. It is based on Semtech's SX126x libraries and adapted to the Arduino framework for ESP32, ESP8266, nRF52832 and RP2040. It will not work with other uC's like AVR.
LoRaWAN version: MAC V1.0.2
and Regional Parameters version: PHY V1.0.2 REV B
RAK11300 module (RP2040) support is only tested with the ArduinoCore Mbed BSP. It will not work with other BSP's for the Raspberry RP2040.
Current testing RAK11300/RAK11310 with the Arduino Pico BSP, still experimental, but promising.
_IMPORTANT: READ WHAT'S NEW IN V2
Some major changes are made in V2 of the SX126x-Arduino library:
- The library now supports all LoRaWAN regions without re-compiling
- The interrupt handling for SX126x IRQ's are taken into separate tasks for ESP32, nRF52 and RP2040
This requires some code changes in your existing applications. Please read WHAT'S NEW IN V2 to learn how to migrate your application to use SX126x-Arduino V2_
I stumbled over the SX126x LoRa family in a customer project. Most of the existing Arduino libraries for Semtech's SX127x family are unfortunately not working with this new generation LoRa chip. I found a usefull base library from Insight SIP which is based on the original Semtech SX126x library and changed it to work with the ESP32.
For now the library is tested with an eByte E22-900M22S module connected to an ESP32 and an Insight SIP ISP4520 which combines a Nordic nRF52832 and a Semtech SX1262 in one module. It is as well tested with an RAKwireless WisCore RAK4630 module
Check out the example provided with this library to learn the basic functions.
Library published under MIT license
Semtech revised BSD license for codeparts used from Semtech S.A.
--- Revised BSD License ---
Copyright (c) 2013, SEMTECH S.A.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the Semtech corporation nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL SEMTECH S.A. BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#define RXTIMEOUT_LORA_MAX
, thanks to @kisChang Radio.TimeOnAir()
for bandwidths other than BW 125, 250 and 500_hwConfig.USE_RXEN_ANT_PWR
lmh_init()
lora_rak4630_init()
hwConfig
structlora_hardware_re_init()
to re-initialize SX1262 connection without resetting the LoRa chipRadio.ReInit()
to re-initialize SX1262 connection without resetting the LoRa chipRadio.IrqProcessAfterDeepSleep()
to handle IRQ that woke up the CPU (RX_DONE, TX_DONE, ...)lmh_setSubBandChannels()
Commissioning.h
. Everything is done with functions and build flagsCommissioning.h
. Only the region has to be setup by #definelmh_setDevEui
, lmh_setAppEui
, lmh_setAppKey
, lmh_setNwkSKey
, lmh_setAppSKey
, lmh_setDevAddr
, lmh_setSingleChannelGateway
WORK IN PROGRESS
Check out the example provided with this library to learn the basic functions.
See examples
To adapt the library to different modules and region specific ISM frequencies some defines are used. The following list is not complete yet and will be extended
#define SX1261_CHIP // if your module has a SX1261 chip
#define SX1262_CHIP // if your module has a SX1262 or SX1268 chip
The hardware configuration is given to the library by a structure with the following elements
hwConfig.CHIP_TYPE = SX1262_CHIP; // SX1261_CHIP for Semtech SX1261 SX1262_CHIP for Semtech SX1262/1268
hwConfig.PIN_LORA_RESET = PIN_LORA_RESET; // GPIO pin connected to NRESET of the SX126x
hwConfig.PIN_LORA_NSS = PIN_LORA_NSS; // GPIO pin connected to NSS of the SX126x
hwConfig.PIN_LORA_SCLK = PIN_LORA_SCLK; // GPIO pin connected to SCK of the SX126x
hwConfig.PIN_LORA_MISO = PIN_LORA_MISO; // GPIO pin connected to MISO of the SX126x
hwConfig.PIN_LORA_DIO_1 = PIN_LORA_DIO_1; // GPIO pin connected to DIO 1 of the SX126x
hwConfig.PIN_LORA_BUSY = PIN_LORA_BUSY; // GPIO pin connected to BUSY of the SX126x
hwConfig.PIN_LORA_MOSI = PIN_LORA_MOSI; // GPIO pin connected to MOSI of the SX126x
hwConfig.RADIO_TXEN = RADIO_TXEN; // GPIO pin used to enable the RX antenna of the SX126x
hwConfig.RADIO_RXEN = RADIO_RXEN; // GPIO pin used to enable the TX antenna of the SX126x
hwConfig.USE_DIO2_ANT_SWITCH = false; // True if DIO2 is used to switch the antenna from RX to TX
hwConfig.USE_DIO3_TCXO = true; // True if DIO3 is used to control the voltage of the TXCO oscillator
hwConfig.USE_DIO3_ANT_SWITCH = false; // True if DIO3 is used to enable/disable the antenna
hwConfig.USE_LDO = false; // False if SX126x DCDC converter is used, true if SX126x LDO is used
hwConfig.USE_RXEN_ANT_PWR = false; // If set to true RADIO_RXEN pin is used to control power of antenna switch
The hardware of the SX126x chips can be designed to use either an internal LDO or an internal DCDC converter. The DCDC converter provides better current savings and will be used in most modules. If there are problems to get the SX126x to work, check which HW configuration is used and set USE_LDO
accordingly.
If USE_LDO
is not set in the hwConfig, DCDC is used as default.
See examples.
There is one example for ArduinoIDE and one example for PlatformIO available.
The PingPong examples show how to define the HW connection between the MCU and the SX126x chip/module.
Another example is for LoRaWan and is tested with a Single Channel (ESP32) and a 8 Channel (Dragino LPS8) LoRaWan gateways. The examples can be found here: ArduinoIDE and one example for PlatformIO
Structure to define the connection between the MCU and the SX126x
hw_config hwConfig;
GPIO definitions for an ESP32. Change it to the connections between the ESP32 and the SX126x in your specific HW design
// ESP32 - SX126x pin configuration
int PIN_LORA_RESET = 4; // LORA RESET
int PIN_LORA_NSS = 5; // LORA SPI CS
int PIN_LORA_SCLK = 18; // LORA SPI CLK
int PIN_LORA_MISO = 19; // LORA SPI MISO
int PIN_LORA_DIO_1 = 21; // LORA DIO_1
int PIN_LORA_BUSY = 22; // LORA SPI BUSY
int PIN_LORA_MOSI = 23; // LORA SPI MOSI
int RADIO_TXEN = 26; // LORA ANTENNA TX ENABLE
int RADIO_RXEN = 27; // LORA ANTENNA RX ENABLE
Check the SX126x datasheet for explanations The bandwidth can be set to any bandwidth supported by the SX126x: |
Index | Bandwidth | Index | Bandwidth |
---|---|---|---|---|
0 | 125 kHz | 5 | 31.25 kHz | |
1 | 250 kHz | 6 | 20.83 kHz | |
2 | 500 kHz | 7 | 15.63 kHz | |
3 | 62.5 kHz | 8 | 10.42 kHz | |
4 | 41.67 kHz | 9 | 7.81 kHz |
// Define LoRa parameters
#define RF_FREQUENCY 868000000 // Hz
#define TX_OUTPUT_POWER 22 // dBm
#define LORA_BANDWIDTH 0 // [0: 125 kHz, 1: 250 kHz, 2: 500 kHz, 3 ... 9 see table]
#define LORA_SPREADING_FACTOR 7 // [SF7..SF12]
#define LORA_CODINGRATE 1 // [1: 4/5, 2: 4/6, 3: 4/7, 4: 4/8]
#define LORA_PREAMBLE_LENGTH 8 // Same for Tx and Rx
#define LORA_SYMBOL_TIMEOUT 0 // Symbols
#define LORA_FIX_LENGTH_PAYLOAD_ON false
#define LORA_IQ_INVERSION_ON false
#define RX_TIMEOUT_VALUE 3000
#define TX_TIMEOUT_VALUE 3000
#define BUFFER_SIZE 64 // Define the payload size here
Fill the structure with the HW configuration
// Define the HW configuration between MCU and SX126x
hwConfig.CHIP_TYPE = SX1262_CHIP; // Example uses an eByte E22 module with an SX1262
hwConfig.PIN_LORA_RESET = PIN_LORA_RESET; // LORA RESET
hwConfig.PIN_LORA_NSS = PIN_LORA_NSS; // LORA SPI CS
hwConfig.PIN_LORA_SCLK = PIN_LORA_SCLK; // LORA SPI CLK
hwConfig.PIN_LORA_MISO = PIN_LORA_MISO; // LORA SPI MISO
hwConfig.PIN_LORA_DIO_1 = PIN_LORA_DIO_1; // LORA DIO_1
hwConfig.PIN_LORA_BUSY = PIN_LORA_BUSY; // LORA SPI BUSY
hwConfig.PIN_LORA_MOSI = PIN_LORA_MOSI; // LORA SPI MOSI
hwConfig.RADIO_TXEN = RADIO_TXEN; // LORA ANTENNA TX ENABLE
hwConfig.RADIO_RXEN = RADIO_RXEN; // LORA ANTENNA RX ENABLE
hwConfig.USE_DIO2_ANT_SWITCH = false; // Example uses an eByte E22 module which uses RXEN and TXEN pins as antenna control
hwConfig.USE_DIO3_TCXO = true; // Example uses an eByte E22 module which uses DIO3 to control oscillator voltage
hwConfig.USE_DIO3_ANT_SWITCH = false; // Only Insight ISP4520 module uses DIO3 as antenna control
hwConfig.USE_LDO = false; // Set to true if SX126x uses LDO instead of DCDC converter
hwConfig.USE_RXEN_ANT_PWR = false; // Antenna power is not controlled by a GPIO
SX126x-Arduino.h
and LoRaWan-Arduino.h
SX126x-RAK4630.h
and LoRaWan-RAK4630.h
SX126x-ISP4520.h
and LoRaWan-ISP4520.h
lora_hardware_init(hwConfig);
Some modules integrate an MCU and the SX126x LoRa transceiver and have a fixed connection between them. In these cases a simplified initialization can be used.
The ISP4520 module has the nRF52832 and SX1261 or SX1262 chips integrated in a module. Therefore the hardware configuration is fixed. To initialize the LoRa chip you need only to specify if the module is based on a SX1261 (ISP4520 EU version) or on a SX1262 (ISP4520 US version).
lora_isp4520_init(SX1262);
The RAK4630/4631 module has the nRF52840 and SX1262 chips integrated in a module. Therefore the hardware configuration is fixed.
lora_rak4630_init();
The RAK11300/11310 module has the RP2040 and SX1262 chips integrated in a module. Therefore the hardware configuration is fixed.
lora_rak11300_init();
The RAK13300 module is an IO module that has a LoRa SX1262 LoRa transceiver. It is made for the RAK11200 ESP32 module and the hardware configuration is fixed.
lora_rak13300_init();
When you want to use the deep sleep function of the ESP32 with external wake up source, you do not want to reset and reconfigure the SX126x chip after its IRQ woke up the ESP32. This re-init function sets up only the required definitions for the communication without resetting the SX126x
lora_hardware_re_init(hwConfig);
RadioEvents.TxDone = OnTxDone;
RadioEvents.RxDone = OnRxDone;
RadioEvents.TxTimeout = OnTxTimeout;
RadioEvents.RxTimeout = OnRxTimeout;
RadioEvents.RxError = OnRxError;
RadioEvents.CadDone = OnCadDone;
Initialize the radio and set the TX and RX parameters
Radio.Init(&RadioEvents);
Radio.SetChannel(RF_FREQUENCY);
Radio.SetTxConfig(MODEM_LORA, TX_OUTPUT_POWER, 0, LORA_BANDWIDTH,
LORA_SPREADING_FACTOR, LORA_CODINGRATE,
LORA_PREAMBLE_LENGTH, LORA_FIX_LENGTH_PAYLOAD_ON,
true, 0, 0, LORA_IQ_INVERSION_ON, TX_TIMEOUT_VALUE);
Radio.SetRxConfig(MODEM_LORA, LORA_BANDWIDTH, LORA_SPREADING_FACTOR,
LORA_CODINGRATE, 0, LORA_PREAMBLE_LENGTH,
LORA_SYMBOL_TIMEOUT, LORA_FIX_LENGTH_PAYLOAD_ON,
0, true, 0, 0, LORA_IQ_INVERSION_ON, true);
When you want to use the deep sleep function of the ESP32 with external wake up source, you do not want to reset and reconfigure the SX126x chip after its IRQ woke up the ESP32. Radio.ReInit() sets up only the required communication without resetting the SX1262. Radio.IrqProcessAfterDeepSleep() is checking the reason for the wake-up IRQ and calls the event handler
Radio.ReInit(&RadioEvents);
Radio.IrqProcessAfterDeepSleep();
Radio.Rx(RX_TIMEOUT_VALUE);
YOU NEED BELOW STEPS ONLY IF YOU WANT TO IMPLEMENT THE LORAWAN FUNCTIONALITY, IT IS NOT REQUIRED FOR BASIC LORA COMMUNICATION
If you want to use LoRaWan communication some additional steps are required.
You need to define a region. The defined region tells the library which frequency and which channels should be used. Valid regions are:
More information:
In addition you need
for your node.
Sparkfun has a nice tutorial how to get these requirements from TheThingsInternet
In addition you must define several LoRaWan parameters.
You can find a lot of information about LoRaWan on the LoRa Alliance website.
The LoRaWAN region is set during the lmh_init() call.
See lmh_init() for details.
To be able to send data over a gateway to an IoT application like TheThingsNetwork you need to set the EUIs and Keys for the device, the application and the sessions.
If you are using ABP activation all 6 values need to be set. If you are using OTAA activation, only the device EUI, the application EUI and the application key are required.
For the difference between ABP and OTAA activation read the TheThingsNetwork Wiki.
The EUIs, keys and address should be defined in your code like this:
// Device EUI
uint8_t nodeDeviceEUI[8] = {0x00, 0x95, 0x64, 0x1F, 0xDA, 0x91, 0x19, 0x0B};
// Application EUI
uint8_t nodeAppEUI[8] = {0x70, 0xB3, 0xD5, 0x7E, 0xD0, 0x02, 0x01, 0xE1};
// Application key for AES encryption
uint8_t nodeAppKey[16] = {0x07, 0xC0, 0x82, 0x0C, 0x30, 0xB9, 0x08, 0x70, 0x0C, 0x0F, 0x70, 0x06, 0x00, 0xB0, 0xBE, 0x09};
// Device address
uint32_t nodeDevAddr = 0x260116F8;
// Network session key for AES encryption
uint8_t nodeNwsKey[16] = {0x7E, 0xAC, 0xE2, 0x55, 0xB8, 0xA5, 0xE2, 0x69, 0x91, 0x51, 0x96, 0x06, 0x47, 0x56, 0x9D, 0x23};
// Application session key for AES encryption
uint8_t nodeAppsKey[16] = {0xFB, 0xAC, 0xB6, 0x47, 0xF3, 0x58, 0x45, 0xC7, 0x50, 0x7D, 0xBF, 0x16, 0x8B, 0xA8, 0xC1, 0x7C};
Then, just before initializing the library set these values with
// Setup the EUIs and Keys
lmh_setDevEui(nodeDeviceEUI);
lmh_setAppEui(nodeAppEUI);
lmh_setAppKey(nodeAppKey);
lmh_setNwkSKey(nodeNwsKey);
lmh_setAppSKey(nodeAppsKey);
lmh_setDevAddr(nodeDevAddr);
If the node talks to a single channel gateway you can fix the frequency and data rate and avoid frequency hopping. See more info in LoRaWan single channel gateway
lmh_setSingleChannelGateway(uint8_t userSingleChannel, int8_t userDatarate)
Initialize LoRaWan.
/**@brief Lora Initialisation
*
* @param callbacks Pointer to structure containing the callback functions
* @param lora_param Pointer to structure containing the parameters
* @param otaa Choose OTAA (true) or ABP (false) activation
* @param nodeClass Choose node class CLASS_A, CLASS_B or CLASS_C, default to CLASS_A
* @param region Choose LoRaWAN region to set correct region parameters, defaults to EU868
*
* @retval error status
*/
lmh_error_status lmh_init(lmh_callback_t *callbacks, lmh_param_t lora_param, bool otaa,
eDeviceClass nodeClass = CLASS_A,
LoRaMacRegion_t region = LORAMAC_REGION_EU868);
Valid regions are: LoRaMacRegion_t region This parameter selects the LoRaWAN region for your application. Allowed values for the region are:
REMARK
CN779-787 devices may not be produced, imported or installed after 2021-01-01; deployed devices may continue to operate through their normal end-of-life.
For some regions and some gateways you need to specifiy a sub band to be used. See more info in Limit frequency hopping to a sub band
lmh_setSubBandChannels(uint8_t subBand)
RAKwireless RAK4630/RAK4631
The subbands for each region are automatically preset to match with the RAKwireless gateways default settings. In this case you do not need to define the sub bands.
LoRaWan needs callbacks and parameters defined before initialization
/** Lora user application data buffer. */
static uint8_t m_lora_app_data_buffer[LORAWAN_APP_DATA_BUFF_SIZE];
/** Lora user application data structure. */
static lmh_app_data_t m_lora_app_data = {m_lora_app_data_buffer, 0, 0, 0, 0};
/**@brief Structure containing LoRaWan parameters, needed for lmh_init()
*
* Set structure members to
* LORAWAN_ADR_ON or LORAWAN_ADR_OFF to enable or disable adaptive data rate
* LORAWAN_DEFAULT_DATARATE OR DR_0 ... DR_5 for default data rate or specific data rate selection
* LORAWAN_PUBLIC_NETWORK or LORAWAN_PRIVATE_NETWORK to select the use of a public or private network
* JOINREQ_NBTRIALS or a specific number to set the number of trials to join the network
* LORAWAN_DEFAULT_TX_POWER or a specific number to set the TX power used
* LORAWAN_DUTYCYCLE_ON or LORAWAN_DUTYCYCLE_OFF to enable or disable duty cycles
* Please note that ETSI mandates duty cycled transmissions.
*/
static lmh_param_t lora_param_init = {LORAWAN_ADR_ON,
LORAWAN_DEFAULT_DATARATE, LORAWAN_PUBLIC_NETWORK,
JOINREQ_NBTRIALS, LORAWAN_DEFAULT_TX_POWER};
/**@brief Structure containing LoRaWan callback functions, needed for lmh_init() */
static lmh_callback_t lora_callbacks = {BoardGetBatteryLevel, BoardGetUniqueId, BoardGetRandomSeed,
lorawan_rx_handler, lorawan_has_joined_handler, lorawan_confirm_class_handler,
lorawan_join_failed_handler, lorawan_unconfirmed_finished, lorawan_confirmed_finished};
The following callbacks are implemented in the library, but you can override them in your application code:
BoardGetBatteryLevel
is an empty pre-defined callback in the library. Every board has a different method to read the battery level (or none at all). If you want the LoRaWAN node to report it's battery level, you should write your own function to read and return the battery level. Keep in mind that the LoRaWAN server expects the battery level as a value between 0 and 255. 100% battery level equals 255.
BoardGetUniqueId
is a pre-defined callback used by the library to get a unique ID of the board. This is used when the device EUI is assigned automatically. In most use cases this is not used.
BoardGetRandomSeed
is used together with BoardGetUniqueId
. In most use cases this is not used.
The following callbacks have to be implemented in your application code:
lorawan_rx_handler
is called when a Downlink was received from the LoRaWAN server. See examples how to implement it.
lorawan_has_joined_handler
is called after the node has successfully joined the network. Keep in mind that when ABP join method is used, this callback is called immediately after lmh_join()
.
lorawan_confirm_class_handler
is called if you change the nodes class with lmh_class_request()
.
lorawan_join_failed_handler
is called if the join process failed. Failing te join process can have multiple reasons. A few can be
lorawan_unconfirmed_finished
is called after a unconfirmed packet send is finished. It is called after RX1 window and RX2 window timed out or after a downlink from the LoRaWAN server was received.
lorawan_confirmed_finished
is called after a confirmed packet send is finished. It has a paramter that tells if a confirmation (ACK
) was received from the LoRaWAN server or not.
Join the LoRaWan network to be able to send and receive data. Default connection type is
void lmh_join(void)
By default when using LoRaWan communication, the node is using frequency hoping. That means that for each package to be sent a random frequency is chosen out of the predefined frequencies for a region. The frequency (== channels) for each region can be found in the file CHANNELS.MD.
If connecting the node to a single channel gateway this is a problem, because a single channel gateway can receive only on one channel (== frequency). To get around this problem the channel hoping can be disabled and a fixed frequency (channel) and datarate can be set by the function
void lmh_setSingleChannelGateway(uint8_t userSingleChannel, int8_t userDatarate);
The first paramenter is the channel (frequency) to be used to communicate with the single channel gateway.
Check the specification of your single channel gateway to find out on which channel (frequency) it is listening and then get the channel number from the file CHANNELS.MD.
The second parameter selects the datarate for the communication. Again check the specification of your single channel gateway to find out what datarate it is using and use it in the function call. It might be that instead of the datarate the spreading factor SF and bandwidth BW are documented. In this case you need to check the file DATARATE.MD to find out which datarate to choose.
E.g. the things4u ESP-1ch-Gateway-v5.0 single channel gateway when setup to US915 region is listening on 902.30 Mhz with a bandwidth of 125kHz and a spreading factor of 7.
In CHANNEL.MD you can find that 902.30 MHz is channel 0 and in DATARATE.MD you can find that SF7 and BW 125 kHz would be for region US915 the data rate DR_3.
In this example we fix the communication to the channel 0 with the datarate DR_3 (SF7 and BW125);
// Setup connection to a single channel gateway
lmh_setSingleChannelGateway(0, DR_3);
While testing the LoRaWan functionality I discovered that for some regions and some LoRaWan gateways it is required to limit the frequency hopping to a specific sub band of the region.
E.g. in the settings of the LoRaWan gateway I bought for testing (Dragino LPS8) you have not only to define the region, but as well one of 8 sub bands. The gateway will listen only on the selected sub band.
The problem is that if the LoRa node uses all available frequencies for frequency hopping, then for sure some of the packets will be lost, because they are sent on frequencies outside of the sub band on which the gateway is listening.
Depending on the region, there could be between 2 and 12 sub bands to select from. Each sub band consists of 8 frequencies with a fixed distance between each. The sub bands are selected by numbers starting with 1
for the first sub band of 8 frequencies.
You have to check with your LoRaWan gateway if you need to setup a sub band
Example to limit the frequency hopping to sub band #1
// For some regions we might need to define the sub band the gateway is listening to
/// \todo This is for Dragino LPS8 gateway. How about other gateways???
if (!lmh_setSubBandChannels(1))
{
Serial.println("lmh_setSubBandChannels failed. Wrong sub band requested?");
}
In Arduino IDE open Sketch->Include Library->Manage Libraries then search for SX126x-Arduino
In PlatformIO open PlatformIO Home, switch to libraries and search for SX126x-Arduino. Or install the library in the terminal with platformio lib install SX126x-Arduino
For manual installation download the archive, unzip it and place the SX126x-Android folder into the library directory.
In Arduino IDE this is usually <arduinosketchfolder>/libraries/
In PlatformIO this is usually <user/.platformio/lib>