last updated: 2021-01-21
LoRa was developped by SEMTECH. We will use here their simple LoRa chip SX1276, that can be bought on a breakout board RFM95W-V2.0 from hoperf (specification document). Unfortunately the board uses a 2 mm pin header pitch instead of the usually used 2.54 mm (0.1 in). So we need an additional breakout board to be able to use the chip on a breadboard.


Connections:
Don't forget to connect the antenna before powering the board! Without antenna the board can be damaged!
If you don't have an antenna, use a wire with 8.2 cm in length.
The board uses the SPI interface (MISO, MOSI, SCK, SS (NSS)) to communicate with the microcontroller, and it is powered with 3.3 V (GND, +3.3 V).
For SPI we usually use the pins of the default SPI interface for MISO, MOSI and SCK. With the function SPI.begin() it is possible to change if a second interface is available. The chip select pin is freely selectable. The library uses by default pin 5 for the Uno. The DI00 pin (RFM95W) must be connected to a pin supporting interrupts, because the LoRa chip signals an incoming message with this output to the microcontroller (default Uno pin 2). Same for DI01, needed only for LoRaWAN (pin 6 for UNO). An output pin of the microcontroller can also be used to reset (RESET pin) the LoRa chip if needed.
Even if we find schematics connecting the Arduino Uno directly to the RFM95W board, this is not a valabel solution! The output pins (MOSI, CLK etc.) of the Uno have 5 V! One solution can be to use level shifters like on the Dragino shield, or to use voltage dividers for all outputs. A simpler solution is an Arduino Uno with 3V by changing the voltage regulator. We will use ESP boards for our exercises.
| LoRa board | Arduino Uno (3V!) | ESP8266 (Lolin/Wemos) | ESP32 |
|---|---|---|---|
| 3.3V (Vin) | 3.3V | 3.3V | 3.3V |
| GND | GND | GND | GND |
| MOSI | 11 | 14 | 23 |
| MISO | 12 | 12 | 19 |
| SCK | 13 | 13 | 18 |
| NSS (SS, CS) | 5 | 16 | 5 |
| RST (Reset) | 9 | not connected | 33 |
| DIO0 (G0, IRQ) | 2 | 15 | 26 |
| DIO1 (G1, IRQ) | 6 (only needed for LoRaWAN!) | (only needed for LoRaWAN!) | (only needed for LoRaWAN!) |
| DIO2-5 | not connected | not connected | not connected |
| ANT | antenna | antenna | antenna |

