Interfacing Zerodrag Nexus1 2.4GHz ELRS Receiver with Arduino

Learn how to interface the Zerodrag Nexus1 2.4 GHz ExpressLRS receiver that combines ESP8285 and SX1280 LoRa chips with Arduino.
Interfacing Zerodrag Nexus1 2.4 GHz ELRS Receiver with Arduino Tutorial Featured Image by CIRCUITSTATE Electronics

If you have been following us for some time, you must have read the LoRa tutorial we published before. In that tutorial, we covered almost everything you need to know about the LoRa RF technology and showed how you can interface the RA-01/RA-02 LoRa modules with Arduino. RA-01/RA-02 uses the SX1278 LoRa chipset from Semtech. Today, we have a new LoRa product to interface. It is the Nexus1 ELRS receiver from Zerodrag Technologies, an avionics design and manufacturing company from India. Nexus1 uses the Semtech SX1280 LoRa chipset and is specially designed for drones and other small fixed wing aircrafts. It is used as a wireless receiver to receive control signals from a wireless controller. In this tutorial, we will learn everything about the Nexus1 receiver and learn how to interface it with any Arduino boards.

Zerodrag Nexus1

Zerodrag Nexus1 2.4GHz ELRS Receiver with SX1280 Front and Back Sides by CIRCUITSTATE Electronics
Zerodrag Nexus1 ELRS receiver front and back sides. Source: Zerodrag Technologies.

The Nexus1 is a 2.4 GHz ELRS receiver is based on the SX1280 LoRa chipset from Semtech. Unlike the SX1278, the SX1280 utilizes the 2.4 GHz ISM (Industrial, Scientific, Medical) radio frequency band for communication. 2.4 GHz is also the same frequency band used by Wi-Fi, Bluetooth and other wireless standards. The uniqueness of LoRa modulation is the ability to perform long range communication with a low power budget. Nexus1 utilizes this feature to facilitate reliable communication between a drone and its operator. The default firmware that comes with the Nexus1 receiver is the ELRS, which is an open-source software stack for radio communication. Nexus1 is an ELRS-certified receiver and can be programmed to talk to multiple types of radio transmitters (controllers) that support the ELRS protocol. Even though the SX1280 can both transmit and receive (half-duplex only), the Nexus1 receiver is primarily used as a receiver because its transmit power is limited. The board also integrates an ESP8285 Wi-Fi SoC from Espressif as well as a 2.4 GHz chip antenna for Wi-Fi communication.

Specifications

Zerodrag Nexus1 2.4GHz ELRS Receiver with SX1280 with Supplied Antennas by CIRCUITSTATE Electronics
Two Zerodrag Nexus1 receivers with their supplied T antennas.
SpecificationValue
MicrocontrollerEspressif ESP8285
RF Transceiver ChipSemtech SX1280
Working Frequency 2.4450 GHz
RF TechnologyLoRa, FLRC, FSK, Wi-Fi (through ESP8285)
Maximum Transmit PowerUp to +12.5 dBm (17.78 mW)
Receiver SensitivityUp to -132 dBm
Maximum Data RateLoRa 202 kbps, FLRC 1300 kbps
Duplex TypeHalf-Duplex
Antenna Types1. UMCC (U.FL) for 2.4 GHz LoRa
2. Chip Antenna for 2.4 GHz Wi-Fi
Supply Voltage5 V (5.5V Max)
Interface/Logic Voltage3.3 V
Maximum Current Consumption
Dimensions20 x 13 mm
Weight1.2 g
Specifications of the Zerodrag Nexus1

Pinout

Zerodrag Nexus1 2.4GHz ELRS Receiver with SX1280 Pinout Diagram CIRCUITSTATE Electronics
Zerodrag Nexus1 ELR receiver pinout diagram. Source: Zerodrag Technologies.

Schematic

Zerodrag does not release the schematic of the Nexus1 receiver. But the design common across many manufacturers that adheres to the ELRS standard. Following is an open-source 2.4GHz ELRS receiver design schematic that is based on the ESP8285 and the SX1281 instead of the SX1280. It also uses a PA+LNA (Power Amplifier + Low Noise Amplifier) chip to increase the output power and the sensitivity. If you ignore the PA+LNA and the small difference between the LoRa chipset, the remaining parts are almost the same. So this can be a good reference for the Nexus1 receiver.

AnyLeaf ELRS 2.4GHz Single Radio Receiver Schematic Diagram CIRCUITSTATE Electronics
AnyLeaf 2.4GHz ELRS single radio receiver schematic. Source: AnyLeaf

SX1280

SX1280 and SX1281 are low-power, and long-range RF transceiver chips from Semtech. SX1280 supports LoRa, FLRC, GFSK, Ranging Engine and Advanced Engine. The SX1281 lacks the latter two features. Both SX1280 and SX1281 are available in a 4×4 mm QFN-24 SMD package.

SX1280 LoRa Transceiver Chip Block Diagram CIRCUITSTATE Electronics
SX1280 block diagram. Source: Semtech

Pinout

SX1280 LoRa Transceiver Chip Pinout Diagram by CIRCUITSTATE Electronics
SX1280 pinout diagram. Source: Semtech
Pin NumberPin NameType (I = Input, O = Output)SPI Description UART Description
0GNDExposed Ground pad
1VR_PARegulated supply for the PA
2VDD_INIRegulated supply input. Connect to Pin 12.
3NRESETIReset signal, active low with internal pull-up at 50kO
4XTAReference oscillator connection or TCXO input
5GNDGround
6XTBReference oscillator connection
7BUSYOTransceiver busy indicator
8DIO1I/OOptional multi-purpose digital I/O
9DIO2I/OOptional multi-purpose digital I/O
10DIO3I/OOptional multi-purpose digital I/O
11VBAT_IOISupply for the Digital IO interface (1.8V to 3.7V). Must be VBAT.
12DCC_FBORegulated output voltage from the internal regulator
13GNDGround
14DCC_SWODC-DC Switcher Output
15VBATISupply for the RFIC (1.8V to 3.7V). Must be VBAT_IO.
16MISO_TXOSPI peripheral outputUART Transmit pin
17MOSI_RXISPI peripheral input UART Receive pin
18SCK_RTSNISPI clock UART Request To Send
19NSS_CTSISPI Chip Select UART Clear To Send
20GNDGround
21GNDGround
22RFIOI/ORF transmit output and receive input
23GNDGround
24GNDGround

If you are new to the LoRa technology, we highly recommend reading the following tutorial from us. Even though it is written for the SX1278, the details are still applicable to the SX1280.

Interfacing-RA-01-RA-02-SX1278-LoRa-Modules-with-ESP32-using-Arduino-Featured-Image-CIRCUITSTATE-Electronics-01

Interfacing RA-01/RA-02 SX1278 LoRa Modules with ESP32 using Arduino

Learn how to interface RA-01 or RA-02 SX1278-based LoRa modules from Ai-Thinker with ESP32 using Arduino. LoRa can add long range and low-power wireless communication capability to your projects.

Breakout Board

Because the Nexus1 receiver does not have a RESET or BOOT button, we soldered the board on a perfboard and added the button externally. We also added an auto-programming circuit so that we can use the RTS and DTR signal to program the board automatically. But for some reason, the auto-programming did not work.

To program the board, you need to press and hold the BOOT button (connecting the GPIO0 to GND) and then press and release the RESET button. This will cause the ESP8285 to go into the bootloader mode. You can also apply the power to the board while the BOOT button is pressed to achieve the same.

Wiring

Wiring the Nexus1 receiver is simple. The board breaks out 4 pins – 5V, TX, RX and GND. You can supply +5V to the power pin to start the module. Do not supply more than 5.5V to the pins as it can damage the LDO on the board. The TX and RX are UART pins coming from the ESP8285 and they can be connected to the UART pins of any Arduino board.

We will use a low-cost FT232L USB-Serial converter to power the Nexus1 boards and to communicate with it via a serial monitor. You only need to connect the 5V, TX, RX and GND pins. The RTS and DTR pins need not to be connected (we modified our FT232L adapters to break out the DTR pin instead of the CTS pin, if you are wondering).

ELRS

As mentioned earlier, ELRS is a completely open-source software stack for radio communication in different frequency bands. The name ELRS is an acronym for Express Long Range System. The ELRS firmware relies on the SX12xx series LoRa chipsets from Semtech and implements a light-weight datagram protocol on top of the LoRa physical interface (PHY). Due to the advantage of LoRa, devices with the ELRS firmware can achieve high packet rates up to 1 KHz as well as long ranges of up to several tens of kilometres. Currently, numerous manufacturers have ELRS-supported products including transmitters and receivers. To make the best use of the technology, ELRS also provide a configurator software to customize just about anything of your ELRS-supported products. You can find more details about the ELRS project from this FAQ page.

If you are good at embedded C/C++, you can clone/fork the ELRS project from GitHub and customize it as however you want. The software is a PlatformIO project that you can modify and build using VS Code and the PlatformIO extension. If you are new to VS Code and PlatformIO, we have great tutorials to get you started.

Getting Started with Platform IDE with VS Code for Embedde Software Development Featured Image CIRCUITSTATE Electronics

Getting Started with PlatformIO – Unified IDE for Embedded Software Development

Learn how to use the PlatformIO unified ecosystem for embedded software development with the help of modern Visual Studio Code IDE.
How-To-Use-VS-Code-To-Create-and-Upload-Arduino-Sketches-Featured-Image-01-1-1

How to Use VS Code for Creating and Uploading Arduino Sketches

Learn how to use the popular VS Code IDE to develop Arduino projects and upload your sketches to Arduino boards directly, without using Arduino IDE.

FLRC

An exciting feature of the SX128x series of chips is the FLRC modem. FLRC stands for Fast Long Range Communication and as the name implies, it allows us to send and receive faster data packets with increased sensitivity (more range). FLRC uses the GFSK (Gaussian Frequency Shift Keying) modulation compared to the regular FSK (Frequency Shift Keying) modulation. But don’t be fooled by the name of FLRC itself, because LoRa will still give you a longer range compared to FLRC for the given transmitted power. But FLRC gives you more data rate with a decent range which is better than the regular FSK. You can combine FLRC with FEC (Forward Error Correction) to increase the robustness of the FLRC packets at a reduced data rate, due to the error correction overhead.

