LoRa P2P Communication

LoRa P2P Communication using ESP32 & Xiao nRF52840

In this blog post, we’ll provide a step-by-step guide on how to implement LoRa P2P communication using the REYAX RYLR993 LoRa module. We’ll be working with two different boards: an ESP32 running Arduino IDE and a XIAO nRF52840 using CircuitPython. This tutorial will add another tool in your IoT toolbox.

This tutorial will cover the following:

  • Integrating the REYAX RYLR993 LoRa module
  • Configuring LoRa settings
  • Establishing a P2P communication link
  • Sending and receiving data

By the end, you’ll have a solid understanding of LoRa P2P communication and be able to apply these concepts to your own projects.

Before we discuss the practical aspects of LoRa P2P communication, let’s clarify what P2P means.

What is LoRa P2P (Peer-to-Peer) Communication ?

Before we understand the P2P communication, let’s gain a foundational understanding of the LoRaWAN architecture.

LoRaWAN consists of 4 key components:

  • End Devices: These are the sensors or devices that collect data at the edge of the network.
  • Gateways: Gateways act as intermediaries, receiving data from multiple end devices and transmitting it to the network server.
  • Network Server: The network server handles the communication between gateways and end devices, as well as data aggregation and routing.
  • Application Server: The application server receives processed data from the network server and performs tasks such as data analysis, visualization, and storage.

This architecture enables efficient and scalable communication between end devices and the cloud.

LoRaWAN Architecture
LoRaWAN Architecture

P2P communication in LoRaWAN refers to a direct connection between two end devices, bypassing the intermediary role of the gateway. This allows for more efficient and localized data exchange, especially in scenarios where gateways may be unavailable or unreliable.

Imagine a network of sensors deployed in a remote location. If sensor 1 needs to send data to sensor 5, a P2P connection can be established directly between them, eliminating the need to rely on a gateway. This can be particularly beneficial in situations where network coverage is limited or where real-time communication is critical. Following figure illustrates the example.

LoRa P2P COmmunication
LoRa P2P Communication

LoRa P2P Setup

Hardware

  • LoRa Modules: We’ll be using the REYAX RYLR993 LoRa module, provided by REYAX Technologies.
  • Microcontrollers: The modules will be connected to an ESP32 and a XIAO nRF52840.
  • Development Environments: We’ll use Arduino IDE for the ESP32 and CircuitPython for the XIAO.

LoRa Module Overview

Reyax RYLR993 LoRaModule
Reyax RYLR993 LoRaModule
  • Frequency Bands: The RYLR993 supports multiple frequency bands, making it suitable for various regions.
  • Low Power Consumption: Its ultra-low power design is ideal for battery-powered applications.
  • Long Range: The module is capable of long-range communication, making it suitable for IoT and remote sensing applications.
  • Customization: The module can be configured using AT commands over UART, allowing for flexibility in customization.

P2P Communication Setup

  • ESP32 and RYLR993: We’ll connect the first RYLR993 module to the ESP32 and program it using Arduino IDE.
  • XIAO nRF52840 and RYLR993: The second RYLR993 module will be connected to the XIAO nRF52840, and we’ll use CircuitPython for programming.
  • P2P Connection: The ESP32 and XIAO will communicate with each other using the LoRa modules, establishing a P2P connection.
LoRa P2P communication setup
LoRa P2P communication setup

ESP32 and REYAX RYLR993 LoRa Module

Connection Diagram

ESP32 and REYAX RYLR993 LoRa Module
ESP32 and REYAX RYLR993 LoRa Module
  • 3.3V: Connect the LoRa module’s power supply pin to the 3.3V pin on the ESP32.
  • GND: Connect the LoRa module’s ground pin to the GND pin on the ESP32.
  • RX: Connect the LoRa module’s RX (receive) pin to the D5 pin on the ESP32.
  • TX: Connect the LoRa module’s TX (transmit) pin to the D4 pin on the ESP32.

Code Setup

Once the hardware is connected, we’ll proceed to write the Arduino code to control the LoRa module and establish communication.

// Include the necessary library
#include <HardwareSerial.h>

// Create an instance of HardwareSerial
HardwareSerial ReyaxLoRaSerial(1); 

