In this tutorial you will learn how to enable long range communication using the LoRa SX1276 module and the ESP32. For instance, if you want to receive data from far away sensors where you have no Wi-Fi coverage, e.g. a temperature sensor in the corner of your garden, LoRa can help.
LoRa is a wireless technology made for sending small amounts of data over long distances using very little power. It works well in open areas and can reach several kilometers, much farther than WiFi or Bluetooth. This makes it ideal for battery-powered devices like sensors in farms, cities, or remote areas where regular WiFi or Bluetooth can’t reach or consumes too much power.
If you are already familiar with LoRa and just want to know how to send environmental data such as temperature and humidity, have a look at the Send Environmental Data with LoRa tutorial.
Required Parts
Below you will find the components required for this tutorial. I used an older ESP32 board, which has been deprecated but you can still get it for a very low price. Note that there is also a version with a USB-C port. However, any other ESP32 will work just fine as well. Just remember you will need two of them and two SX1276 modules – one for sending and one for receiving.
As for the SX1276 LoRa transceiver modules watch out what version you buy! Depending on the country the permitted frequencies these modules are allowed to use differ (link). It is 868MHz for Europe, 915MHz for North America and 433MHz for Asia.
The module description either lists the frequency or has a number such as 868 or 915 in the name. I listed a module with the 868MHz, since I am in Europe. But you can get this module also for the 915MHz band.

2 x Lora 868/915M Module SX1276

2 x ESP32 lite

USB Data Cable

Dupont Wire Set

2x Breadboard
Makerguides is a participant in affiliate advertising programs designed to provide a means for sites to earn advertising fees by linking to Amazon, AliExpress, Elecrow, and other sites. As an Affiliate we may earn from qualifying purchases.
What is LoRa?
If you’re building Arduino or ESP32 projects and need to send data wirelessly over long distances, LoRa (short for Long Range) might be exactly what you need. Unlike WiFi and Bluetooth, which are designed for fast, short-range communication, LoRa is built for low-power, long-distance data transmission — perfect for remote sensors and devices that need to run for months or years on a small battery.
LoRa uses a special kind of radio modulation called Chirp Spread Spectrum (CSS). This makes it highly resistant to interference and allows devices to talk to each other over several kilometers, even in noisy environments or with obstacles like trees and buildings in the way. LoRa operates in unlicensed ISM frequency bands — typically 868 MHz in Europe, 915 MHz in North America, and 433 MHz in some Asian countries. These frequencies vary by region but all share the advantage of being free to use without a license.
The trade-off with LoRa is that it’s not made for high-speed data. It works best for small payloads, such as sensor readings or device status updates. Data rates typically range from 0.3 kbps to 50 kbps, depending on the spreading factor (SF), bandwidth, and coding rate you choose. The range also depends on these settings and your environment: you can expect 2–5 km in urban areas and 10–15 km in rural or line-of-sight conditions — sometimes even more with proper antennas.
LoRa vs WiFi and Bluetooth
WiFi offers fast speeds but drains power quickly and usually covers less than 100 meters. Bluetooth has an even shorter range and more suited for wearables and personal devices. The following table compares the main features of LoRa with WiFi and Bluetooth:
| Feature | LoRa | WiFi | Bluetooth |
|---|---|---|---|
| Power Consumption | Very low (μA sleep, ~10–50 mA active) | High (~70–300 mA active) | Low (~10–30 mA active) |
| Range | 2–15 km (rural), 0.5–5 km (urban) | ~50–100 meters (indoor), ~200 meters (open) | ~10–100 meters |
| Typical Data Rate | 0.3–50 kbps | Up to 100–600 Mbps (ESP32: ~150 Mbps) | ~1–3 Mbps (Classic), ~125–200 kbps (BLE) |
| Latency | High (100 ms to several seconds) | Low (~1–10 ms) | Low (~3–50 ms BLE) |
In this post, we’ll dive into how to use LoRa with ESP32 boards and modules like the SX1276, and show you how to start sending messages between devices miles apart — without needing WiFi, cell towers, or much power.
SX1276 LoRa Module
The SX1276 is a popular LoRa transceiver module made by Semtech, and it’s one of the most commonly used chips for long-range, low-power wireless communication in DIY and IoT projects. It supports LoRa modulation as well as traditional FSK/OOK modes, making it flexible for a wide range of applications.
The SX1276 is at the heart of many LoRa modules, including the well-known HopeRF RFM95W, which is very similar to the module I am using in this tutorial. The following picture shows the front of the SX1276 LoRa Module with the SX1276 chip, the Quarz Crystal and the Voltage regulator.