SX1280 LoRa Transceiver Chip FLRC Fixed Length Packet Format CIRCUITSTATE Electronics
FLRC fixed-length packet format
SX1280 LoRa Transceiver Chip FLRC Variable Length Packet Format CIRCUITSTATE Electronics
FLRC variable-length packet format

LoRa Ranging

LoRa Ranging is another new feature of the SX1280. It uses a packet similar to the LoRa Explicit (Variable Length) packet, and with the help of a ranging request originating from a transmitter, and a receiver responding to the ranging request, we can deduce the distance between both ends from the time it takes for the message to travel (Time-of-Flight).

SX12XX-LoRa Arduino Library

In the RA-01/RA-02 LoRa module tutorial, we used the LoRa Arduino library from Sandeep Mistry. This time, we can use the SX12XX-LoRa library from Stuart Robinson. It is a fully featured driver for the SX12xx series LoRa chipsets including the SX1280. The library has examples for every model, and supports both LoRa and FLRC transmissions on the SX128x series chips. We will modify two of the examples from the project to create an Arduino sketch that we can run on the Nexus1 receiver.

Arduino Code

LoRa Sender & Receiver

Following is the Arduino code we created to test the basic transmit and receive functionalities of the SX1280 LoRa chip on the Nexus1 receiver. We combined the examples 3_LoRa_Transmitter and 4_LoRa_Receiver to create it. You can download the project as a PlatformIO project and build it with the help of VS Code.


//===========================================================================================//
// Includes

#include <SPI.h>
#include <SX128XLT.h>

//===========================================================================================//
// Macros

#define   PIN_LORA_CS         15
#define   PIN_LORA_BUSY       5
#define   PIN_LORA_RESET      2
#define   PIN_LORA_DIO1       4
#define   PIN_LORA_ANTSEL     9
#define   PIN_LED1            16

#define   LORA_DEVICE         DEVICE_SX1280 // We need to define the device we are using  

#define   RXBUFFER_SIZE       255 //RX buffer size  

//===========================================================================================//
// Globals

//LoRa Modem Parameters
const uint32_t Frequency = 2445000000;           // Frequency of transmissions
const int32_t Offset = 0;                        // Offset frequency for calibration purposes
const uint8_t Bandwidth = LORA_BW_0400;          // LoRa bandwidth
const uint8_t SpreadingFactor = LORA_SF7;        // LoRa spreading factor
const uint8_t CodeRate = LORA_CR_4_5;            // LoRa coding rate

const int8_t TXpower = 10;                       // Power for transmissions in dBm
const uint16_t packet_delay = 1000;              // mS delay between packets

uint8_t TXPacketL;
uint32_t TXPacketCount, startmS, endmS;
uint8_t buff[] = "Hello World 1234567890";

uint32_t RXpacketCount;
uint32_t errors;
uint8_t RXBUFFER [RXBUFFER_SIZE]; // Create the buffer that received packets are copied into
uint8_t RXPacketL;  // Stores length of packet received
int16_t  PacketRSSI;  // Stores RSSI of received packet
int8_t  PacketSNR;  // Stores signal to noise ratio of received packet

SX128XLT loraDevice; // Create a library class instance called loraDevice

//===========================================================================================//
// Forward Declarations

void initLoRa();
void initLoRaTx();
void initLoRaRx();
void loraTask();
void loraSender();
void loraReceiver();
void printTxPacketOK();
void printTxPacketError();
void printRxPacketOK();
void printRxPacketError();
void printElapsedTime();
void flashLED (uint16_t flashes, uint16_t delaymS);

//===========================================================================================//

void setup() {
  Serial.begin (115200);
  while (!Serial);

  Serial.println();
  Serial.println ("SX1280 LoRa Test");

  ESP.wdtDisable();
  *((volatile uint32_t*) 0x60000900) &= ~(1); // Hardware WDT OFF
  // *((volatile uint32_t*) 0x60000900) |= 1; // Hardware WDT ON

  SPI.begin();
  initLoRa();
}

//===========================================================================================//

void loop() {
  loraTask();
}

//===========================================================================================//

void initLoRa() {
  #if OPT_IS_SENDER == 1
    initLoRaTx();
  #elif OPT_IS_SENDER == 0
    initLoRaRx();
  #endif
}

//===========================================================================================//

void initLoRaTx() {
  pinMode (PIN_LED1, OUTPUT);
  // pinMode (PIN_LORA_ANTSEL, OUTPUT);
  // digitalWrite (PIN_LORA_ANTSEL, LOW); // Setting Antenna Select to onboard one

  flashLED (2, 125); // Two quick LED flashes to indicate program start

  Serial.println (F("initLoRaTx(): Starting transmitter.."));

  // SPI beginTranscation is normally part of library routines, but if it is disabled in library
  // a single instance is needed here, so uncomment the program line below
  // SPI.beginTransaction (SPISettings (8000000, MSBFIRST, SPI_MODE0));

  // Setup hardware pins used by device, then check if device is found.
  if (loraDevice.begin (PIN_LORA_CS, PIN_LORA_RESET, PIN_LORA_BUSY, PIN_LORA_DIO1, -1, -1, LORA_DEVICE)) {
    Serial.println (F("initLoRaTx(): LoRa device is found."));
    flashLED (2, 125); // Two further quick LED flashes to indicate device found
    delay (1000);
  }
  else {
    Serial.println (F("initLoRaTx(): No device is responding."));
    while (1) {
      flashLED (50, 50); // Long fast speed LED flash indicates device error
    }
  }

  // The 'Setup LoRa device' list below can be replaced with a single function call;
  // loraDevice.setupLoRa (Frequency, Offset, SpreadingFactor, Bandwidth, CodeRate);

  // Setup LoRa device
  loraDevice.setMode (MODE_STDBY_RC);
  loraDevice.setRegulatorMode (USE_LDO);
  loraDevice.setPacketType (PACKET_TYPE_LORA);
  loraDevice.setRfFrequency (Frequency, Offset);
  loraDevice.setBufferBaseAddress (0, 0);
  loraDevice.setModulationParams (SpreadingFactor, Bandwidth, CodeRate);
  loraDevice.setPacketParams (12, LORA_PACKET_VARIABLE_LENGTH, 255, LORA_CRC_ON, LORA_IQ_NORMAL, 0, 0);
  loraDevice.setDioIrqParams (IRQ_RADIO_ALL, (IRQ_TX_DONE + IRQ_RX_TX_TIMEOUT), 0, 0);
  loraDevice.setHighSensitivity();

  Serial.println();
  loraDevice.printModemSettings(); // Reads and prints the configured LoRa settings, useful check
  Serial.println();
  loraDevice.printOperatingSettings(); // Reads and prints the configured operating settings, useful check
  Serial.println();
  Serial.println();
  loraDevice.printRegisters (0x900, 0x9FF); // Print contents of device registers
  Serial.println();
  Serial.println();

  Serial.println (F("initLoRaTx(): Transmitter is ready."));
  Serial.println();
}

//===========================================================================================//

void initLoRaRx() {
  pinMode (PIN_LED1, OUTPUT); // Setup pin as output for indicator LED
  flashLED (2, 125); // Two quick LED flashes to indicate program start

  Serial.println (F("initLoRaRx(): Starting receiver.."));

  // SPI beginTranscation is normally part of library routines, but if it is disabled in the library,
  // a single instance is needed here, so uncomment the program line below.
  // SPI.beginTransaction (SPISettings (8000000, MSBFIRST, SPI_MODE0));

  // Setup hardware pins used by device, then check if device is found.
  if (loraDevice.begin (PIN_LORA_CS, PIN_LORA_RESET, PIN_LORA_BUSY, PIN_LORA_DIO1, -1, -1, LORA_DEVICE)) {
    Serial.println (F("initLoRaRx(): LoRa device is found."));
    flashLED (2, 125);
    delay (1000);
  }
  else {
    Serial.println (F("initLoRaRx(): No device is responding."));
    while (1) {
      flashLED (50, 50); // Long fast speed LED flash indicates device error
    }
  }

  // The 'Setup LoRa device' list below can be replaced with a single function call;
  // loraDevice.setupLoRa (Frequency, Offset, SpreadingFactor, Bandwidth, CodeRate);

  // Setup LoRa device
  loraDevice.setMode (MODE_STDBY_RC);
  loraDevice.setRegulatorMode (USE_LDO);
  loraDevice.setPacketType (PACKET_TYPE_LORA);
  loraDevice.setRfFrequency (Frequency, Offset);
  loraDevice.setBufferBaseAddress (0, 0);
  loraDevice.setModulationParams (SpreadingFactor, Bandwidth, CodeRate);
  loraDevice.setPacketParams (12, LORA_PACKET_VARIABLE_LENGTH, 255, LORA_CRC_ON, LORA_IQ_NORMAL, 0, 0);
  loraDevice.setDioIrqParams (IRQ_RADIO_ALL, (IRQ_TX_DONE + IRQ_RX_TX_TIMEOUT), 0, 0);
  loraDevice.setHighSensitivity();

  Serial.println();
  loraDevice.printModemSettings(); // Reads and prints the configured LoRa settings, useful check
  Serial.println();
  loraDevice.printOperatingSettings(); // Reads and prints the configured operting settings, useful check
  Serial.println();
  Serial.println();
  loraDevice.printRegisters (0x900, 0x9FF); // Print contents of device registers
  Serial.println();
  Serial.println();

  Serial.print (F("initLoRaRx(): Receiver is ready - RXBUFFER_SIZE "));
  Serial.println (RXBUFFER_SIZE);
  Serial.println();
}

//===========================================================================================//

void loraTask() {
  #if OPT_IS_SENDER == 1
    loraSender();
  #elif OPT_IS_SENDER == 0
    loraReceiver();
  #endif
}

//===========================================================================================//

void loraSender() {
  Serial.print (F("loraSender(): Sending message with power "));
  Serial.print (TXpower); // Print the transmit power defined
  Serial.println (F(" dBm.."));
  Serial.println (F("loraSender(): Packet = "));
  Serial.flush();

  TXPacketL = sizeof (buff);  // Set TXPacketL to length of array
  buff [TXPacketL - 1] = '*'; // Replace null character at buffer end so its visible on reciver

  loraDevice.printASCIIPacket (buff, TXPacketL);  // Print the buffer (the sent packet) as ASCII

  digitalWrite (PIN_LED1, HIGH);
  startmS =  millis();  // Start transmit timer
  if (loraDevice.transmit (buff, TXPacketL, 10000, TXpower, WAIT_TX)) {  // Will return packet length sent if OK, otherwise 0 if transmit, timeout 10 seconds
    endmS = millis(); // Packet sent, note end time
    TXPacketCount++;
    Serial.println();
    printTxPacketOK();
  }
  else {
    Serial.println();
    printTxPacketError(); // Transmit packet returned 0, there was an error
  }

  digitalWrite (PIN_LED1, LOW);
  Serial.println();
  delay (packet_delay);  // Have a delay between packets
}