void setup() {
  // Initialize the primary hardware serial port for monitoring
  Serial.begin(115200);
  Serial.println();
  delay(2000);

  // Initialize the secondary hardware serial port for communication with Reyax LoRa
  // Parameters are (TX pin, RX pin)
  ReyaxLoRaSerial.begin(9600, SERIAL_8N1, 4, 5); // Change 4 and 5 to the pins you want to use

  delay(1000);

  Serial.println();
  Serial.println("Serial monitor settings :");
  Serial.println("- End Char  : Newline");
  Serial.println("- Baud Rate : 115200");
  Serial.println();
}

void loop() { 
  // Read data from ReyaxLoRa and send it to Serial monitor
  if (ReyaxLoRaSerial.available()) {
    Serial.println(ReyaxLoRaSerial.readString());
  }
  
  // Read data from Serial monitor and send it to ReyaxLoRa
  if (Serial.available()) {
    ReyaxLoRaSerial.print(Serial.readString());
  }
}

Header Inclusion

  • Includes the HardwareSerial library for communication with the LoRa module using a secondary serial port.

HardwareSerial Instance

  • Creates an instance named ReyaxLoRaSerial for the secondary serial port.

Setup Function

  • Initializes the primary serial port for monitoring.
  • Initializes the secondary serial port for communication with the Reyax LoRa module.
  • Prints serial monitor settings for reference.

Loop Function:

  • Continuously checks for available data from the LoRa module and sends it to the serial monitor.
  • Checks for available data from the serial monitor and sends it to the LoRa module.

This code establishes a bidirectional communication between the ESP32 and the Reyax LoRa module, allowing for data transmission in both directions.

Testing using AT commands

What are AT commands ?
Text-based instructions used to control communication modules like LoRa.
Standardized protocol for configuring settings, sending/receiving data, and managing communication tasks.
  • AT: Checks if the interface is available. If the moudle returns OK, i.e. module interfacing is successful.
  • AT+OPMODE = N: Sets the operational mode (proprietary or LoRaWAN). N=1 sets to Properity mode, N=2 sets to LoRaWAN mode.
  • AT+OPMODE=?: Queries the current operational mode.
  • AT+BAND = N: Sets the frequency band (region-specific). N = frequency value.
  • AT+BAND= ?: Queries the current frequency band.
  • AT+ADDRESS = N: Sets the device address.
  • AT+ADDRESS=?: Queries the curre device address.
  • AT + SEND = ADDRSS, Message length, message : Sends data to another device (requires address, message length, and message).

We will follow the following steps.

  1. We will first send the AT command, and the device should return OK.
  2. Then we will set the OPMODE to properity mode. To do that send the AT+OPMODE=1 command.
  3. NExt we will set the address of device to 1. TO do that send the AT+ADDRESS=1 command.
  4. Finally to change the BAND of device send the AT+BAND=freuqncy band value (e.g. 923000000) and the device will ask you to reset the device.
  5. And FInally you can use the AT+SEND=2,30,Hello from Arduino IDE ESP32. The previous command sends the message to the device at address 2.

Note

The specific set of AT commands may vary depending on the LoRa module you’re using. Consult the module’s documentation for a complete list.

The video demonstrates setting the operational mode, frequency band, and address using AT commands.

XIAO NRF52840 and REYAX RYLR993 LoRa Module

Connection Diagram

  • VCC: Connect the VCC pin of the LoRa module to the VUSB or 3.3V pin on the Xiao nRF52840.
  • GND: Connect the GND pin of the LoRa module to the GND pin on the Xiao nRF52840.
  • RX: Connect the RX pin of the LoRa module to the TX pin (pin 6) on the Xiao nRF52840.
  • TX: Connect the TX pin of the LoRa module to the RX pin (pin 7) on the Xiao nRF52840.
XIAO NRF52840 and REYAX RYLR993 LoRa Module
XIAO NRF52840 and REYAX RYLR993 LoRa Module

Code Setup

Once the hardware is connected, we’ll proceed to write the circuit python code to control the LoRa module and establish communication.

import board
import busio
import time
import sys

# Define RX and TX pins for CircuitPython
rx_pin = board.RX  # RX pin
tx_pin = board.TX  # TX pin

# Initialize hardware serial
uart = busio.UART(tx=tx_pin, rx=rx_pin, baudrate=9600, timeout=2.0)  # Increased timeout to 2 seconds

# Initialize the serial monitor for debugging
print("Serial monitor settings:")
print("- End Char  : Newline")
print("- Baud Rate : 115200")
print()

# Function to handle non-blocking input
def non_blocking_input():
    import select
    # Check if data is available on stdin
    if select.select([sys.stdin], [], [], 0.0)[0]:
        return sys.stdin.readline().strip()  # Read input from stdin if available
    return None

