Long Range (LoRa)
LoRa is a low-power, long-range wireless communication technology, primarily used for Internet of Things (IoT) applications. It works well for IoT applications since it is designed to transmit small amounts of data over long distances
This project demonstrates how to send data from one board to another using a LoRa (Long Range) communication module. This project needs two boards (we will use one Arduino Nano and one ESP32), each with a LoRa module. One will act as the transmitter and the other will act as the receiver. Both can very well act as a transmistter and a receiver but we will keep the data transmission uni-directional for simplicity. The transmitter waits for user input from the serial monitor. Once input is received it transmits the string via LoRA to the receiver which prints it on its serial monitor.
Components
Both the transmitter and the receiver circuits will use the components listed below.
Component | Purpose |
---|---|
RA02-SX1278 | The LoRa module |
SX1278 antenna | The LoRa module antenna |
Circuit Diagrams
ESP32

Arduino Nano
Since the Nano has a 5V operating (signal) voltage while the SX1278 has 3.3V we need to add a bi-directional level shifter. A direct connection without a level shifter may damage the SX1278 module.

Connections
Connect the antenna to the SX 1278 Module to improve the range and performance.
RA-02-SX1278 Module
Both the transmitter and the receiver will have the connections listed below. Please see the connections for the microcontroller you are using`.
ESP32 | SX1278 RA-02 |
---|---|
3.3V | VCC |
GND | GND |
D23 | COPI (MOSI) |
D19 | CIPO (MISO) |
D18 | SCK |
D5 | NSS |
D25 | RST |
D26 (interrupt) | DIO0 |
Since the Nano has a 5V operating (signal) voltage while the SX1278 has 3.3V we need to add a bi-directional level shifter. A direct connection without a level shifter may damage the SX1278 module.
The table below only lists the board to module pin connections. See the circuit diagram for connections through a level shifter.
Arduino Nano | SX1278 RA-02 |
---|---|
3.3V | VCC |
GND | GND |
D11 | COPI (MOSI) |
D12 | CIPO (MISO) |
D18 | SCK |
D13 | NSS |
RST | RST |
D2 (interrupt) | DIO0 |
Code
Transmitter
/*
Project: LoRa Communication
Circuit: LoRa Transmitter
Project Description: This sketch is for the LoRa transmitter functionality using the SX1278 LoRa module.
Author: STEMVentor Educonsulting
This code is copyrighted. Please do not reuse or share for any purpose other than for learning with subscribed STEMVentor programs. This code is for educational purposes only and is not for production use.
*/
// LIBRARIES
#include <SPI.h> // Library for LoRa communications
//https://github.com/sandeepmistry/arduino-LoRa
#include <LoRa.h> // Library for LoRa communications
// PIN DEFINITIONS
// Note that the GPIO numbers should be used in code not the numbers printed on the board.
// ESP32
/*
SX1278 -> ESP32
3.3V -> 3.3V
GND -> GND
COPI (MOSI) -> D23
CIPO (MISO) -> D19
SCK -> D18
NSS -> D5
DIO0 -> D26 (should be interrupt enabled)
// If a software reset option is not required this need not be connected.
RST -> D25
*/
// Nano
// Since the Nano has a 5V op voltage while the SX1278 has 3.3V we need to add a bi-directional level shifter.
/*
SX1278 -> Nano
3.3V -> 3.3V
GND -> GND
COPI (MOSI) -> D11
CIPO (MISO) -> D12
SCK -> D18
NSS -> D13
DIO0 -> D2 (should be interrupt enabled)
// If a software reset option is not required this need not be connected.
RST -> RST
*/
// Need to define even if not connected.
#define NSS_PIN 15
#define RST_PIN 5
#define DIO0_PIN 4
// GLOBAL VARIABLES
int incoming_byte = 0; // for incoming serial data
String serial_in_string = ""; // for incoming serial data string
// To store the message to be sent to the receiver over LoRa.
String lora_send_data_msg = "Default message";
// LOCAL FUNCTIONS
// Read an entire string and then transmit it via LoRa
// (this is a blocking function and waits until timeout).
void readString()
{
Serial.println("Enter a data string to be transmitted via LoRa:");
while (Serial.available() == 0) {} //wait for data available
serial_in_string = Serial.readString(); //read until timeout
// serial_in_string.trim(); // remove any \r \n whitespace at the end of the String
}
void sendDataOverLora(String lora_send_data) //sends the data as a string over LoRa
{
Serial.println("Sending packet id " + lora_send_data.substring(0,1));
LoRa.beginPacket(); // start packet
LoRa.print(lora_send_data); // add payload
LoRa.endPacket(); // finish packet and send it
Serial.println("Sent packet id " + lora_send_data.substring(0,1));
}
// Read data from sensors and transmit to receiver over LoRa.
void sendTestData() {
Serial.print("Sending packet: ");
Serial.println(counter);
// Test data
// Send bursts of packets so that the microcontroller reads at least one. Is this required? It seems to receiving correctly if sent only once.
// for(int j = 0 ; j < 50 ; j++){
LoRa.beginPacket();
LoRa.print("hello ");
LoRa.println(counter);
LoRa.endPacket();
// delay(1000);
// }
counter++;
}
// Setup LoRa
void LoRaSetup() {
Serial.begin(9600);
// Wait until serial port is opened.
while (!Serial);
// Initialize LoRa
// void setPins(int ss = LORA_DEFAULT_SS_PIN, int reset = LORA_DEFAULT_RESET_PIN, int dio0 = LORA_DEFAULT_DIO0_PIN);
LoRa.setPins(NSS_PIN, RST_PIN, DIO0_PIN);
//Select the frequency accordng to your location
//433E6 for Asia
//866E6 for Europe
//915E6 for North America
if (!LoRa.begin(433E6)) {
Serial.println("LoRa initialization failed!");
// Try repeatedly until initialization is successful.
while(1);
delay(1000);
}
// OR
// while(!LoRa.begin(433E6)) {
// Serial.println("LoRa initialization failed!");
// delay(1000);
// }
Serial.println("LoRa receiver initialized!");
// The transmitter microcontroller should have the same syncword in order to make this connection private and secure.
LoRa.setSyncWord(0xA5);
}
// THE setup FUNCTION RUNS ONCE WHEN YOU PRESS RESET OR POWER THE BOARD.
void setup()
{
// Start the serial communication with baud rate suitable for your components.
Serial.begin(9600);
LoRaSetup();
Serial.println("LoRa module initialized!");
}
// THE loop FUNCTION RUNS OVER AND OVER AGAIN FOREVER UNTIL THE BOARD IS POWERED OFF.
void loop()
{
// sendTestData();
readString();
sendDataOverLora(receivedString);
}
Receiver
/*
Project: LoRa Communication
Circuit: LoRa Receiver
Description: This sketch is for the LoRa receiver functionality using the SX1278 LoRa module. This sketch has two options to receive data, one using a callback function (this needs the DIO0 pin to be connected to an interrupt pin on the board) and one in a loop (which does not need an interrupt pin).
Author: STEMVentor Educonsulting
This code is copyrighted. Please do not reuse or share for any purpose other than for learning with subscribed STEMVentor programs. This code is for educational purposes only and is not for production use.
*/
// LIBRARIES
#include <SPI.h> // Library for LoRa communications
//https://github.com/sandeepmistry/arduino-LoRa
#include <LoRa.h> // Library for LoRa communications
// PIN DEFINITIONS
// ESP32
/*
SX1278 -> ESP32
3.3V -> 3.3V
GND -> GND
COPI (MOSI) -> D23
CIPO (MISO) -> D19
SCK -> D18
NSS -> D5
DIO0 -> D26 (should be interrupt enabled)
// If a software reset option is not required this need not be connected.
RST -> D25
*/
// Nano
// Since the Nano has a 5V op voltage while the SX1278 has 3.3V we need to add a bi-directional level shifter.
/*
SX1278 -> Nano
3.3V -> 3.3V
GND -> GND
COPI (MOSI) -> D11
CIPO (MISO) -> D12
SCK -> D18
NSS -> D13
DIO0 -> D2 (should be interrupt enabled)
// If a software reset option is not required this need not be connected.
RST -> RST
*/
// ESP32
#define NSS_PIN 5
#define RST_PIN 25
#define DIO0_PIN 26
// GLOBAL VARIABLES
// To store the message sent by the transmitter over LoRa.
String incoming_lora_msg = "";
// LOCAL FUNCTIONS
/* When a packet is received over LoRa it triggers an interrupt on the microcontroller board pin (which should be interrupt enabled) conected to the DIO0 pin on the module. This executes the callback function specified. Keep a callback function small. Doing too much in an interrupt function causes the board to "panic" and hang. Simply return the received value in a global variable and take up processing elsewhere. Specifically, Serial.println is an interrupt call itself so it should not be used in an interrupt callback function.
*/
void onReceiveCallback(int packet_size) {
// Serial.println(packet_size);
// If there is no packet, return without any further processing.
if (packet_size == 0) return;
// Any of the Serial APIs/Functions can be used to read the LoRa data
// while (LoRa.available()) {
// incoming_lora_msg += (char)LoRa.read();
// }
// Serial.println("Message: " + incoming_lora_msg);
// OR
// for (int i = 0; i < packet_size; i++) {
// Serial.print((char)LoRa.read());
// }
// OR
Serial.print(LoRa.readString());
// print RSSI of packet
// Serial.print("' with RSSI ");
// Serial.println(LoRa.packetRssi());
}
// This is the alternative approach where you keep listening in a loop
// and act on the data if a packet is received.
// With this you can do a lot in the function itself
// but it is not the most efficient approach in general.
void LoRaListenInLoop(){
// try to parse packet
int packet_size = LoRa.parsePacket();
if (packet_size) {
// received a packet
Serial.print("Received packet '");
// read packet
while (LoRa.available()) {
// Serial.print((char)LoRa.read());
String lora_data = LoRa.readString();
Serial.print(lora_data);
}
// print RSSI of packet
Serial.print("' with RSSI ");
Serial.println(LoRa.packetRssi());
}
}
// Setup
void setup() {
Serial.begin(9600);
// Wait until serial port is opened.
while (!Serial);
// Serial.println("LoRa Receiver with Callback.");
// OR
Serial.println("LoRa Receiver in a Loop.");
// Initialize LoRa
// void setPins(int ss = LORA_DEFAULT_SS_PIN, int reset = LORA_DEFAULT_RESET_PIN, int dio0 = LORA_DEFAULT_DIO0_PIN);
LoRa.setPins(NSS_PIN, RST_PIN, DIO0_PIN);
//Select the frequency accordng to your location
//433E6 for Asia
//866E6 for Europe
//915E6 for North America
if (!LoRa.begin(433E6)) {
Serial.println("LoRa initialization failed!");
// Try repeatedly until initialization is successful.
while(1);
delay(1000);
}
// OR
// while(!LoRa.begin(433E6)) {
// Serial.println("LoRa initialization failed!");
// delay(1000);
// }
Serial.println("LoRa receiver initialized!");
// The transmitter microcontroller should have the same syncword in order to make this connection private and secure.
LoRa.setSyncWord(0xA5);
// IF USING A CALLBACK
// Register the receive callback
// LoRa.onReceive(onReceiveCallback);
// put the radio into receive mode
// LoRa.receive();
}
// Loop
void loop() {
// Do nothing if using callback
// OR
// Call the function to listen in a loop
LoRaListenInLoop();
}