//===========================================================================================//

void loraReceiver() {
  RXPacketL = loraDevice.receive (RXBUFFER, RXBUFFER_SIZE, 60000, WAIT_RX); // Wait for a packet to arrive with 60seconds (60000mS) timeout

  digitalWrite (PIN_LED1, HIGH); // Something has happened

  PacketRSSI = loraDevice.readPacketRSSI(); // Read the recived RSSI value
  PacketSNR = loraDevice.readPacketSNR(); // Read the received SNR value

  Serial.print (F("loraReceiver(): RSSI = "));
  Serial.print (PacketRSSI); // Print the RSSI value
  Serial.print (F(", SNR = "));
  Serial.print (PacketSNR); // Print the SNR value
  Serial.println (F(" dB"));

  if (RXPacketL == 0) { // If the loraDevice.receive() function detects an error, RXpacketL == 0
    printRxPacketError();
  }
  else {
    printRxPacketOK();
  }

  digitalWrite (PIN_LED1, LOW); // LED off
  Serial.println();
}

//===========================================================================================//

void printTxPacketOK() {
  uint16_t localCRC;

  Serial.println ("printTxPacketOK(): Status:");

  Serial.print (F("BytesSent = "));
  Serial.println (TXPacketL); // Print transmitted packet length

  localCRC = loraDevice.CRCCCITT (buff, TXPacketL, 0xFFFF);
  Serial.print (F("CRC = 0x"));
  Serial.println (localCRC, HEX); // Print CRC of sent packet

  Serial.print (F("Transmit Time = "));
  Serial.print (endmS - startmS); // Print transmit time of packet
  Serial.println (F(" mS"));

  Serial.print (F("Packets Counter = "));
  Serial.println (TXPacketCount); // Print total of packets sent OK
}

//===========================================================================================//

void printTxPacketError() {
  //if here there was an error transmitting packet
  uint16_t IRQStatus;
  IRQStatus = loraDevice.readIrqStatus(); //read the the interrupt register
  Serial.print (F(" SendError,"));
  Serial.print (F("Length,"));
  Serial.print (TXPacketL); //print transmitted packet length
  Serial.print (F(",IRQreg,"));
  Serial.print (IRQStatus, HEX); //print IRQ status
  loraDevice.printIrqStatus(); //prints the text of which IRQs set
}

//===========================================================================================//

void printRxPacketOK() {
  uint16_t IRQStatus, localCRC;

  IRQStatus = loraDevice.readIrqStatus(); //read the LoRa device IRQ status register

  RXpacketCount++;

  printElapsedTime(); //print elapsed time to Serial Monitor
  Serial.println (F(", Received Packet = "));
  loraDevice.printASCIIPacket (RXBUFFER, RXPacketL); //print the packet as ASCII characters
  Serial.println();

  localCRC = loraDevice.CRCCCITT (RXBUFFER, RXPacketL, 0xFFFF);  //calculate the CRC, this is the external CRC calculation of the RXBUFFER
  Serial.print (F("CRC = 0x")); //contents, not the LoRa device internal CRC
  Serial.println (localCRC, HEX);

  Serial.print (F("RSSI = "));
  Serial.print (PacketRSSI);
  Serial.println (F(" dBm"));

  Serial.print (F("SNR = "));
  Serial.print (PacketSNR);
  Serial.println (F(" dB"));

  Serial.print (F("Length = "));
  Serial.println (RXPacketL);

  Serial.print (F("Packets = "));
  Serial.println (RXpacketCount);

  Serial.print (F("Errors = "));
  Serial.println (errors);

  Serial.print (F("IRQreg = 0x"));
  Serial.print (IRQStatus, HEX);

  Serial.println();
}

//===========================================================================================//

void printRxPacketError() {
  uint16_t IRQStatus;
  IRQStatus = loraDevice.readIrqStatus(); // Read the LoRa device IRQ status register

  printElapsedTime(); // Print elapsed time to Serial Monitor

  if (IRQStatus & IRQ_RX_TIMEOUT) { // Check for an RX timeout
    Serial.println (F("printRxPacketError(): RXTimeout"));
  }
  else {
    errors++;
    Serial.println (F("printRxPacketError(): Status:"));

    Serial.print (F("RSSI = "));
    Serial.print (PacketRSSI);
    Serial.println (F(" dBm"));

    Serial.print (F("SNR = "));
    Serial.print (PacketSNR);
    Serial.println (F(" dB"));

    Serial.print (F("Length = "));
    Serial.println (loraDevice.readRXPacketL()); // Get the real packet length

    Serial.print (F("Packets = "));
    Serial.println (RXpacketCount);

    Serial.print (F("Errors = "));
    Serial.println (errors);

    Serial.print (F("IRQreg = 0x"));
    Serial.print (IRQStatus, HEX);

    loraDevice.printIrqStatus(); // Print the names of the IRQ registers set
  }

  delay (250); // Gives a longer buzzer and LED flash for error
}

//===========================================================================================//

void flashLED (uint16_t flashes, uint16_t delaymS) {
  uint16_t index;
  for (index = 1; index <= flashes; index++) {
    digitalWrite (PIN_LED1, HIGH);
    delay (delaymS);
    digitalWrite (PIN_LED1, LOW);
    delay (delaymS);
  }
}

//===========================================================================================//

void printElapsedTime() {
  float seconds;
  seconds = millis() / 1000;
  Serial.print (F("Time = "));
  Serial.print (seconds, 0);
  Serial.print (F("s"));
}

//===========================================================================================//
main.cpp

Since we have both the transmitter and receiver code in a single main.cpp file, we are using the PlatformIO configuration file to create two targets Sender and Receiver. There is a C/C++ macro called OPT_IS_SENDER in the configuration file with different values for the Sender and Receiver targets. When you change the PlatformIO build targets, the code will be automatically compiled for both the Sender and Receiver examples. Following is our platformio.ini configuration file.

; PlatformIO Project Configuration File
;
;   Build options: build flags, source filter
;   Upload options: custom upload port, speed and extra flags
;   Library options: dependencies, extra library storages
;   Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html

[env]
platform = espressif8266
board = esp8285
framework = arduino
lib_deps =
  https://github.com/StuartsProjects/SX12XX-LoRa.git
extra_scripts = extra_upload.py

[env:Sender]
upload_protocol = esptool
upload_speed = 2000000
upload_port = COM26
build_flags = -D OPT_IS_SENDER=1

[env:Receiver]
upload_protocol = esptool
upload_speed = 2000000
upload_port = COM13
build_flags = -D OPT_IS_SENDER=0
INI

To help with uploading the programs to two different devices, we have also added an extra Python script that you can run with then following command in the Terminal. Running the command will compile both targets and upload them sequentially. Make sure to edit the upload_port variable in the configuration file as per the COM port assigned in your computer.

pio run -t upload
Terminal

Following is the Python script.

import os
from SCons.Script import DefaultEnvironment

env = DefaultEnvironment()

def after_build (source, target, env):
    print ("Uploading to Sender...")
    env.Execute ("pio run -t upload -e Sender")
    print ("Uploading to Receiver...")
    env.Execute ("pio run -t upload -e Receiver")

env.AddPostAction ("buildprog", after_build)
extra_upload.py

You can press the BOOT button and then press and release the RESET button to begin uploading the new code. After the uploading is complete, you can open any serial monitor applications and create two instances; one for the Sender and one for the Receiver. We are using the VS Code’s Serial Monitor plugin here. The LED on the Sender will flash whenever it is sending a message. The LED on the Receiver will blink when it receives a message. Following is the serial monitor log from the Sender. We are transmitting at an output power of 10 dBm here.

SX1280 LoRa Test
initLoRaTx(): Starting transmitter..
initLoRaTx(): LoRa device is found.

SX1280,PACKET_TYPE_LORA,2444999936hz,SF7,BW406250,CR4:5
SX1280,PACKET_TYPE_LORA,Preamble_12,Explicit,PayloadL_255,CRC_ON,IQ_NORMAL,LNAgain_HighSensitivity

Reg    0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
0x900  80 FF 77 41 20 FA BC 13 C1 80 00 00 00 00 00 61 
0x910  9C 44 00 00 00 19 00 00 00 19 87 65 43 21 7F FF 
0x920  FF FF FF 00 70 37 12 50 D0 80 00 C0 5F D2 8F 0A 
0x930  00 C0 00 00 00 24 00 21 28 B0 30 0D 01 51 63 0C 
0x940  58 0B 32 0A 16 24 6B 96 00 18 00 00 00 00 00 00 
0x950  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
0x960  00 00 00 00 00 00 00 00 00 00 FF FF FF FF FF FF 
0x970  FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 04 
0x980  00 0B 18 70 00 00 00 4C 00 F0 64 00 00 00 00 00 
0x990  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
0x9A0  00 08 EC B8 9D 8A E6 66 04 00 00 00 00 00 00 00 
0x9B0  00 08 EC B8 9D 8A E6 66 04 00 00 00 00 00 00 00 
0x9C0  00 16 00 3F E8 01 FF FF FF FF 5E 4D 25 10 55 55 
0x9D0  55 55 55 55 55 55 55 55 55 55 55 55 55 00 00 00 
0x9E0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
0x9F0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 


initLoRaTx(): Transmitter is ready.

loraSender(): Sending message with power 10 dBm..
loraSender(): Packet = 
Hello World 1234567890*
printTxPacketOK(): Status:
BytesSent = 23
CRC = 0xDAAB
Transmit Time = 21 mS
Packets Counter = 1

loraSender(): Sending message with power 10 dBm..
loraSender(): Packet = 
Hello World 1234567890*
printTxPacketOK(): Status:
BytesSent = 23
CRC = 0xDAAB
Transmit Time = 21 mS
Packets Counter = 2

loraSender(): Sending message with power 10 dBm..
loraSender(): Packet = 
Hello World 1234567890*
printTxPacketOK(): Status:
BytesSent = 23
CRC = 0xDAAB
Transmit Time = 21 mS
Packets Counter = 3