while True:
    # Check if data is available from LoRa module
    if uart.in_waiting > 0:
        data = uart.read(256)  # Read up to 256 bytes
        if data:
            print("Received:", data.decode('utf-8').strip())  # Print the data from LoRa module

    # Check if data is available from serial monitor
    input_data = non_blocking_input()  # Check for non-blocking input
    if input_data:
        # Append carriage return and line feed
        uart.write((input_data + '\r\n').encode('utf-8'))  # Send data to LoRa module

    time.sleep(0.1)  # Small delay to prevent excessive CPU usage

  1. Import Libraries:
    • board: Provides access to the microcontroller’s pin definitions.
    • busio: Handles hardware communication protocols like UART (serial communication).
    • time: Used for creating delays.
    • sys: Used for handling input from the serial monitor.
  2. Define RX and TX Pins:
    • rx_pin: Sets the pin used for receiving data (connected to the LoRa module’s TX pin).
    • tx_pin: Sets the pin used for transmitting data (connected to the LoRa module’s RX pin).
  3. Initialize Hardware Serial:
    • uart = busio.UART(tx=tx_pin, rx=rx_pin, baudrate=9600, timeout=2.0):
      • Creates a UART object named uart for serial communication.
      • tx and rx specify the TX and RX pins used.
      • baudrate is set to 9600 to match the LoRa module’s settings.
      • timeout is increased to 2 seconds to ensure sufficient time for data reception.
  4. Serial Monitor Settings:
    • Prints information about the serial monitor settings for reference:
      • Newline character as the end character.
      • Baud rate set to 115200.
  5. Non-Blocking Input Function:
    • def non_blocking_input():: Defines a function to handle input without blocking the main loop.
      • Uses the select module to check if data is available on standard input (serial monitor).
      • If data is available, it reads the line from stdin, strips any leading/trailing whitespace, and returns it.
      • Otherwise, it returns None.
  6. Main Loop (while True):
    • This loop continuously checks for data and sends it between the LoRa module and the serial monitor.
    • if uart.in_waiting > 0:: Checks if any data is waiting to be read from the LoRa module (using in_waiting).
      • If there’s data (uart.read(256) reads up to 256 bytes).
        • If data is read, it’s decoded from bytes to a UTF-8 string, stripped of whitespace, and printed to the serial monitor using print.
    • input_data = non_blocking_input(): Calls the function to check for non-blocking input from the serial monitor.
      • If there’s input (input_data):
        • Appends carriage return (\r) and line feed (\n) characters to the input data.
        • Encodes the data with carriage return and line feed into UTF-8 format.
        • Writes the encoded data to the LoRa module using uart.write().
    • time.sleep(0.1): Introduces a small delay of 0.1 seconds between checks to avoid excessive CPU usage.

Overall, this code enables you to:

  • Receive data from the LoRa module and display it on the serial monitor.
  • Type commands or data into the serial monitor and send them to the LoRa module.

Testing using AT commands

We will follow the following steps.

  1. We will first send the AT command, and the device should return OK.
  2. Then we will set the OPMODE to properity mode. To do that send the AT+OPMODE=1 command.
  3. NExt we will set the address of device to 1. TO do that send the AT+ADDRESS=2 command.
  4. Finally to change the BAND of device send the AT+BAND=freuqncy band value (e.g. 923000000) and the device will ask you to reset the device.
  5. And FInally you can use the AT+SEND=1,31,Hello from CircuitPython XIAO NRF52840. The previous command sends the message to the device at address 1 i.e. ESP32.

Note

The specific set of AT commands may vary depending on the LoRa module you’re using. Consult the module’s documentation for a complete list.

The video demonstrates setting the operational mode, frequency band, and address using AT commands.

Sending XIAO board sensor data

If you are familiar with XIAO NRF5240 sense board, you will know that this board has internal sensors and we will send the data of those sensors to the ESP32.

import time
import board
import digitalio
import busio
from adafruit_lsm6ds.lsm6ds3trc import LSM6DS3TRC

# LoRa Module Setup
import sys
import select

# Define RX and TX pins for CircuitPython
rx_pin = board.RX  # RX pin
tx_pin = board.TX  # TX pin

# Initialize hardware serial
uart = busio.UART(tx=tx_pin, rx=rx_pin, baudrate=9600, timeout=2.0)  # Increased timeout to 2 seconds

