Site icon High Voltages

LoRa P2P Communication using ESP32 & Xiao nRF52840

LoRa P2P Communication

LoRa P2P Communication

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:

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:

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 Module Overview

Reyax RYLR993 LoRaModule
Reyax RYLR993 LoRaModule

P2P Communication Setup

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

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

HardwareSerial Instance

Setup Function

Loop Function:

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.

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

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:

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:

2. Main Loop and Data Transmission:

Code in Action

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

LoRa Peer-to-Peer Communication with ESP32 - The Ultimate Guide
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

Exit mobile version