loraSender(): Sending message with power 10 dBm..
loraSender(): Packet = 
Hello World 1234567890*
printTxPacketOK(): Status:
BytesSent = 23
CRC = 0xDAAB
Transmit Time = 21 mS
Packets Counter = 4

loraSender(): Sending message with power 10 dBm..
loraSender(): Packet = 
Hello World 1234567890*
printTxPacketOK(): Status:
BytesSent = 23
CRC = 0xDAAB
Transmit Time = 21 mS
Packets Counter = 5

loraSender(): Sending message with power 10 dBm..
loraSender(): Packet = 
Hello World 1234567890*
printTxPacketOK(): Status:
BytesSent = 23
CRC = 0xDAAB
Transmit Time = 21 mS
Packets Counter = 6

loraSender(): Sending message with power 10 dBm..
loraSender(): Packet = 
Hello World 1234567890*
printTxPacketOK(): Status:
BytesSent = 23
CRC = 0xDAAB
Transmit Time = 20 mS
Packets Counter = 7
Transmitter Serial Monitor

Following is the serial monitor output from the Receiver.

SX1280 LoRa Test
initLoRaRx(): Starting receiver..
initLoRaRx(): LoRa device is found.

SX1280,PACKET_TYPE_LORA,2444999936hz,SF7,BW406250,CR4:5
SX1280,PACKET_TYPE_LORA,Preamble_12,Explicit,PayloadL_255,CRC_ON,IQ_NORMAL,LNAgain_HighSensitivity

Reg    0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
0x900  80 FF 77 41 20 FA BC 13 C1 80 00 00 00 00 00 61 
0x910  9C 44 00 00 00 19 00 00 00 19 87 65 43 21 7F FF 
0x920  FF FF FF 00 70 37 12 50 D0 80 00 C0 5F D2 8F 0A 
0x930  00 C0 00 00 00 24 00 21 28 B0 30 0D 01 51 63 0C 
0x940  58 0B 32 0A 16 24 6B 96 00 18 00 00 00 00 00 00 
0x950  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
0x960  00 00 00 00 00 00 00 00 00 00 FF FF FF FF FF FF 
0x970  FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 04 
0x980  00 0B 18 70 00 00 00 4C 00 F0 64 00 00 00 00 00 
0x990  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
0x9A0  00 08 EC B8 9D 8A E6 66 04 00 00 00 00 00 00 00 
0x9B0  00 08 EC B8 9D 8A E6 66 04 00 00 00 00 00 00 00 
0x9C0  00 16 00 3F E8 01 FF FF FF FF 5E 4D 25 10 55 55 
0x9D0  55 55 55 55 55 55 55 55 55 55 55 55 55 00 00 00 
0x9E0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
0x9F0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 


initLoRaRx(): Receiver is ready - RXBUFFER_SIZE 255

loraReceiver(): RSSI = -51, SNR = 13 dB
Time = 3s, Received Packet = 
Hello World 1234567890*
CRC = 0xDAAB
RSSI = -51 dBm
SNR = 13 dB
Length = 23
Packets = 1
Errors = 0
IRQreg = 0x8012

loraReceiver(): RSSI = -51, SNR = 13 dB
Time = 4s, Received Packet = 
Hello World 1234567890*
CRC = 0xDAAB
RSSI = -51 dBm
SNR = 13 dB
Length = 23
Packets = 2
Errors = 0
IRQreg = 0x8012

loraReceiver(): RSSI = -50, SNR = 11 dB
Time = 5s, Received Packet = 
Hello World 1234567890*
CRC = 0xDAAB
RSSI = -50 dBm
SNR = 11 dB
Length = 23
Packets = 3
Errors = 0
IRQreg = 0x8012

loraReceiver(): RSSI = -51, SNR = 14 dB
Time = 6s, Received Packet = 
Hello World 1234567890*
CRC = 0xDAAB
RSSI = -51 dBm
SNR = 14 dB
Length = 23
Packets = 4
Errors = 0
IRQreg = 0x8012

loraReceiver(): RSSI = -51, SNR = 14 dB
Time = 7s, Received Packet = 
Hello World 1234567890*
CRC = 0xDAAB
RSSI = -51 dBm
SNR = 14 dB
Length = 23
Packets = 5
Errors = 0
IRQreg = 0x8012

loraReceiver(): RSSI = -50, SNR = 13 dB
Time = 8s, Received Packet = 
Hello World 1234567890*
CRC = 0xDAAB
RSSI = -50 dBm
SNR = 13 dB
Length = 23
Packets = 6
Errors = 0
IRQreg = 0x8012

loraReceiver(): RSSI = -50, SNR = 13 dB
Time = 9s, Received Packet = 
Hello World 1234567890*
CRC = 0xDAAB
RSSI = -50 dBm
SNR = 13 dB
Length = 23
Packets = 7
Errors = 0
IRQreg = 0x8012
Receiver Serial Monitor

The RSSI (Received Signal Strength Indicator) indicates how strong the received RF signal is. Its value will increase when the signal strength increases. You can test this by moving the transmitter and receiver away from and then closer to each other. The signal strength will drop significantly if you keep the antennas too close to each other, contrary to what you might expect. The SNR (Signal to Noise Ratio) indicates the ratio between noise (any signals that can not be decoded) and the information signal. Its value will also increase when the signal quality increases compared to the background noise.

SX1280 2.4GHz LoRa Arduino Example Code with PlatformIO and VS Code by CIRCUITSTATE Electronics
LoRa Arduino example with PlatformIO and VS Code

Code Explained

Following is the overview of the steps we need to follow to get the transmitter to working.

  1. Initialize the SX1280 as a transmitter by loading the registers with the transmitter configurations. This include common parameters for the LoRa PHY as well as transmitter specific values. The communication between the microcontroller and the SX1280 is carried out through the SPI. If the configuration is loaded correctly, proceed to the next step.

  2. Use the built-in library function to load the data buffer and supply the transmit power and a wait time. The function will try to transmit the data within the specified time period.

  3. Wait for the interrupt from the DIO1 pin or the IRQ software register. If the transmission is not complete in time, print the error status.

  4. If the packet is transmitted within the specified time, print the time it took to transmit the data.

  5. Count the number of packets sent.

In the Arduino’s setup() function, we have the initLoRa() function to initialize the LoRa chip. initLoRa() is a common function that will initialize the SX1280 based on the target type, which is determined by the value of OPT_IS_SENDER. If the value is 1, then the SX1280 is initialized as a transmitter and if the value is 0, it is initialized as a receiver. Functions initLoRaTx() and initLoRaTx() are used for the initializations.

In the initLoRaTx() function, we first try to initialize the SPI interface and try to communicate with the SX1280. If we can not communicate with the chip, it could mean that the pin assignments or the device type configurations are not correct. After that, we can load the RF configuration to the SX1280 with the following lines.

  // Setup LoRa device
  loraDevice.setMode (MODE_STDBY_RC);
  loraDevice.setRegulatorMode (USE_LDO);
  loraDevice.setPacketType (PACKET_TYPE_LORA);
  loraDevice.setRfFrequency (Frequency, Offset);
  loraDevice.setBufferBaseAddress (0, 0);
  loraDevice.setModulationParams (SpreadingFactor, Bandwidth, CodeRate);
  loraDevice.setPacketParams (12, LORA_PACKET_VARIABLE_LENGTH, 255, LORA_CRC_ON, LORA_IQ_NORMAL, 0, 0);
  loraDevice.setDioIrqParams (IRQ_RADIO_ALL, (IRQ_TX_DONE + IRQ_RX_TX_TIMEOUT), 0, 0);
  loraDevice.setHighSensitivity();
C++

For some of the parameters, we are using custom values and for some other we are using the default values. If you only want to specify the important values and keep the other parameters to their defaults, you can use following single line to load the configuration.

  loraDevice.setupLoRa (Frequency, Offset, SpreadingFactor, Bandwidth, CodeRate);
C++

After that we can print the configuration and the snapshot of the configuration registers. The code then proceeds to the loop() function where it repeatedly calls the LoRa related function loraTask(). Based on the type of target, the function will call either loraSender() or the loraReceiver(). In the loraSender() function, we simply fetch the data from a buffer, calculate the length and use the transmit() function to send it. If the transmission is successful, we will print the time it took to transmit it. If the transmission ends up in an error, we will print the error status.

The process for the receiver is similar.

  1. Initialize the SX1280 as a transmitter by loading the registers with the receiver configurations, which is the same as the transmitter. The communication between the microcontroller and the SX1280 is carried out through the SPI. If the configuration is loaded correctly, proceed to the next step.

  2. Wait for a message to arrive. The SX1280 will generate an interrupt through the DIO1 pin of the IRQ registers.

  3. When an interrupt is received, read the receive buffer of the SX1280 and save it to a local buffer and print it along with the RSSI and SNR values.

FLRC Sender & Receiver

Here, we combined the examples 52_FLRC_Transmitter and 53_FLRC_Receiver to create a single PlatformIO project. Similar to the previous example, you can choose the target ton compile and upload the code.


//===========================================================================================//
// Includes

#include <SPI.h>
#include <SX128XLT.h>

//===========================================================================================//
// Macros

#define   PIN_LORA_CS         15
#define   PIN_LORA_BUSY       5
#define   PIN_LORA_RESET      2
#define   PIN_LORA_DIO1       4
#define   PIN_LORA_ANTSEL     9
#define   PIN_LED1            16

#define   LORA_DEVICE         DEVICE_SX1280 // We need to define the device we are using  

#define   RXBUFFER_SIZE       127 // RX buffer size  

//===========================================================================================//
// Globals

//FLRC Modem Parameters
const uint32_t Frequency = 2445000000;  // Frequency of transmissions
const int32_t Offset = 0; // Offset frequency for calibration purposes

const uint8_t BandwidthBitRate = FLRC_BR_1_300_BW_1_2;  // FLRC bandwidth and bit rate, 1.3Mbs
const uint8_t CodingRate = FLRC_CR_1_0; // FLRC coding rate
const uint8_t BT = RADIO_MOD_SHAPING_BT_1_0;  // FLRC BT
const uint32_t Syncword = 0x01234567; // FLRC uses syncword

// Transmitter configuration
const int8_t TXpower = 0;  // Power for transmissions in dBm
const uint16_t packet_delay = 1000; // mS delay between packets
uint8_t TXPacketL;
uint32_t TXPacketCount, startmS, endmS;
uint8_t buff[] = "Hello World 1234567890";