# Initialize the serial monitor for debugging
print("Serial monitor settings:")
print("- End Char  : Newline")
print("- Baud Rate : 115200")
print()

# Function to handle non-blocking input
def non_blocking_input():
    if select.select([sys.stdin], [], [], 0.0)[0]:
        return sys.stdin.readline().strip()  # Read input from stdin if available
    return None

# IMU Setup
# On the Seeed XIAO Sense the LSM6DS3TR-C IMU is connected on a separate
# I2C bus and it has its own power pin that we need to enable.
imupwr = digitalio.DigitalInOut(board.IMU_PWR)
imupwr.direction = digitalio.Direction.OUTPUT
imupwr.value = True
time.sleep(0.1)

imu_i2c = busio.I2C(board.IMU_SCL, board.IMU_SDA)
sensor = LSM6DS3TRC(imu_i2c)

# Configure LoRa settings
def send_lora_message(address, message):
    # Prepare the message with carriage return and line feed
    uart.write(f"AT+SEND={address},{len(message)},{message}\r\n".encode('utf-8'))
    time.sleep(0.1)  # Ensure the message is sent

# Set up the IMU and LoRa module
def initialize_lora():
    # Set to proprietary mode
    #uart.write(b"AT+OPMODE=1\r\n")
    #time.sleep(1)
    # Set the frequency band
    #uart.write(b"AT+BAND=923000000\r\n")
    #time.sleep(1)
    # Set the address ID
    uart.write(b"AT+ADDRESS=2\r\n")  # Set address for this device
    time.sleep(1)

initialize_lora()

# Main loop
last_sent_time = time.time()

while True:
    current_time = time.time()

    # Check if it's time to send data
    if current_time - last_sent_time >= 20:  # Send every 60 seconds
        # Read IMU data
        gyro_x, gyro_y, gyro_z = sensor.gyro
        imu_data = f"Gyro X: {gyro_x:.2f}, Y: {gyro_y:.2f}, Z: {gyro_z:.2f}"
        
        # Send data to address 1
        send_lora_message(2, imu_data)
        
        # Update the last sent time
        last_sent_time = current_time

    # Check if data is available from LoRa module
    if uart.in_waiting > 0:
        data = uart.read(256)  # Read up to 256 bytes
        if data:
            print("Received:", data.decode('utf-8').strip())  # Print the data from LoRa module

    # Check if data is available from serial monitor
    input_data = non_blocking_input()  # Check for non-blocking input
    if input_data:
        # Append carriage return and line feed
        uart.write((input_data + '\r\n').encode('utf-8'))  # Send data to LoRa module

    time.sleep(0.1)  # Small delay to prevent excessive CPU usage


This code builds upon the previous version by incorporating an IMU (Inertial Measurement Unit) sensor and sending its data through the LoRa module. Here’s a breakdown in two parts:

1. IMU Setup and LoRa Configuration:

  • The code imports libraries for interacting with the LSM6DS3TRC IMU sensor on the Xiao board.
  • It sets up the I2C communication and initializes the sensor object.
  • A function send_lora_message is defined to format and send data through the LoRa module. It takes an address and message as arguments, prepares the data with carriage return and line feed, sends it using the uart object, and adds a small delay.
  • An initialize_lora function is called during startup. It’s currently commented out, but it could be used to set the LoRa module’s operational mode, frequency band, and address (uncommented line sets the address to 2).

2. Main Loop and Data Transmission:

  • The main loop continuously runs. It checks if a specific time interval (20 seconds) has passed since the last data transmission.
  • If it’s time to send data:
    • The code reads gyro data (X, Y, Z axes) from the IMU sensor.
    • It formats the data into a string with labels and readings.
    • The send_lora_message function is called to send this formatted IMU data to a specific address (set to 2 in this case).
    • The last sent time is updated.
  • The loop also checks for incoming data from the LoRa module and the serial monitor, handling them similarly to the previous version (printing received data and sending data with carriage return/line feed).

Code in Action

To see the code in action or the testing of the module, watch the following video.

LoRa P2P communication with REYAX RYLR993 using ESP32 and XIAO NRF5240

If you are interested to learn more about IoT using ESP32 and Arduino IDE. Check my blog Get Started with IoT development using Hands-on ESP32 with Arduino IDE book.

Code is also available on github: https://github.com/HighVoltages/LoRa-P2P-Communication-using-ESP32-Xiao-nRF52840

Leave a Comment

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.