This tutorial covers how to get started with LoRa LPWAN using TTGO LoRa32 SX1276. TTGO Lora32 is an ESP32 device with a LoRa module and an SSD1306 LCD display.
Using a pair of these devices, we will explore how to send and receive packets using a LoRa network. This is a point-to-point communication where one TTGO Lora32 acts as a sender and the other one is the receiver. Using the LCD display we will visualize the packets transmitted. In this project, we will use PlatformIO to build this project, even if you can choose the IDE you like.
What is LoRa LPWAN?
Before using TTGO LoRa32 with SX1276 chipset, it is useful to describe briefly what is LoRa. LoRa stands for Long Range and is a low-power wide-area network (LPWAN). It uses a wide range of radio frequencies that are:
- 868MHz in Europe
- 915MHz in Australia and North America
- 923MHz in Asia
These are free frequencies. LoRa is very efficient network that can transmit data packets in a long-range (more than 10Km in rural areas). If you want to have more information you can refer to Semtech website.
TTGO LoRa32 SX1276 pins
TTGO LoRa32 is an ESP32 device with an SX1276 chipset that handles the LoRa network and an SSD1306 LCD display. It is useful to list the pins we will use. You can buy these devices at DigitSpace.com.
SSD1306 Pins
The SSD1306 is connected to the ESP32 using I2C protocol. The pins used are:
Name | ESP32 Pins |
SDA | 4 |
SCL | 15 |
RST | 16 |
SX1276 Pins – LoRa module
The LoRa module SX1276 is connected to the ESP32 using the SPI protocol. The pins used are:
Name | ESP32 Pins |
MISO | 19 |
CS | 17 |
MOSI | 27 |
SCK | 5 |
RST | 14 |
IRQ | 26 |
Installing the LoRa library and the SSD1306 library using PlatformIO
Before jumping into the project, it is necessary to select the right libraries to use. This tutorial uses PlatformIO to develop this project. Anyway, you can build it with Arduino IDE. We assume you are familiar with PlatformIO so it is necessary to create a project and select the TTGO LoRa32 device as shown below:

Next, open the platform
io.ini file. Here we will configure the library used in this project. Add the following lines:
lib_deps =
sandeepmistry/LoRa @ ^0.7.2
adafruit/Adafruit SSD1306 @ ^2.4.0
adafruit/Adafruit GFX Library @ ^1.10.1
adafruit/Adafruit BusIO @ ^1.5.0
LoRa sender code
Now we can develop the LoRa sender. Below the code to use:
#include <Arduino.h>
// LoRa include
#include <SPI.h>
#include <LoRa.h>
// display include
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// Define OLED PIN
#define OLED_SDA 4
#define OLED_SCL 15
#define OLED_RST 16
// LoRa pins
#define LORA_MISO 19
#define LORA_CS 18
#define LORA_MOSI 27
#define LORA_SCK 5
#define LORA_RST 14
#define LORA_IRQ 26
// LoRa Band (change it if you are outside Europe according to your country)
#define LORA_BAND 866E6
// Simple counter
int counter = 0;
Adafruit_SSD1306 display(128, 32, &Wire, OLED_RST);
void resetDisplay() {
digitalWrite(OLED_RST, LOW);
delay(25);
digitalWrite(OLED_RST, HIGH);
}
void initializeDisplay() {
Serial.println("Initializing display...");
Wire.begin(OLED_SDA, OLED_SCL);
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3c)) {
Serial.println("Failed to initialize the dispaly");
for (;;);
}
Serial.println("Display initialized");
display.clearDisplay();
display.setTextColor(WHITE);
display.setTextSize(1);
display.setCursor(0,0);
display.println("Welcome to LORA");
display.setTextSize(1);
display.println("Lora sender");
display.display();
}
void initLoRa() {
Serial.println("Initializing LoRa....");
SPI.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
LoRa.setPins(LORA_CS, LORA_RST, LORA_IRQ);
// Start LoRa using the frequency
int result = LoRa.begin(LORA_BAND);
if (result != 1) {
display.setCursor(0,10);
display.println("Failed to start LoRa network!");
for (;;);
}
Serial.println("LoRa initialized");
display.setCursor(0,15);
display.println("LoRa network OK!");
display.display();
delay(2000);
}
void setup() {
Serial.begin(9600);
Serial.println("Setup LoRa Sender....");
resetDisplay();
initializeDisplay();
initLoRa();
}
void loop() {
// As example, we will send a simple packet
LoRa.beginPacket();
LoRa.print("Hi LoRa receiver. This is number:");
LoRa.print(counter);
LoRa.endPacket();
display.clearDisplay();
display.setCursor(0,2);
display.print("Packet: ");
display.print(counter);
display.display();
counter++;
delay(2000);
}
Code language: C++ (cpp)
You can find the sender source code at Github.
How the sender code works
The code is easy. At the beginning we define the libraries to include. First, the libraries needed to handle LoRa protocol:
// LoRa include
#include <SPI.h>
#include <LoRa.h>
Code language: C++ (cpp)
and next the libraries to handle the SSD1306 LCD:
// display include
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
Code language: PHP (php)
Initializing the SSD1306
Now, the ESP32 sketch initializes the LCD display. First, the sketch defines the PINS to use to interact with the SSD1306 using the PINS shown above:
// Define OLED PIN
#define OLED_SDA 4
#define OLED_SCL 15
#define OLED_RST 16
Code language: C++ (cpp)
Next, we initialize the LCD display, creating an Adafruit_SSD1306 object as shown below:
Adafruit_SSD1306 display(128, 32, &Wire, OLED_RST);
Code language: C++ (cpp)
Now, we define two functions: one to reset the display:
void resetDisplay() {
digitalWrite(OLED_RST, LOW);
delay(25);
digitalWrite(OLED_RST, HIGH);
}
Code language: JavaScript (javascript)
In the code above, the sketch send an LOW signal to the reset pin and then an HIGH value to reset it.
Finally, we can initialize the SSD1306 using the code below:
void initializeDisplay() {
Serial.println("Initializing display...");
Wire.begin(OLED_SDA, OLED_SCL);
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3c)) {
Serial.println("Failed to initialize the dispaly");
for (;;);
}
Serial.println("Display initialized");
display.clearDisplay();
display.setTextColor(WHITE);
display.setTextSize(1);
display.setCursor(0,0);
display.println("Welcome to LORA");
display.setTextSize(1);
display.println("Lora sender");
display.display();
}
Code language: C++ (cpp)
In the line 4, we start an I2C communication using the SDA PIN (data pin) and the CLK PIN (the clock).
In the line 5, the code initializes the LCD screen using 0x3C address. If everything works as expected, it is possible to interact with the OLED display using the Adafruit library functions.
Initializing the SX1276 Module
Next, we initialize the LoRa module SX1276. As said before, the module uses the SPI protocol, therefore we have to define the following PINS as described previously:
// LoRa pins
#define LORA_MISO 19
#define LORA_CS 18
#define LORA_MOSI 27
#define LORA_SCK 5
#define LORA_RST 14
#define LORA_IRQ 26
Code language: C++ (cpp)
It is necessary to define the LoRa band:
// LoRa Band (change it if you are outside Europe according to your country)
#define LORA_BAND 866E6
Code language: C++ (cpp)
If you are outside Europe, you have to change it using the following values:
//433E6 for Asia
//915E6 for North America
Code language: JSON / JSON with Comments (json)
Then, we can initialize the LoRa module SX1276 setting the SPI pins:
SPI.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
Code language: C++ (cpp)
and then the LoRa pins:
LoRa.setPins(LORA_CS, LORA_RST, LORA_IRQ);
Code language: CSS (css)
The last step is initializing the LoRa protocol setting the right frequency:
int result = LoRa.begin(LORA_BAND);
if (result != 1) {
display.setCursor(0,10);
display.println("Failed to start LoRa network!");
for (;;);
}
Code language: C++ (cpp)
Sending the LoRa packet
To make things simple, we can suppose to send a simple packet every 2 seconds. This packet is a simple string with a counter that is incremented everytime we send a new packet. In the loop() to send a packet, it is necessary to define a new packet using:
LoRa.beginPacket();
Code language: CSS (css)
Next, we fill the packet with the string we want to send and with the counter:
LoRa.print("Hi LoRa receiver. This is number:");
LoRa.print(counter);
Code language: C++ (cpp)
Finally, when we have finished our packet we call:
LoRa.endPacket();
Code language: C++ (cpp)
LoRa receiver source code
Now, we will develop the LoRa receiver that will receive the packets send from the LoRa sender. In this case, you have to care a new project using PlatfromIO. The receiver sketch is very simple and it is similar to the sender:
#include <Arduino.h>
// LoRa include
#include <SPI.h>
#include <LoRa.h>
// display include
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// Define OLED PIN
#define OLED_SDA 4
#define OLED_SCL 15
#define OLED_RST 16
// LoRa pins
#define LORA_MISO 19
#define LORA_CS 18
#define LORA_MOSI 27
#define LORA_SCK 5
#define LORA_RST 14
#define LORA_IRQ 26
// LoRa Band (change it if you are outside Europe according to your country)
#define LORA_BAND 866E6
// Simple counter
int counter = 0;
String data;
Adafruit_SSD1306 display(128, 32, &Wire, OLED_RST);
void resetDisplay() {
digitalWrite(OLED_RST, LOW);
delay(25);
digitalWrite(OLED_RST, HIGH);
}
void initializeDisplay() {
Serial.println("Initializing display...");
Wire.begin(OLED_SDA, OLED_SCL);
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3c)) {
Serial.println("Failed to initialize the dispaly");
for (;;);
}
Serial.println("Display initialized");
display.clearDisplay();
display.setTextColor(WHITE);
display.setTextSize(1);
display.setCursor(0,0);
display.println("Welcome to LORA");
display.setTextSize(1);
display.println("Lora receiver");
display.display();
}
void onReceive(int packetSize) {
Serial.println("Packet received");
if (packetSize) {
while (LoRa.available()) {
data = LoRa.readString();
Serial.println("Data:" + data);
}
}
display.setCursor(0,20);
display.println(data);
display.display();
}
void initLoRa() {
Serial.println("Initializing LoRa....");
SPI.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
LoRa.setPins(LORA_CS, LORA_RST, LORA_IRQ);
// Start LoRa using the frequency
int result = LoRa.begin(LORA_BAND);
if (result != 1) {
display.setCursor(0,10);
display.println("Failed to start LoRa network!");
for (;;);
}
// LoRa.onReceive(onReceive);
// LoRa.receive();
Serial.println("LoRa initialized");
display.setCursor(0,15);
display.println("LoRa network OK!");
display.display();
delay(2000);
}
void setup() {
Serial.begin(9600);
Serial.println("Setup LoRa Sender....");
resetDisplay();
initializeDisplay();
initLoRa();
}
void loop() {
//try to parse packet
int packetSize = LoRa.parsePacket();
if (packetSize) {
//received a packet
Serial.print("Received packet ");
//read packet
while (LoRa.available()) {
data = LoRa.readString();
Serial.print(data);
}
display.clearDisplay();
display.setCursor(0,2);
display.println(data);
display.display();
}
}
Code language: C++ (cpp)
The code is very simple to the code used for the sender. Let us analyze how to receive packets in LoRa.
The receiver source code is available a Github.
How to receive packets in LoRa32 SX1276
There are two different methods to receive packets:
- Using a callback function
- Handling the incoming packets in the loop() method
Implementing a callback function to receive packets
If we want to use a callback function, the first step is implementing it:
void onReceive(int packetSize) {
Serial.println("Packet received");
if (packetSize) {
while (LoRa.available()) {
data = LoRa.readString();
Serial.println("Data:" + data);
}
}
}
Code language: C++ (cpp)
In this piece of code, the sketch simply checks if the packetSize is greater than zero. If this condition is satisfied, we can read the data packets, using:
LoRa.readString();
Code language: C++ (cpp)
Next, it is necessary to register this callback function:
LoRa.onReceive(onReceive);
Code language: C++ (cpp)
Finally, the sketch calls:
LoRa.receive();
Code language: CSS (css)
to start receiving packets.
Reading LoRa packets in the loop() method
The alternative method is reading LoRa packets in the loop() method. First, we have to check if a new packet is available:
int packetSize = LoRa.parsePacket();
Code language: C++ (cpp)
Next, if the a new packet is ready we can read it:
if (packetSize) {
//received a packet
Serial.print("Received packet ");
//read packet
while (LoRa.available()) {
data = LoRa.readString();
Serial.print(data);
}
...
}
Code language: C++ (cpp)
That’s all!
You may like other tutorials about ESP32:
- How to connect ESP32 and ESP8266 using ESP-Now protocol
- How to connect ESP32 to the smartphone using Node-RED
Testing the LoRa receiver and the LoRa sender
You can now upload the code to the LoRa receiver through the PlatformIO. Below some images of the LoRa receiver at work:

Below the LoRa receiver:

Finally, the sender and receiver while exchange packets:

Wrapping up
In this article, we have covered how to get started with LoRa network and you have learned how to use TTGO LoRa32 SX1276 to build a simple LoRa network where the sender and the receiver exchanges packets.
We have developed a LoRa sender and a LoRa receiver using PlatformIO, you can improve this project using LoRa to send and receive sensor readings.
I’m using the Heltec LoRa32 Wifi board. The code as listed will compile, but at runtime it will give you this error:
[E][esp32-hal-i2c.c:1426] i2cCheckLineState(): Bus Invalid State, TwoWire() Can’t init sda=0, scl=0
The fix – which took me a heck of a long time to figure out – is you need to set the pin mode for the reset line to OUTPUT in the resetDisplay function…i.e. add this line at the top right after void resetDisplay:
pinMode(OLED_RST, OUTPUT);
It took an embarrassingly long time to figure this out so I thought I’d save some suffering for others. 🙂
Thx for pointing out it
Hello,
I´m trying to connect an ESP32 LoRa Heltec to a Dragino Gateway, what can I do?
Hello,
Same article on my site.
I have a question: how do you secure the packets – or send it with a specific “key”, only the receiver to know what are you sending.
Or, just make a simple encoding on the sender and the unpack at the receiver?
Hi, you can encode the message in a way that only the receiver knows it. Is there a better way to secure LoRa packets?
Hello, I have a heltec esp32 v2, and my dht11 still continue giving me and “nan” as answer. Do you have any ideia whats is happening?
Check the connections and the voltage
This sketch worked well for me. I am using two ESP8266’s two 128×32 OLED’s and two SX1276 modules. I had to use different pins but it is working great. I had to remove the SPI pin assignments due to a compiler error. I assume that’s related to the TTGO boards used in the tutorial. Also appreciate explaining the receiver callback and loop methods.