// Receiver configuration
uint32_t RXpacketCount;
uint32_t errors;
uint8_t RXBUFFER [RXBUFFER_SIZE]; // Create the buffer that received packets are copied into
uint8_t RXPacketL;  // Stores length of packet received
int16_t  PacketRSSI;  // Stores RSSI of received packet
int8_t  PacketSNR;  // Stores signal to noise ratio of received packet

SX128XLT loraDevice; // Create a library class instance called loraDevice

//===========================================================================================//
// Forward Declarations

void initLoRa();
void initLoRaTx();
void initLoRaRx();
void loraTask();
void loraSender();
void loraReceiver();
void printTxPacketOK();
void printTxPacketError();
void printRxPacketOK();
void printRxPacketError();
void printElapsedTime();
void flashLED (uint16_t flashes, uint16_t delaymS);

//===========================================================================================//

void setup() {
  Serial.begin (115200);
  while (!Serial);

  Serial.println ("\nSX1280 LoRa Test");

  ESP.wdtDisable();
  *((volatile uint32_t*) 0x60000900) &= ~(1); // Hardware WDT OFF
  // *((volatile uint32_t*) 0x60000900) |= 1; // Hardware WDT ON

  SPI.begin();
  initLoRa();
}

//===========================================================================================//

void loop() {
  loraTask();
}

//===========================================================================================//

void initLoRa() {
  #if OPT_IS_SENDER == 1
    initLoRaTx();
  #elif OPT_IS_SENDER == 0
    initLoRaRx();
  #endif
}

//===========================================================================================//

void initLoRaTx() {
  pinMode (PIN_LED1, OUTPUT);
  // pinMode (PIN_LORA_ANTSEL, OUTPUT);
  // digitalWrite (PIN_LORA_ANTSEL, LOW); // Setting Antenna Select to onboard one

  flashLED (2, 125); // Two quick LED flashes to indicate program start

  Serial.println (F("initLoRaTx(): Starting FLRC transmitter.."));

  // SPI beginTranscation is normally part of library routines, but if it is disabled in library
  // a single instance is needed here, so uncomment the program line below
  // SPI.beginTransaction (SPISettings (8000000, MSBFIRST, SPI_MODE0));

  // Setup hardware pins used by device, then check if device is found.
  if (loraDevice.begin (PIN_LORA_CS, PIN_LORA_RESET, PIN_LORA_BUSY, PIN_LORA_DIO1, -1, -1, LORA_DEVICE)) {
    Serial.println (F("initLoRaTx(): FLRC device is found."));
    flashLED (2, 125); // Two further quick LED flashes to indicate device found
    delay (1000);
  }
  else {
    Serial.println (F("initLoRaTx(): No FLRC device is responding."));
    while (1) {
      flashLED (50, 50); // Long fast speed LED flash indicates device error
    }
  }

  //Setup FLRC
  loraDevice.setupFLRC (Frequency, Offset, BandwidthBitRate, CodingRate, BT, Syncword);

  //The full details of the setupFLRC function call above are listed below
  //loraDevice.setMode (MODE_STDBY_RC);
  //loraDevice.setRegulatorMode (USE_LDO);
  //loraDevice.setPacketType (PACKET_TYPE_FLRC);
  //loraDevice.setRfFrequency (Frequency, Offset);
  //loraDevice.setBufferBaseAddress (0, 0);
  //loraDevice.setModulationParams (BandwidthBitRate, CodingRate, BT);
  //loraDevice.setPacketParams (PREAMBLE_LENGTH_32_BITS, FLRC_SYNC_WORD_LEN_P32S, RADIO_RX_MATCH_SYNCWORD_1, RADIO_PACKET_VARIABLE_LENGTH, 127, RADIO_CRC_3_BYTES, RADIO_WHITENING_OFF);
  //loraDevice.setDioIrqParams (IRQ_RADIO_ALL, (IRQ_TX_DONE + IRQ_RX_TX_TIMEOUT), 0, 0);              //set for IRQ on TX done and timeout on DIO1
  //loraDevice.setSyncWord1 (Syncword);

  Serial.println();
  loraDevice.printModemSettings(); // Reads and prints the configured LoRa settings, useful check
  Serial.println();
  loraDevice.printOperatingSettings(); // Reads and prints the configured operating settings, useful check
  Serial.println();
  Serial.println();
  loraDevice.printRegisters (0x900, 0x9FF); // Print contents of device registers
  Serial.println();
  Serial.println();

  Serial.println (F("initLoRaTx(): FLRC transmitter is ready."));
  Serial.println();
}

//===========================================================================================//

void initLoRaRx() {
  pinMode (PIN_LED1, OUTPUT); // Setup pin as output for indicator LED
  flashLED (2, 125); // Two quick LED flashes to indicate program start

  Serial.println (F("initLoRaRx(): Starting FLRC receiver.."));

  // SPI beginTranscation is normally part of library routines, but if it is disabled in the library,
  // a single instance is needed here, so uncomment the program line below.
  // SPI.beginTransaction (SPISettings (8000000, MSBFIRST, SPI_MODE0));

  // Setup hardware pins used by device, then check if device is found.
  if (loraDevice.begin (PIN_LORA_CS, PIN_LORA_RESET, PIN_LORA_BUSY, PIN_LORA_DIO1, -1, -1, LORA_DEVICE)) {
    Serial.println (F("initLoRaRx(): FLRC device is found."));
    flashLED (2, 125);
    delay (1000);
  }
  else {
    Serial.println (F("initLoRaRx(): No FLRC device is responding."));
    while (1) {
      flashLED (50, 50); // Long fast speed LED flash indicates device error
    }
  }

  // Setup FLRC
   loraDevice.setupFLRC (Frequency, Offset, BandwidthBitRate, CodingRate, BT, Syncword);

  // The full details of the setupFLRC function call above are listed below.
  //loraDevice.setMode (MODE_STDBY_RC);
  //loraDevice.setRegulatorMode (USE_LDO);
  //loraDevice.setPacketType (PACKET_TYPE_FLRC);
  //loraDevice.setRfFrequency (Frequency, Offset);
  //loraDevice.setBufferBaseAddress (0, 0);
  //loraDevice.setModulationParams (BandwidthBitRate, CodingRate, BT);
  //loraDevice.setPacketParams (PREAMBLE_LENGTH_32_BITS, FLRC_SYNC_WORD_LEN_P32S, RADIO_RX_MATCH_SYNCWORD_1, RADIO_PACKET_VARIABLE_LENGTH, 127, RADIO_CRC_3_BYTES, RADIO_WHITENING_OFF);
  //loraDevice.setDioIrqParams (IRQ_RADIO_ALL, (IRQ_TX_DONE + IRQ_RX_TX_TIMEOUT), 0, 0); // Set for IRQ on TX done and timeout on DIO1
  //loraDevice.setSyncWord1 (Syncword);

  loraDevice.setFLRCPayloadLengthReg (127); // FLRC will filter packets on receive according to length, so set to longest packet

  Serial.println();
  loraDevice.printModemSettings(); // Reads and prints the configured modem settings, useful check
  Serial.println();
  loraDevice.printOperatingSettings(); // Reads and prints the configured operting settings, useful check
  Serial.println();
  Serial.println();
  loraDevice.printRegisters (0x900, 0x9FF); // Print contents of device registers
  Serial.println();
  Serial.println();

  Serial.print (F("initLoRaRx(): Receiver is ready - RXBUFFER_SIZE "));
  Serial.println (RXBUFFER_SIZE);
  Serial.println();
}

//===========================================================================================//

void loraTask() {
  #if OPT_IS_SENDER == 1
    loraSender();
  #elif OPT_IS_SENDER == 0
    loraReceiver();
  #endif
}

//===========================================================================================//

void loraSender() {
  Serial.print (F("loraSender(): Sending message with power "));
  Serial.print (TXpower); // Print the transmit power defined
  Serial.println (F(" dBm.."));
  Serial.println (F("loraSender(): Packet = "));
  Serial.flush();

  TXPacketL = sizeof (buff);  // Set TXPacketL to length of array
  buff [TXPacketL - 1] = '*'; // Replace null character at buffer end so its visible on receiver

  loraDevice.printASCIIPacket (buff, TXPacketL);  // Print the buffer (the sent packet) as ASCII

  digitalWrite (PIN_LED1, HIGH);
  startmS =  millis();  // Start transmit timer

  TXPacketL = loraDevice.transmit (buff, TXPacketL, 10000, TXpower, WAIT_TX);  //will return 0 if transmit fails, timeout 10 seconds

  if (TXPacketL > 0) {  // Will return packet length sent if OK, otherwise 0 if transmit, timeout 10 seconds
    endmS = millis(); // Packet sent, note end time
    TXPacketCount++;
    Serial.println();
    printTxPacketOK();
  }
  else {
    Serial.println();
    printTxPacketError(); // Transmit packet returned 0, there was an error
  }

  digitalWrite (PIN_LED1, LOW);
  Serial.println();
  delay (packet_delay);  // Have a delay between packets
}

//===========================================================================================//

void loraReceiver() {
  RXPacketL = loraDevice.receive (RXBUFFER, RXBUFFER_SIZE, 60000, WAIT_RX); // Wait for a packet to arrive with 60seconds (60000mS) timeout

  digitalWrite (PIN_LED1, HIGH); // Something has happened

  PacketRSSI = loraDevice.readPacketRSSI(); // Read the recived RSSI value
  PacketSNR = loraDevice.readPacketSNR(); // Read the received SNR value

  Serial.print (F("loraReceiver(): RSSI = "));
  Serial.print (PacketRSSI); // Print the RSSI value
  Serial.print (F(", SNR = "));
  Serial.print (PacketSNR); // Print the SNR value
  Serial.println (F(" dB"));

  if (RXPacketL == 0) { // If the loraDevice.receive() function detects an error, RXpacketL == 0
    printRxPacketError();
  }
  else {
    printRxPacketOK();
  }

  digitalWrite (PIN_LED1, LOW); // LED off
  Serial.println();
}

//===========================================================================================//