We use the LoRa library from Sandeep Mistry. Information about the library can be found in the corresponding LoRa Application Programming Interface (API).
Here are two basic sketches for the sender (transmitter):
// LoRa simple sender lora_sender_esp32_868.ino
// ESP32 MH ET Live MiniKit with RFM95W (SX1276)
// CS(SS)=5, RST=33, DIO0(IRQ)=26, freq = 868MHz
// SPI (ESP32 default): MOSI=23, MISO=19, SCK=18
#include <SPI.h>
#include <LoRa.h>
const byte PIN_SS = 5; // LoRa radio chip select
const byte PIN_RST = 33; // LoRa radio reset
const byte PIN_IRQ = 26; // hardware interrupt pin!
//const byte PIN_SCK = 18;
//const byte PIN_MISO = 19;
//const byte PIN_MOSI = 23;
int counter = 0;
void setup() {
Serial.begin(115200);
Serial.println("LoRa sender\n");
//SPI.begin(PIN_SCK, PIN_MISO, PIN_MOSI, PIN_SS); //SPI LoRa pins
LoRa.setPins(PIN_SS, PIN_RST, PIN_IRQ); // setup LoRa transceiver module
if (!LoRa.begin(868E6)) {
Serial.println("Error starting LoRa!");
while (true); //endless loop
}
}
void loop() {
Serial.print("Sending packet number: ");
Serial.println(counter);
LoRa.beginPacket();
LoRa.print("This is packet number ");
LoRa.print(counter);
LoRa.endPacket();
counter++;
delay(60000);
}
And the receiver:
// Lora simple receiver lora_receiver_esp32_868.ino
// ESP32 MH ET Live MiniKit with RFM95W (SX1276)
// CS(SS)=5, RST=33, DIO0(IRQ)=26, freq = 868MHz
// SPI (ESP32 default): MOSI=23, MISO=19, SCK=18
#include <SPI.h>
#include <LoRa.h>
const byte PIN_SS = 5; // LoRa radio chip select
const byte PIN_RST = 33; // LoRa radio reset
const byte PIN_IRQ = 26; // hardware interrupt pin!
//const byte PIN_SCK = 18;
//const byte PIN_MISO = 19;
//const byte PIN_MOSI = 23;
byte packet_size = 0;
void setup() {
Serial.begin(115200);
Serial.println("LoRa receiver\n");
//SPI.begin(PIN_SCK, PIN_MISO, PIN_MOSI, PIN_SS); //SPI LoRa pins
LoRa.setPins(PIN_SS, PIN_RST, PIN_IRQ); // setup LoRa transceiver module
if (!LoRa.begin(868E6)) {
Serial.println("Error starting LoRa!");
while (true); // endless loop
}
}
void loop() {
packet_size = LoRa.parsePacket(); // try to parse packet
if (packet_size) { // received a packet
Serial.print("Received packet with ");
Serial.print(packet_size);
Serial.print(" byte: '");
while (LoRa.available()) { // read packet
Serial.print((char)LoRa.read());
}
Serial.print("' with RSSI = ");
Serial.print(LoRa.packetRssi());
Serial.print("dBm");
}
}
Note: If we use other pins than the library defaults to connect to our chip, we must declare this in the software:
const byte PIN_SS = 5; // LoRa radio chip select
const byte PIN_RST = 33; // LoRa radio reset
const byte PIN_IRQ = 26; // hardware interrupt pin!
void setup() {
...
LoRa.setPins(PIN_SS, PIN_RST, PIN_IRQ); // setup LoRa transceiver module
...
}
The following sketch shows how a transceiver can be build with duplex communication and callbacks. Transmitter and receiver use a unique address (only 1 byte = 256 addresses, 0xFF for broadcast messages). The message gets an identifier (ID), realised in the sketch with a simple message counter. The delay time between sends is not static (randomized) to avoid collisions.
// LoRa duplex communication with callback lora_sender_esp32_868_duplex.ino
// ESP32 MH ET Live MiniKit with RFM95W (SX1276)
// CS(SS)=5, RST=33, DIO0(IRQ)=26, freq = 868MHz
// SPI (ESP32 default): MOSI=23, MISO=19, SCK=18
#include <SPI.h>
#include <LoRa.h>
const byte PIN_SS = 5; // LoRa radio chip select
const byte PIN_RST = 33; // LoRa radio reset
const byte PIN_IRQ = 26; // hardware interrupt pin!
//const byte PIN_SCK = 18;
//const byte PIN_MISO = 19;
//const byte PIN_MOSI = 23;
const byte NODE_ADDR = 0x01; // address of this device
const byte GATEWAY_ADDR = 0xFE; // destination 0xFE = gateway 0xFF = broadcast
unsigned long send_delay = 6000; // delay in ms between sends
byte msg_out_id = 0; // counter of outgoing messages = message id
byte addr_in_rec, addr_in_sender, msg_in_id, msg_in_length;
String message, msg_out, msg_in, lora_rssi, lora_snr;
volatile bool flag_message_received = false; // flag set by callback
void setup() {
Serial.begin(115200);
Serial.println("LoRa duplex with callback\n");
//SPI.begin(PIN_SCK, PIN_MISO, PIN_MOSI, PIN_SS); //SPI LoRa pins
LoRa.setPins(PIN_SS, PIN_RST, PIN_IRQ); // setup LoRa transceiver module
if (!LoRa.begin(868E6)) {
Serial.println("Error starting LoRa!");
while (true); // endless loop
}
LoRa.onReceive(onReceive); // init the callback function
LoRa.receive(); // start receive mode
}
void loop() {
LoRa.receive(); // go back into receive mode
if (flag_message_received) { // if receive flag is set by callback
readMessage();
flag_message_received = false; // set flag back to false
}
if (non_blocking_delay(send_delay)) { // send a message all delay ms
message = "HeLoRa World!";
send_message(message);
Serial.println("Sending:");
Serial.print("GW addr.: ");
Serial.print(GATEWAY_ADDR);
Serial.print(" (1 byte) + node addr.: ");
Serial.print(NODE_ADDR);
Serial.print(" (1 byte) + msg ID: ");
Serial.print(msg_out_id);
Serial.println(" (1 byte) +");
Serial.print("msg length: ");
Serial.print(message.length());
Serial.println(" (1 byte) + message: \"" + message + "\"");
Serial.println("---------------------------------------------------------------------");
send_delay = send_delay + random(1000); // randomize delay time (avoid collisions)
}
delay(1);
}
// callback function
void onReceive(int packetSize) {
if (packetSize == 0) { // if there's no packet, return
return;
}
flag_message_received = true; //Set flag to perform read in main loop
}
// send the message and increment ID
void send_message(String message_out) {
LoRa.beginPacket(); // start packet
LoRa.write(GATEWAY_ADDR); // add destination address
LoRa.write(NODE_ADDR); // add sender address
LoRa.write(msg_out_id); // add message ID (counter)
LoRa.write(message_out.length()); // add payload length
LoRa.print(message_out); // add payload
LoRa.endPacket(); // finish packet and send it
msg_out_id++; // increment message counter (ID)
}
// read a message and check if valid
void readMessage() {
addr_in_rec = LoRa.read(); // recipient address
addr_in_sender = LoRa.read(); // sender address
msg_in_id = LoRa.read(); // incoming msg ID
msg_in_length = LoRa.read(); // incoming msg length
while (LoRa.available()) {
msg_in = LoRa.readString();
yield();
}
if (msg_in_length != msg_in.length()) {// check length for error
Serial.println("error: message length does not match length");
return;
}
if (addr_in_rec != GATEWAY_ADDR && addr_in_rec != 0xFF) {
Serial.println("This message is not for me.");
return;
}
lora_rssi = LoRa.packetRssi();
lora_snr = LoRa.packetSnr();
Serial.print("From: 0x" + String(addr_in_sender, HEX));
Serial.print("\tto: 0x" + String(addr_in_rec, HEX));
Serial.print("\tRSSI: " + lora_rssi);
Serial.println("\tSnr: " + lora_snr);
Serial.print("Message: " + msg_in);
Serial.print("\tLength: " + String(msg_in_length));
Serial.println("\tID: " + String(msg_in_id));
Serial.println("-----------------------------------------------------------");
}
// non blocking delay using millis(), returns true if time is up
bool non_blocking_delay(unsigned long milliseconds) {
static unsigned long nb_delay_prev_time = 0;
if(millis() >= nb_delay_prev_time + milliseconds) {
nb_delay_prev_time += milliseconds;
return true;
}
return false;
}