This chip operates in the 137 MHz to 1020 MHz range, but most modules using it are configured for either 433 MHz (Asia), 868 MHz (Europe), or 915 MHz (North America), depending on your region. On the back of module you usually find a small table where the operating frequency of the specific module is marked. The picture below shows a module that operates in the 915 MHz band and therefore could be used in North America:

Make sure you buy and use a module with the correct transmission frequency for your country. Otherwise you may get into trouble with the organization that regulates radio transmissions in your country!
The module can communicate over distances of 2 to 15+ kilometers, depending on antenna quality, terrain, and settings. The SX1276 supports data rates from 0.018 kbps to 37.5 kbps in LoRa mode and up to 300 kbps in FSK mode, and offers fine-grained control over parameters like bandwidth, spreading factor, and coding rate, allowing you to optimize for range, power consumption, or reliability.
Technical Details of SX1276
The following list shows the main features of the SX1276 LoRa module:
- Operating voltage: 1.8 – 3.7V
- Frequency Band: 868MHZ/915MH
- Output power: 20dBm/100mW
- Receiving sensitivity: -139dBm@146B
- Modulation: LORA/GFSK
- Data interface: 4-wire SPI
- Module Size: 16*16mm Interface Spacing 2.0mm
- Transmit current: 120mA@100mw/ 3.3V
- Receive Current: 10mA/3.3V
- Sleep current: 0.2uA
For more details see the datasheet linked below:
Pinout of SX1276 LoRa Module
The SX1276 LoRa Module has 16 pins. One for the Antenna (ANT), one for power (VCC), several ground pins (GND), the pins for SPI (REST, NSS, SCK, MOSI, MISO) and six configurable Digital IO pins (DIO):

Note that the SX1276 uses 3.3V logic and you therefore cannot connect it directly to an Arduino UNO, for instance, which operates a 5V logic levels. If you want to use an Arduino board instead of an ESP32 you either must use a logic level shifter or an Arduino that uses 3.3V logic.
Making the SX1276 LoRa Module breadboard-friendly
The pin holes of the SX1276 LoRa Module are 2 mm apart, which means if you solder pins they do not fit the 2.54 mm spacing of a standard breadboard. However, since we do not need all the pins of the module you can bend the poles of a standard 2.54 mm pin header and make it work.
The picture below shows the GND, DIO3, DIO4, VCC and DIO0 connected to a 2.54 mm pin header. I bent the poles on the top of the header to fit the smaller 2 mm spacing of the pin holes of the module:

Note that DIO1 and DIO2 are not connected. You could also skip DIO3, DIO4, since they are not needed but it quicker to solder the complete pin header instead of individual pins.
On the other side of the module we can do the same. There we connect RES, NSS, SCK, MOSI and MISO and leave the two GND pins and DIO5 unconnected:

With this simple trick you can make the SX1276 LoRa Module fit on a breadboard. The picture below shows the bottom of the module with both pin headers and the antenna soldered:

You just have to watch out when wiring up the board since the labels on the silkscreen and the pins are slightly offset.
Also note that your antenna might look differently (longer, just a wire) depending on the module and its frequency setting.
Connecting SX1276 to ESP32
The SX1276 is controlled via an SPI interface. When wiring it to an ESP32 you have to make the following connections:
| SX1276 | ESP32 |
|---|---|
| MOSI | 23 |
| MSIO | 19 |
| SCK | 18 |
| RST | 17 |
| NSS | 5 |
| DIO0 | 4 |
| GND | GND |
| VCC | 3.3V |
Make sure that you connect VCC to the 3.3V output pin of your ESP32. Also, if you use the breadboard-friendly version of the module remember that only the GND pin next to the antenna ANT pin is connected – the other two are not! Finally DIO0 should be connected to an interruptible pin of the ESP32. The picture below shows the complete wiring diagram:

If you are using a different ESP32 board, the pins for hardware SPI, which the above wiring is relying on, may differ. If you are not sure which pins of your board to use have a look at the Find I2C and SPI default pins tutorial.
The next photo shows how that looks like on my breadboard, when completely wired:

Finally, note that we are actually not going to use DIO0 in the following code. Which means you could leave this connection out. However, DIO0 can be used to signal that data has arrived via an interrupt handler, which might come in handy in your application.
Code for LoRa communication
In this section we write the code for the LoRa sender and receiver. The hardware and wiring for the sender and receiver is identical (a ESP32 with a SX1276 connected) but the code is different.
The sender sends an incrementing counter value every two seconds, while the receiver waits for the message from the sender and prints the counter value to the Serial Monitor when received. The sender also blinks the built-in LED when sending a message, while the receiver blinks its LED when receiving a message.
We start with the code for the sender. Have a quick look at the complete code and then we dive into its details.
Sender
#include <SPI.h>
#include <LoRa.h>
// WEMOS LOLIN32 Lite
// MOSI -> 23
// MISO -> 19
// SCK -> 18
#define SS 5
#define RST 17
#define DIO0 4
#define LED_BUILTIN 22
void setup() {
Serial.begin(115200);
pinMode(LED_BUILTIN, OUTPUT);
LoRa.setPins(SS, RST, DIO0);
// 433E6: Asia, 868E6: Europe, 915E6: North America
while (!LoRa.begin(868E6)) {
Serial.println(".");
delay(500);
}
LoRa.setSyncWord(0x34); // 0-0xFF sync word to match the receiver
LoRa.setSpreadingFactor(12); // (6-12) higher value increases range but decreases data rate
LoRa.setSignalBandwidth(125E3); // lower value increases range but decreases data rate
LoRa.setCodingRate4(8); // higher value increases range but decreases data rate
LoRa.enableCrc(); // improves data reliability
}
void loop() {
static int counter = 0;
Serial.print("Sending counter: ");
Serial.println(++counter);
digitalWrite(LED_BUILTIN, HIGH);
LoRa.beginPacket();
LoRa.print("Counter=");
LoRa.print(counter);
LoRa.endPacket();
delay(1000);
digitalWrite(LED_BUILTIN, LOW);
delay(1000);
}
First, we include the two libraries we need:
#include <SPI.h> #include <LoRa.h>
The SPI.h library allows the ESP32 to communicate with the LoRa transceiver over SPI (Serial Peripheral Interface). While, the LoRa.h library gives us a simple interface to send and receive LoRa packets. You can install the LoRa library by Sandeep Mistry using the Library Manager in the Arduino IDE. The picture below shows a successful installation of the library:

Next, we define the GPIO pins connected to the module’s control lines for the SPI interface:
#define SS 5 #define RST 17 #define DIO0 4 #define LED_BUILTIN 22
SS, RST, and DIO0 are required by the LoRa module. We also define LED_BUILTIN to control the onboard LED, which gives us visual feedback during transmission. On the WEMOS LOLIN32 Lite, the onboard LED is connected to pin 22.
setup
Now let’s step into the setup() function. This part runs once when the board powers up or resets:
void setup() {
Serial.begin(115200);
pinMode(LED_BUILTIN, OUTPUT);
LoRa.setPins(SS, RST, DIO0);
We begin by initializing the Serial monitor for debugging. Then, we set the LED pin as an output. Next, we use LoRa.setPins() to tell the library which pins connect to the LoRa module.
Then we initialize the LoRa radio at 868 MHz, which is used in Europe. Remember that you have to use the specific frequency permitted for LoRa transmissions in your country!
while (!LoRa.begin(868E6)) {
Serial.println(".");
delay(500);
}
If the LoRa module fails to start, we keep retrying in a loop, printing a dot each time. This helps diagnose startup issues like wiring problems.
Once the module is ready, we configure it for reliable long-range communication:
LoRa.setSyncWord(0x34); LoRa.setSpreadingFactor(12); LoRa.setSignalBandwidth(125E3); LoRa.setCodingRate4(8); LoRa.enableCrc(); }
Here’s what each setting does:
setSyncWord(0x34)makes sure only devices with the same sync word can talk to each other. It’s like a simple network ID.setSpreadingFactor(12)increases the range and reliability by spreading the signal more. Values go from 6 to 12—higher means better range but slower data.setSignalBandwidth(125E3)sets the radio bandwidth to 125 kHz. Narrower bandwidth increases range and decreases data rate.setCodingRate4(8)boosts error correction, helping data arrive intact even over noisy links.enableCrc()adds a checksum to each packet to detect transmission errors.
loop
Now let’s move to the loop() function, which runs continuously:
void loop() {
static int counter = 0;
Serial.print("Sending counter: ");
Serial.println(++counter);
We use a static int to keep track of how many packets we have sent. Next we print the value to the Serial monitor each time for debugging.
Before sending a packet, we turn on the LED so we know it’s transmitting:
digitalWrite(LED_BUILTIN, HIGH);
Then we build the LoRa packet (=message):
LoRa.beginPacket();
LoRa.print("Counter=");
LoRa.print(counter);
LoRa.endPacket();
This creates a packet with the contents "Counter=1", "Counter=2", and so on. The receiver will receive this string and can decide what to do with it.
After sending, we turn off the LED and wait a second before the next transmission:
delay(1000); digitalWrite(LED_BUILTIN, LOW); delay(1000); }
Each cycle takes about two seconds—one for the transmission and one for the pause. You can adjust the delay to match your needs.
And that’s it! This sketch sends LoRa packets every two seconds while blinking an LED to show activity. On the Serial Monitor should see a message printed every time a package is sent:

In the next section we write the corresponding code for the receiver.
Receiver
The Receiver waits for the transmission from the Sender and prints them on the Serial Monitor. Here is the complete code:
#include <SPI.h>
#include <LoRa.h>
// WEMOS LOLIN32 Lite
// MOSI -> 23
// MISO -> 19
// SCK -> 18
#define SS 5
#define RST 17
#define DIO0 4
#define LED_BUILTIN 22
void setup() {
Serial.begin(115200);
pinMode(LED_BUILTIN, OUTPUT);
LoRa.setPins(SS, RST, DIO0);
// 433E6: Asia, 868E6: Europe, 915E6: North America
while (!LoRa.begin(868E6)) {
Serial.println(".");
delay(500);
}
LoRa.setSyncWord(0x34); // 0-0xFF sync word to match the receiver
LoRa.setSpreadingFactor(12); // (6-12) higher value increases range but decreases data rate
LoRa.setSignalBandwidth(125E3); // lower value increases range but decreases data rate
LoRa.setCodingRate4(8); // higher value increases range but decreases data rate
LoRa.enableCrc(); // improves data reliability
}
void loop() {
if (LoRa.parsePacket()) {
digitalWrite(LED_BUILTIN, LOW);
while (LoRa.available()) {
Serial.print((char)LoRa.read());
}
Serial.printf(" (%d)\n", LoRa.packetRssi());
delay(100);
digitalWrite(LED_BUILTIN, HIGH);
}
}
As before we start by including the same two essential libraries:
#include <SPI.h> #include <LoRa.h>
This is identical to the sender. SPI.h enables communication over the SPI bus, and LoRa.h gives us all the functions we need to receive packets.
Next, we define the same pin mappings for the WEMOS LOLIN32 Lite:
#define SS 5 #define RST 17 #define DIO0 4 #define LED_BUILTIN 22
This sets up communication with the LoRa module, and again, we assign pin 22 for the built-in LED. Hardware and most of the code are identical for Sender and Receiver but you could use different hardware setups, e.g. different microcontrollers or pins.
setup
Now we move into the setup() function, which looks almost identical to the sender:
void setup() {
Serial.begin(115200);
pinMode(LED_BUILTIN, OUTPUT);
LoRa.setPins(SS, RST, DIO0);
We open the serial connection at 115200 baud so we can print incoming messages. Then we set the LED pin as output and assign the LoRa control pins with LoRa.setPins().
Just like on the sender, we initialize LoRa at the 868 MHz frequency, used in Europe:
while (!LoRa.begin(868E6)) {
Serial.println(".");
delay(500);
}
Obviously, both Sender and Receiver must operate on the same frequency. If the module doesn’t initialize successfully, we retry every 500 milliseconds, printing dots to the Serial Monitor for feedback.
Then comes the exact same LoRa configuration used in the sender:
LoRa.setSyncWord(0x34); LoRa.setSpreadingFactor(12); LoRa.setSignalBandwidth(125E3); LoRa.setCodingRate4(8); LoRa.enableCrc(); }
By mirroring the configuration from the sender, we ensure that both devices can understand each other. If even one parameter mismatches – like sync word or bandwidth – the receiver won’t be able to decode the packet.
loop
Now let’s look at the heart of the receiver, the loop() function:
void loop() {
if (LoRa.parsePacket()) {
Here, LoRa.parsePacket() checks whether a packet has arrived. If nothing is received, the function returns 0, and the rest of the loop is skipped.
When a packet is received, we immediately turn the LED on to signal the event.
digitalWrite(LED_BUILTIN, LOW);
Note that the logic for the built-in LED is reversed. LOW means the LED is on and HIGH means the LED is off.
Next, we read the contents of the packet byte by byte and print it as characters to the Serial Monitor:
while (LoRa.available()) {
Serial.print((char)LoRa.read());
}
This is where the "Counter=1", "Counter=2" messages sent by the transmitter appear. LoRa.available() checks if more bytes are available, and LoRa.read() gets each byte from the packet. We cast it to char so it prints as readable text.
Then we print the RSSI (Received Signal Strength Indicator), which tells us how strong the signal was:
Serial.printf(" (%d)\n", LoRa.packetRssi());
This is useful for debugging your radio range. The more negative the number, the weaker the signal. For example, -45 dB is strong, while -120 dB is nearly unreadable.
Finally, we delay briefly to be able to see the LED is on and then turn it off, indicating that the code now waits for the next packet.
delay(100);
digitalWrite(LED_BUILTIN, HIGH);
}
}
With this code, your ESP32 LoRa receiver listens for messages like "Counter=3" sent from your transmitter. You get visual confirmation through the LED and live updates in the Serial Monitor with signal strength readings in brackets:

And that’s it. Now you can send data from sensors that are kilometers away and due to the low power consumption of LoRa can run such sensors for a long time on battery power.
Conclusions
In this tutorial you learned how to achieve long range communication using the LoRa SX1276 module and the ESP32.
If you want to use an Arduino instead of an ESP32 you either need to use a logic level converter or an Arduino that operates on 3.3 Volts, since the SX1276 module does not work with 5V! Have a look at the Interface Arduino Uno with ST7735 TFT using Level Shifter tutorial if you want to learn how to use a logic level converter.
A typical LoRa application is sending temperature, humidity and other environmental data over long distances. See our Send Environmental Data with LoRa tutorial, if that is what you want to do.
For fast and simple short range communication Bluetooth is more suitable. You can find information on that in the ESP32 And Bluetooth Module- HC-05 and the How To Connect ESP32 Bluetooth With A Smartphone tutorials.
And if you want to transmit information over the internet the ESP32 send Telegram Message might be useful.
LoRa is great for transmitting between a few sensors. But if you have many sensors you need to manage and want to send measurements to the internet LoRaWAN is the way to go. See the LoRaWAN with Thinknode G1 Gateway tutorial for more information on this topic.
Finally, I recommend that you browse the information on https://www.thethingsnetwork.org to learn more about LoRa and the example code of the LoRa library. In this blog post we just scratched the surface.
If you have any questions feel free to leave them in the comment section.
Happy Tinkering ; )
Stefan is a professional software developer and researcher. He has worked in robotics, bioinformatics, image/audio processing and education at Siemens, IBM and Google. He specializes in AI and machine learning and has a keen interest in DIY projects involving Arduino and 3D printing.