void printTxPacketOK() {
  uint16_t localCRC;

  Serial.println ("printTxPacketOK(): Status:");

  Serial.print (F("BytesSent = "));
  Serial.println (TXPacketL); // Print transmitted packet length

  localCRC = loraDevice.CRCCCITT (buff, TXPacketL, 0xFFFF);
  Serial.print (F("CRC = 0x"));
  Serial.println (localCRC, HEX); // Print CRC of sent packet

  Serial.print (F("Transmit Time = "));
  Serial.print (endmS - startmS); // Print transmit time of packet
  Serial.println (F(" mS"));

  Serial.print (F("Packets Counter = "));
  Serial.println (TXPacketCount); // Print total of packets sent OK
}

//===========================================================================================//

void printTxPacketError() {
  //if here there was an error transmitting packet
  uint16_t IRQStatus;
  IRQStatus = loraDevice.readIrqStatus(); //read the the interrupt register
  Serial.print (F(" SendError,"));
  Serial.print (F("Length,"));
  Serial.print (TXPacketL); //print transmitted packet length
  Serial.print (F(",IRQreg,"));
  Serial.print (IRQStatus, HEX); //print IRQ status
  loraDevice.printIrqStatus(); //prints the text of which IRQs set
}

//===========================================================================================//

void printRxPacketOK() {
  uint16_t IRQStatus, localCRC;

  IRQStatus = loraDevice.readIrqStatus(); //read the LoRa device IRQ status register

  RXpacketCount++;

  printElapsedTime(); //print elapsed time to Serial Monitor
  Serial.println (F(", Received Packet = "));
  loraDevice.printASCIIPacket (RXBUFFER, RXPacketL); //print the packet as ASCII characters
  Serial.println();

  localCRC = loraDevice.CRCCCITT (RXBUFFER, RXPacketL, 0xFFFF);  //calculate the CRC, this is the external CRC calculation of the RXBUFFER
  Serial.print (F("CRC = 0x")); //contents, not the LoRa device internal CRC
  Serial.println (localCRC, HEX);

  Serial.print (F("RSSI = "));
  Serial.print (PacketRSSI);
  Serial.println (F(" dBm"));

  Serial.print (F("SNR = "));
  Serial.print (PacketSNR);
  Serial.println (F(" dB"));

  Serial.print (F("Length = "));
  Serial.println (RXPacketL);

  Serial.print (F("Packets = "));
  Serial.println (RXpacketCount);

  Serial.print (F("Errors = "));
  Serial.println (errors);

  Serial.print (F("IRQreg = 0x"));
  Serial.print (IRQStatus, HEX);

  Serial.println();
}

//===========================================================================================//

void printRxPacketError() {
  uint16_t IRQStatus;
  IRQStatus = loraDevice.readIrqStatus(); // Read the LoRa device IRQ status register

  printElapsedTime(); // Print elapsed time to Serial Monitor

  if (IRQStatus & IRQ_RX_TIMEOUT) { // Check for an RX timeout
    Serial.println (F("printRxPacketError(): RXTimeout"));
  }
  else {
    errors++;
    Serial.println();
    Serial.println (F("printRxPacketError(): Status:"));

    Serial.print (F("RSSI = "));
    Serial.print (PacketRSSI);
    Serial.println (F(" dBm"));

    Serial.print (F("SNR = "));
    Serial.print (PacketSNR);
    Serial.println (F(" dB"));

    Serial.print (F("Length = "));
    Serial.println (loraDevice.readRXPacketL()); // Get the real packet length

    Serial.print (F("Packets = "));
    Serial.println (RXpacketCount);

    Serial.print (F("Errors = "));
    Serial.println (errors);

    Serial.print (F("IRQreg = 0x"));
    Serial.println (IRQStatus, HEX);
    
    Serial.print (F("printRxPacketError(): IRQ Status = "));
    loraDevice.printIrqStatus(); // Print the names of the IRQ registers set
    Serial.println();
  }

  delay (250); // Gives a longer buzzer and LED flash for error
}

//===========================================================================================//

void flashLED (uint16_t flashes, uint16_t delaymS) {
  uint16_t index;
  for (index = 1; index <= flashes; index++) {
    digitalWrite (PIN_LED1, HIGH);
    delay (delaymS);
    digitalWrite (PIN_LED1, LOW);
    delay (delaymS);
  }
}

//===========================================================================================//

void printElapsedTime() {
  float seconds;
  seconds = millis() / 1000;
  Serial.print (F("Time = "));
  Serial.print (seconds, 0);
  Serial.print (F("s"));
}

//===========================================================================================//
main.cpp

Following is the output from the Transmitter in the serial monitor. Notice how small the transmit time is. It is less than 1 millisecond. Compared to that, our LoRa transmitter took around 20 ms to transmit the same message.


SX1280 LoRa Test
initLoRaTx(): Starting FLRC transmitter..
initLoRaTx(): FLRC device is found.

SX1280,PACKET_TYPE_FLRC,2444999936hz,BandwidthBitRate_FLRC_BR_1_300_BW_1_2,CodingRate_CR_1_0,BT_1
SX1280,PACKET_TYPE_FLRC,Preamble_32_BITS,SyncWordLength_4,SyncWordMatch_1,VariableLengthPacket,PayloadLength_127,CRC_3_Bytes,Whitening_OFF,LNAgain_HighSensitivity

Reg    0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
0x900  80 0C 7B 02 20 FA C0 00 00 80 00 00 00 00 00 FF 
0x910  FF FF 00 00 00 19 00 00 00 19 87 65 43 21 7F FF 
0x920  FF FF FF 0C 70 37 0A 50 D0 80 00 C0 5F D2 8F 0A 
0x930  00 C0 00 00 00 24 00 21 28 B0 30 09 01 59 70 08 
0x940  58 0B 32 0A 14 24 6A 96 00 18 00 00 00 00 00 00 
0x950  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
0x960  00 00 00 00 00 00 00 00 00 00 FF FF FF FF FF FF 
0x970  FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 04 
0x980  00 0B 18 70 00 3F 00 4C 00 F0 64 00 00 00 00 00 
0x990  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
0x9A0  14 06 66 BC 13 C1 E6 66 04 00 00 00 00 00 00 00 
0x9B0  14 06 66 BC 13 C1 E6 66 04 00 00 00 00 00 00 00 
0x9C0  60 74 00 7F FC 01 FF FF FF FF 5E 4D 25 94 55 01 
0x9D0  23 45 67 55 55 55 55 55 55 55 55 55 55 00 00 00 
0x9E0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
0x9F0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 


initLoRaTx(): FLRC transmitter is ready.
loraSender(): Sending message with power 0 dBm..
loraSender(): Packet = 
Hello World 1234567890*
printTxPacketOK(): Status:
BytesSent = 23
CRC = 0xDAAB
Transmit Time = 0 mS
Packets Counter = 1

loraSender(): Sending message with power 0 dBm..
loraSender(): Packet = 
Hello World 1234567890*
printTxPacketOK(): Status:
BytesSent = 23
CRC = 0xDAAB
Transmit Time = 1 mS
Packets Counter = 2

loraSender(): Sending message with power 0 dBm..
loraSender(): Packet = 
Hello World 1234567890*
printTxPacketOK(): Status:
BytesSent = 23
Transmitter Serial Monitor

But unlike LoRa, FLRC will produce more errors if the spectrum (frequency bands) is being heavily used, such as when you have multiple Wi-Fi and Bluetooth devices nearby. When an error occurs, the FLRC Receiver will a message like the following.

loraReceiver(): RSSI = -99, SNR = -14 dB
Time = 60s
printRxPacketError(): Status:
RSSI = -99 dBm
SNR = -14 dB
Length = 32
Packets = 57
Errors = 1
IRQreg = 0x46
printRxPacketError(): IRQ Status = ,IRQ_RX_DONE,IRQ_SYNCWORD_VALID,IRQ_CRC_ERROR
Receiver Serial Monitor

Following is the normal output from the Receiver device.


SX1280 LoRa Test
initLoRaRx(): Starting FLRC receiver..
initLoRaRx(): FLRC device is found.

SX1280,PACKET_TYPE_FLRC,2444999936hz,BandwidthBitRate_FLRC_BR_1_300_BW_1_2,CodingRate_CR_1_0,BT_1
SX1280,PACKET_TYPE_FLRC,Preamble_32_BITS,SyncWordLength_4,SyncWordMatch_1,VariableLengthPacket,PayloadLength_127,CRC_3_Bytes,Whitening_OFF,LNAgain_HighSensitivity

Reg    0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
0x900  80 0C 7B 02 20 FA C0 00 00 80 00 00 00 00 00 FF 
0x910  FF FF 00 00 00 19 00 00 00 19 87 65 43 21 7F FF 
0x920  FF FF FF 0C 70 37 0A 50 D0 80 00 C0 5F D2 8F 0A 
0x930  00 C0 00 00 00 24 00 21 28 B0 30 09 01 59 70 08 
0x940  58 0B 32 0A 14 24 6A 96 00 18 00 00 00 00 00 00 
0x950  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
0x960  00 00 00 00 00 00 00 00 00 00 FF FF FF FF FF FF 
0x970  FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 04 
0x980  00 0B 18 70 00 3F 00 4C 00 F0 64 00 00 00 00 00 
0x990  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
0x9A0  14 06 66 BC 13 C1 E6 66 04 00 00 00 00 00 00 00 
0x9B0  14 06 66 BC 13 C1 E6 66 04 00 00 00 00 00 00 00 
0x9C0  60 74 00 7F FC 01 FF FF FF FF 5E 4D 25 94 55 01 
0x9D0  23 45 67 55 55 55 55 55 55 55 55 55 55 00 00 00 
0x9E0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
0x9F0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 


initLoRaRx(): Receiver is ready - RXBUFFER_SIZE 127

loraReceiver(): RSSI = -32, SNR = 16 dB
Time = 2s, Received Packet = 
Hello World 1234567890*
CRC = 0xDAAB
RSSI = -32 dBm
SNR = 16 dB
Length = 23
Packets = 1
Errors = 0
IRQreg = 0x6

loraReceiver(): RSSI = -32, SNR = 16 dB
Time = 3s, Received Packet = 
Hello World 1234567890*
CRC = 0xDAAB
RSSI = -32 dBm
SNR = 16 dB
Length = 23
Packets = 2
Errors = 0
IRQreg = 0x6

loraReceiver(): RSSI = -32, SNR = 16 dB
Time = 4s, Received Packet = 
Hello World 1234567890*
CRC = 0xDAAB
RSSI = -32 dBm
SNR = 16 dB
Length = 23
Packets = 3
Errors = 0
IRQreg = 0x6

loraReceiver(): RSSI = -32, SNR = 16 dB
Time = 5s, Received Packet = 
Hello World 1234567890*
CRC = 0xDAAB
RSSI = -32 dBm
SNR = 16 dB
Length = 23
Packets = 4
Errors = 0
IRQreg = 0x6
Receiver Serial Monitor

Code Explained

The FLRC code is similar to the LoRa Sender/Receiver but instead of loading LoRa-specific parameters, we load the FLRC-specific parameters with the following line.

// Setup FLRC
loraDevice.setupFLRC (Frequency, Offset, BandwidthBitRate, CodingRate, BT, Syncword);
C++

As you can see, we are initializing the FLRC with a minimum number of parameters leaving everything else to the default. You can also individually, specify the parameters. One important thing with the FLRC is the presence of the Syncword. FLRC required a sync word to properly decode the packet. Both the transmitter and the receiver should use the same sync word in order to communicate successfully. Sending the data is almost similar to the LoRa code. The rest of the code is self-explanatory.

LoRa Ranging

For the LoRa ranging example, we combined the examples 54_Ranging_Master and the 55_Ranging_Slave. The code should be self-explanatory at this point.


//===========================================================================================//
// Includes

#include <SPI.h>
#include <SX128XLT.h>

//===========================================================================================//
// Macros

#define   PIN_LORA_CS         15
#define   PIN_LORA_BUSY       5
#define   PIN_LORA_RESET      2
#define   PIN_LORA_DIO1       4
#define   PIN_LORA_ANTSEL     9
#define   PIN_LED1            16

#define   LORA_DEVICE         DEVICE_SX1280 // We need to define the device we are using  

//===========================================================================================//
// Globals

//LoRa Modem Parameters
const uint32_t Frequency = 2445000000; // Frequency of transmissions in hz
const int32_t Offset = 0; // Offset frequency in hz for calibration purposes
const uint8_t Bandwidth = LORA_BW_0800; // LoRa bandwidth
const uint8_t SpreadingFactor = LORA_SF8; // LoRa spreading factor
const uint8_t CodeRate = LORA_CR_4_5; // LoRa coding rate
const uint16_t Calibration = 11350; // Manual Ranging calibrarion value

const int8_t RangingTXPower = 10; // Transmit power used
const uint32_t RangingAddress = 16; // Must match address in receiver

const uint16_t waittimemS = 10000; // Wait this long in mS for packet before assuming timeout
const uint16_t TXtimeoutmS = 5000; // Ranging TX timeout in mS
const uint16_t packet_delaymS = 0; // Forced extra delay in mS between ranging requests
const uint16_t rangingcount = 5; // Number of times ranging is carried out for each distance measurment
float distance_adjustment = 1.0000; // Adjustment factor to calculated distance

const int8_t TXpower = 10;                       // Transmit power used
const uint16_t  rangingRXTimeoutmS = 0xFFFF;     // Ranging RX timeout in mS

uint16_t ranging_errors, rangings_valid, ranging_results;
uint16_t IrqStatus;
uint32_t endwaitmS, startrangingmS, range_result_sum, range_result_average;
float distance, distance_sum, distance_average;
bool ranging_error;
int32_t range_result;
int16_t RangingRSSI;
uint32_t response_sent;

SX128XLT loraDevice; // Create a library class instance called loraDevice

//===========================================================================================//
// Forward Declarations

void initLoRa();
void initInitiator();
void initResponder();
void loraTask();
void responderFunction();
void initiatorFunction();
void flashLED (uint16_t flashes, uint16_t delaymS);

//===========================================================================================//

void setup() {
  Serial.begin (115200);
  while (!Serial);

  Serial.println ("\nSX1280 LoRa Test");

  ESP.wdtDisable();
  *((volatile uint32_t*) 0x60000900) &= ~(1); // Hardware WDT OFF
  // *((volatile uint32_t*) 0x60000900) |= 1; // Hardware WDT ON

  SPI.begin();
  initLoRa();
}

//===========================================================================================//

void loop() {
  loraTask();
}

//===========================================================================================//

void initLoRa() {
  #if OPT_IS_INITIATOR == 1
    initInitiator();
  #elif OPT_IS_INITIATOR == 0
    initResponder();
  #endif
}

//===========================================================================================//

void initInitiator() {
  pinMode (PIN_LED1, OUTPUT);
  // pinMode (PIN_LORA_ANTSEL, OUTPUT);
  // digitalWrite (PIN_LORA_ANTSEL, LOW); // Setting Antenna Select to onboard one

  flashLED (2, 125); // Two quick LED flashes to indicate program start

  Serial.println (F("initInitiator(): Starting ranging initiator.."));

  if (loraDevice.begin (PIN_LORA_CS, PIN_LORA_RESET, PIN_LORA_BUSY, PIN_LORA_DIO1, -1, -1, LORA_DEVICE)) {
    Serial.println (F("initInitiator(): Device is found."));
    flashLED (2, 125);
    delay (1000);
  }
  else {
    Serial.println (F("initInitiator(): No device is responding."));

    while (1) {
      flashLED (50, 50); // Long fast flash indicates device error
    }
  }

  loraDevice.setupRanging (Frequency, Offset, SpreadingFactor, Bandwidth, CodeRate, RangingAddress, RANGING_MASTER);

  // loraDevice.setRangingCalibration (Calibration); // Override automatic lookup of calibration value from library table

  Serial.println();
  loraDevice.printModemSettings(); // Reads and prints the configured LoRa settings, useful check
  Serial.println();
  loraDevice.printOperatingSettings(); // Reads and prints the configured operating settings, useful check
  Serial.println();
  Serial.println();
  loraDevice.printRegisters (0x900, 0x9FF); // Print contents of device registers, normally 0x900 to 0x9FF
  Serial.println();
  Serial.println();

  Serial.println (F("initInitiator(): Ranging configuration ="));
  Serial.print (F("Address = "));
  Serial.println (RangingAddress);
  Serial.print (F("Calibration Value = "));
  Serial.println (loraDevice.getSetCalibrationValue());

  Serial.println (F("initInitiator(): Ranging initiator RAW ready."));
  Serial.println();

  delay (2000);
}

//===========================================================================================//

void initResponder() {
  pinMode (PIN_LED1, OUTPUT); // Setup pin as output for indicator LED
  flashLED (2, 125); // Two quick LED flashes to indicate program start

  Serial.println (F("initResponder(): Starting ranging responder.."));

  if (loraDevice.begin (PIN_LORA_CS, PIN_LORA_RESET, PIN_LORA_BUSY, PIN_LORA_DIO1, -1, -1, LORA_DEVICE)) {
    Serial.println (F("initResponder(): Device is found."));
    flashLED (2, 125);
    delay (1000);
  }
  else {
    Serial.println (F("initResponder(): No device is responding."));

    while (1) {
      flashLED (50, 50); // Long fast speed flash indicates device error
    }
  }

  //The function call list below shows the complete setup for the LoRa device for ranging using the information
  //defined in the Settings.h file.
  //The 'Setup LoRa device for Ranging' list below can be replaced with a single function call, note that
  //the calibration value will be loaded automatically from the table in the library;
  //loraDevice.setupRanging(Frequency, Offset, SpreadingFactor, Bandwidth, CodeRate, RangingAddress, RangingRole);

  loraDevice.setupRanging (Frequency, Offset, SpreadingFactor, Bandwidth, CodeRate, RangingAddress, RANGING_SLAVE);

  //***************************************************************************************************
  //Setup LoRa device for Ranging Slave
  //***************************************************************************************************
  /*
    loraDevice.setMode (MODE_STDBY_RC);
    loraDevice.setPacketType (PACKET_TYPE_RANGING);
    loraDevice.setModulationParams (SpreadingFactor, Bandwidth, CodeRate);
    loraDevice.setPacketParams (12, LORA_PACKET_VARIABLE_LENGTH, 0, LORA_CRC_ON, LORA_IQ_NORMAL, 0, 0);
    loraDevice.setRfFrequency (Frequency, Offset);
    loraDevice.setTxParams (TXpower, RADIO_RAMP_02_US);
    loraDevice.setRangingMasterAddress (RangingAddress);
    loraDevice.setRangingSlaveAddress (RangingAddress);
    loraDevice.setRangingCalibration (loraDevice.lookupCalibrationValue(SpreadingFactor, Bandwidth));
    loraDevice.setRangingRole (RANGING_SLAVE);
    loraDevice.writeRegister (REG_RANGING_FILTER_WINDOW_SIZE, 8); //set up window size for ranging averaging
    loraDevice.setHighSensitivity();
  */
  //***************************************************************************************************

  loraDevice.setRangingCalibration (11300); // Override automatic lookup of calibration value from library table

  Serial.println();
  Serial.println (F("initResponder(): Ranging configuration ="));
  Serial.print (F("Address = "));
  Serial.println (RangingAddress);
  Serial.print (F("Calibration Value = "));
  Serial.println (loraDevice.getSetCalibrationValue());

  Serial.println (F("initResponder(): Ranging responder is ready."));
  Serial.println();
  delay (2000);
}

//===========================================================================================//

void loraTask() {
  #if OPT_IS_INITIATOR == 1
    initiatorFunction();
  #elif OPT_IS_INITIATOR == 0
    responderFunction();
  #endif
}

//===========================================================================================//

void responderFunction() {
  loraDevice.receiveRanging (RangingAddress, 0, TXpower, NO_WAIT);

  endwaitmS = millis() + rangingRXTimeoutmS;

  while (!digitalRead (PIN_LORA_DIO1) && (millis() <= endwaitmS)); // Wait for Ranging valid or timeout

  if (millis() >= endwaitmS) {
    Serial.println (F("responderFunction(): Ranging receive timeout error."));
    flashLED (2, 100); // Single flash to indicate timeout
  }
  else {
    IrqStatus = loraDevice.readIrqStatus();
    digitalWrite (PIN_LED1, HIGH);

    if (IrqStatus & IRQ_RANGING_SLAVE_RESPONSE_DONE) {
      response_sent++;
      Serial.print (F("responderFunction(): Response sent = "));
      Serial.println (response_sent);
    }
    else {
      Serial.println (F("responderFunction(): Responder error."));
      Serial.print ("IRQ = 0x");
      Serial.println (IrqStatus, HEX);
      loraDevice.printIrqStatus();
    }

    digitalWrite (PIN_LED1, LOW);
    Serial.println();
  }
}

//===========================================================================================//

void initiatorFunction() {
  uint8_t index;
  distance_sum = 0;
  range_result_sum = 0;
  ranging_results = 0; // Count of valid results in each loop

  for (index = 1; index <= rangingcount; index++) {
    startrangingmS = millis();

    Serial.println (F("initiatorFunction(): Initiating ranging.."));

    loraDevice.transmitRanging (RangingAddress, TXtimeoutmS, RangingTXPower, WAIT_TX);

    IrqStatus = loraDevice.readIrqStatus();

    if (IrqStatus & IRQ_RANGING_MASTER_RESULT_VALID) {
      ranging_results++;
      rangings_valid++;

      digitalWrite (PIN_LED1, HIGH);

      Serial.println (F("initiatorFunction(): Ranging result:"));
      Serial.print (F("Status = Valid, "));

      range_result = loraDevice.getRangingResultRegValue (RANGING_RESULT_RAW);
      Serial.print (F("Register = "));
      Serial.print (range_result);

      if (range_result > 800000) {
        range_result = 0;
      }

      range_result_sum = range_result_sum + range_result;

      distance = loraDevice.getRangingDistance (RANGING_RESULT_RAW, range_result, distance_adjustment);
      distance_sum = distance_sum + distance;

      Serial.print (F(", Distance = "));
      Serial.print (distance, 1);

      Serial.print (F(", RSSIReg = "));
      Serial.print (loraDevice.readRegister (REG_RANGING_RSSI));

      RangingRSSI = loraDevice.getRangingRSSI();
      Serial.print (F(", RSSI = "));
      Serial.print (RangingRSSI);
      Serial.print (F(" dBm"));

      Serial.println();

      digitalWrite (PIN_LED1, LOW);
    }
    else {
      ranging_errors++;
      distance = 0;
      range_result = 0;

      Serial.println (F("initiatorFunction(): Ranging error."));
      Serial.print (F("IRQ = 0x"));
      Serial.println (IrqStatus, HEX);
      Serial.println();
    }

    delay (packet_delaymS);

    if (index == rangingcount) {
      range_result_average = (range_result_sum / ranging_results);

      if (ranging_results == 0) {
        distance_average = 0;
      }
      else {
        distance_average = (distance_sum / ranging_results);
      }

      Serial.println (F("initiatorFunction(): Ranging average results:"));

      Serial.print (F("Total Valid = "));
      Serial.print (rangings_valid);

      Serial.print (F(", Total Errors = "));
      Serial.print (ranging_errors);

      Serial.print (F(", Average RAW Result = "));
      Serial.print (range_result_average);

      Serial.print (F(", Average Distance = "));
      Serial.print (distance_average, 1);
      Serial.print (F(" m"));
      Serial.println();

      delay (2000);
    }

    Serial.println();
  }
}

//===========================================================================================//

void flashLED (uint16_t flashes, uint16_t delaymS) {
  uint16_t index;
  for (index = 1; index <= flashes; index++) {
    digitalWrite (PIN_LED1, HIGH);
    delay (delaymS);
    digitalWrite (PIN_LED1, LOW);
    delay (delaymS);
  }
}

//===========================================================================================//
main.cpp

Since we renamed the transmitter and receiver functions to Initiator and Responder, you have to use a new PIO configuration file.

; PlatformIO Project Configuration File
;
;   Build options: build flags, source filter
;   Upload options: custom upload port, speed and extra flags
;   Library options: dependencies, extra library storages
;   Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html

[env]
platform = espressif8266
board = esp8285
framework = arduino
lib_deps =
  https://github.com/StuartsProjects/SX12XX-LoRa.git
extra_scripts = extra_upload.py

[env:Initiator]
upload_protocol = esptool
upload_speed = 2000000
upload_port = COM26
build_flags = -D OPT_IS_INITIATOR=1

[env:Responder]
upload_protocol = esptool
upload_speed = 2000000
upload_port = COM13
build_flags = -D OPT_IS_INITIATOR=0
platformio.ini

The extra Python script also changes.

import os
from SCons.Script import DefaultEnvironment

env = DefaultEnvironment()

def after_build (source, target, env):
    print ("Uploading to Initiator...")
    env.Execute ("pio run -t upload -e Initiator")
    print ("Uploading to Responder...")
    env.Execute ("pio run -t upload -e Responder")

env.AddPostAction ("buildprog", after_build)
extra_upload.py

After uploading the code, the Initiator will start sending ranging requests. If the Responder is also active, it will start to respond to the requests as you can see from the serial monitor outputs below. The Initiator will send 5 requests before calculating the average value of the distance. If the values are not accurate, you will need to modify the calibration value. Stuart has a detailed explanation of the ranging calibration here.

SX1280 LoRa Test
initInitiator(): Starting ranging initiator..
initInitiator(): Device is found.

SX1280,PACKET_TYPE_RANGING,2444999936hz,SF8,BW812500,CR4:5
SX1280,PACKET_TYPE_RANGING,Preamble_12,Explicit,PayloadL_0,CRC_ON,IQ_NORMAL,LNAgain_HighSensitivity

Reg    0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
0x900  80 00 89 41 20 FA BC 13 C1 80 00 00 00 00 00 61 
0x910  9C 70 00 00 00 10 00 00 00 10 87 65 43 21 7F FF 
0x920  FF FF FF 0C 70 37 0E 50 C0 80 00 C0 2C 56 8F 0A 
0x930  00 C0 00 00 00 24 00 21 68 B0 30 0D 01 52 C6 0C 
0x940  58 0B 32 0A 10 24 6B 96 00 18 00 00 00 00 00 00 
0x950  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
0x960  00 00 00 00 00 00 00 00 00 00 FF FF FF FF FF FF 
0x970  FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 04 
0x980  00 0B 18 70 00 00 00 4C 00 F0 64 00 00 00 00 00 
0x990  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
0x9A0  00 08 EC B8 9D 8A E6 66 04 00 00 00 00 00 00 00 
0x9B0  00 08 EC B8 9D 8A E6 66 04 00 00 00 00 00 00 00 
0x9C0  00 16 00 3F E8 01 FF FF FF FF 5E 4D 25 10 55 55 
0x9D0  55 55 55 55 55 55 55 55 55 55 55 55 55 00 00 00 
0x9E0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
0x9F0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 


initInitiator(): Ranging configuration =
Address = 16
Calibration Value = 11350
initInitiator(): Ranging initiator RAW ready.

initiatorFunction(): Initiating ranging..
initiatorFunction(): Ranging result:
Status = Valid, Register = 78, Distance = 3.5, RSSIReg = 103, RSSI = -47 dBm

initiatorFunction(): Initiating ranging..
initiatorFunction(): Ranging result:
Status = Valid, Register = 85, Distance = 3.8, RSSIReg = 104, RSSI = -46 dBm

initiatorFunction(): Initiating ranging..
initiatorFunction(): Ranging result:
Status = Valid, Register = 74, Distance = 3.3, RSSIReg = 103, RSSI = -47 dBm

initiatorFunction(): Initiating ranging..
initiatorFunction(): Ranging result:
Status = Valid, Register = 83, Distance = 3.7, RSSIReg = 104, RSSI = -46 dBm

initiatorFunction(): Initiating ranging..
initiatorFunction(): Ranging result:
Status = Valid, Register = 84, Distance = 3.8, RSSIReg = 104, RSSI = -46 dBm
initiatorFunction(): Ranging average results:
Total Valid = 5, Total Errors = 0, Average RAW Result = 80, Average Distance = 3.6 m

initiatorFunction(): Initiating ranging..
initiatorFunction(): Ranging result:
Status = Valid, Register = 83, Distance = 3.7, RSSIReg = 104, RSSI = -46 dBm

initiatorFunction(): Initiating ranging..
initiatorFunction(): Ranging result:
Status = Valid, Register = 97, Distance = 4.4, RSSIReg = 103, RSSI = -47 dBm

initiatorFunction(): Initiating ranging..
initiatorFunction(): Ranging result:
Status = Valid, Register = 69, Distance = 3.1, RSSIReg = 104, RSSI = -46 dBm

initiatorFunction(): Initiating ranging..
initiatorFunction(): Ranging result:
Status = Valid, Register = 62, Distance = 2.8, RSSIReg = 104, RSSI = -46 dBm

initiatorFunction(): Initiating ranging..
initiatorFunction(): Ranging result:
Status = Valid, Register = 95, Distance = 4.3, RSSIReg = 104, RSSI = -46 dBm
initiatorFunction(): Ranging average results:
Total Valid = 10, Total Errors = 0, Average RAW Result = 81, Average Distance = 3.7 m
Initiator Serial Monitor
SX1280 LoRa Test
initResponder(): Starting ranging responder..
initResponder(): Device is found.

initResponder(): Ranging configuration =
Address = 16
Calibration Value = 11300
initResponder(): Ranging responder is ready.

responderFunction(): Response sent = 1

responderFunction(): Response sent = 2

responderFunction(): Response sent = 3

responderFunction(): Response sent = 4

responderFunction(): Response sent = 5

responderFunction(): Response sent = 6

responderFunction(): Response sent = 7

responderFunction(): Response sent = 8

responderFunction(): Response sent = 9

responderFunction(): Response sent = 10

responderFunction(): Response sent = 11

responderFunction(): Response sent = 12

responderFunction(): Response sent = 13

responderFunction(): Response sent = 14

responderFunction(): Response sent = 15

responderFunction(): Response sent = 16

responderFunction(): Response sent = 17

responderFunction(): Response sent = 18

responderFunction(): Response sent = 19

responderFunction(): Response sent = 20
Responder Serial Monitor

With this post, we wanted to introduce you to the new features and capabilities of the SX1280 LoRa chip with the help of the Zerodrag Nexus1 receiver. We hope this tutorial has been resourceful to you. If you run into any issues, or have a question or feedback, please post them in the comments. Happy coding 😉

Links

  1. Zerodrag Nexus1 – Official Product Page
  2. Zerodrag Nexus1 ExpressLRS Receiver – Buy from DRK Store
  3. ExpressLRS – Official Website
  4. ExpressLRS – GitHub

Share to your friends
Vishnu Mohanan

Vishnu Mohanan

Founder and CEO at CIRCUITSTATE Electronics

Articles: 93

Leave a Reply

Your email address will not be published. Required fields are marked *

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

The reCAPTCHA verification period has expired. Please reload the page.