Skip to content
Tutorial
16 min read
October 17, 2026

LoRa with ESP32 and Arduino Uno: SX1278 Wiring, Code and Range

Send data kilometres without WiFi using SX1278/SX1276 LoRa modules. Wiring for ESP32 and Uno (with 3.3 V level shifting), sender and receiver code, spreading factor and airtime maths, antennas and legal limits.

What You'll Build

A long-range wireless link between two boards using cheap LoRa modules. An Arduino Uno node reads a temperature sensor and sends a tiny 7-byte packet every 10 seconds. An ESP32 gateway receives it, decodes it, prints the temperature, and tells you the signal strength (RSSI), signal-to-noise ratio (SNR) and how many packets were lost.

You will learn:

  • What LoRa is, how it differs from WiFi and Bluetooth, and what spreading factor does
  • How to wire an SX1278/SX1276 module to an ESP32 and to a 5 V Arduino Uno without damaging it
  • How to send and receive text packets, then compact binary packets
  • How to calculate airtime, trade range against speed, and stay within duty-cycle limits
  • How to choose an antenna and what range to realistically expect
DifficultyIntermediate
Build timeAbout 45 minutes
BoardsArduino Uno (node) and ESP32 (gateway), or two of either
ModuleSX1278 (433 MHz) or SX1276 (868/915 MHz), such as Ra-02 or RFM95
The finished link: an Arduino Uno sensor node (left) and an ESP32 gateway (right), each with a LoRa module and antenna.

What Is LoRa?

LoRa (Long Range) is a radio modulation from Semtech that trades data rate for range and battery life. It works in the licence-free ISM bands (433, 868 and 915 MHz depending on region) and can reach several kilometres in open country with a modest antenna, while a node sipping a few milliamps can run for months on batteries.

It does this with chirp spread spectrum: instead of switching a carrier on and off, each symbol is a frequency sweep (a "chirp") across the whole channel bandwidth. Receivers can pull these sweeps out of noise that is stronger than the signal itself, which is where the remarkable sensitivity comes from.

LoRaWiFiBluetooth LE
Typical rangeKilometres20 to 50 m indoors10 to 50 m
Data rate0.3 to about 5 kbps (our settings)Tens of MbpsUp to 1 to 2 Mbps
PowerVery lowHighVery low
InfrastructureNone (point to point)RouterPhone or hub
Good forSensors, tracking, remote nodesStreaming, webWearables, short links

LoRa is not LoRaWAN. LoRa is the radio layer, which is what this article uses. LoRaWAN is a network protocol on top of it, with gateways, a server and encryption. We use raw LoRa for direct point-to-point links.

The Key Idea: Spreading Factor

The most important setting is the spreading factor (SF), from 7 to 12. A higher SF stretches each symbol out over a longer chirp.

  • Higher SF: longer range and better sensitivity, but each packet takes much longer on the air and the data rate drops sharply.
  • Lower SF: faster, shorter airtime, less range.

Each step up in SF roughly doubles the airtime. Bandwidth (125 kHz by default) and coding rate (4/5 by default) also play a part, but SF is the lever you will use most.

Airtime for a 20-byte payload at 125 kHz bandwidth and coding rate 4/5:

Spreading factorAirtimeEffective bit rateApprox. sensitivity
SF756.6 ms5469 bps-123 dBm
SF8102.9 ms3125 bps-126 dBm
SF9185.3 ms1758 bps-129 dBm
SF10370.7 ms977 bps-132 dBm
SF11741.4 ms537 bps-134.5 dBm
SF121318.9 ms293 bps-137 dBm

Sensitivity values are the typical figures from Semtech's documentation for a 125 kHz channel. The ranking matters more than the exact number: every SF step buys roughly 2.5 to 3 dB of extra link budget at the cost of double the airtime.

Chirp spread spectrum: a low spreading factor sweeps the band quickly, a high one sweeps slowly. Longer chirps mean more energy per symbol and better noise immunity.
Airtime of a 20-byte packet from SF7 (56.6 ms) to SF12 (1319 ms): each step roughly doubles the time on air.

Duty Cycle and the Law

Because LoRa lives in shared licence-free bands, regulators limit how much you may transmit. In the EU 868 MHz band, many sub-bands are limited to a 1 percent duty cycle: you may be on air at most 1 percent of the time. That is where airtime maths becomes practical:

minimum interval = airtime / duty cycle

Spreading factorAirtime (20 B)Minimum interval at 1% duty
SF756.6 msAbout 5.7 s
SF9185.3 msAbout 18.5 s
SF121318.9 msAbout 132 s

Rules differ between countries and sub-bands, and some allow more power or other limits (for example frequency-hopping or listen-before-talk in other regions). Always check your local regulations for the frequency, maximum power and duty cycle that apply to you. Choose the module and frequency that are legal where you live: 433 MHz boards are common, but not every region permits every band.

Components

9 items
NameQtyLink
SX1278 (433 MHz) or SX1276 (868/915 MHz) LoRa module, e.g. Ra-02, RFM95×2—
Antenna for the same band (spring, whip or SMA)×2—
Arduino Uno R3×1—
ESP32 DevKit×1—
3.3 V regulator (AMS1117-3.3 or similar) or 3.3 V supply module×1—
Resistors 1k and 2k for the 5 V to 3.3 V dividers (or a level shifter)×8—
TMP36 temperature sensor (for the binary example)×1—
Breakout/adapter board for 2 mm pitch modules (Ra-02)×2—
Breadboard and jumper wires×1—

Module Pinout

PinFunction
3.3VPower: 3.3 V only
GNDGround
SCKSPI clock
MISOSPI data out of the module
MOSISPI data into the module
NSS (CS)SPI chip select
RSTReset
DIO0Interrupt: signals "packet received" or "packet sent"
ANTAntenna connection

The Ra-02 has a 2 mm pin pitch that does not fit a breadboard, so it needs a small adapter board. The RFM95 has the same 2 mm pitch. Ready-made modules with 2.54 mm pins exist and are easier for prototyping.

Wiring the ESP32

The ESP32 is a 3.3 V device, so it connects directly using the default VSPI pins.

LoRa moduleESP32
3.3V3V3
GNDGND
SCKGPIO 18
MISOGPIO 19
MOSIGPIO 23
NSSGPIO 5
RSTGPIO 27
DIO0GPIO 26

Wiring the Arduino Uno

The Uno's logic is 5 V, but the SX127x is 3.3 V only. Signals from the Uno into the module must be reduced to 3.3 V. Signals from the module into the Uno are fine.

LoRa moduleUnoLevel handling
3.3VSeparate 3.3 V regulatorSee warning below
GNDGNDCommon ground
SCKD13Divider (5 V to 3.3 V)
MISOD12Direct
MOSID11Divider (5 V to 3.3 V)
NSSD10Divider (5 V to 3.3 V)
RSTD9Divider (5 V to 3.3 V)
DIO0D2Direct

A simple divider is 1 k from the Uno pin to the module pin, with 2 k from the module pin to GND, giving about 3.3 V. A 4-channel bidirectional level shifter does the same job more neatly.

Arduino Uno wiring: 1k/2k dividers on SCK, MOSI, NSS and RST; MISO and DIO0 go straight back; the module has its own 3.3 V regulator.
ESP32 wiring: direct 3.3 V connections on the VSPI pins (SCK 18, MISO 19, MOSI 23) plus NSS 5, RST 27, DIO0 26.

🚨 Danger

**Never power the module from 5 V**, and never drive its inputs with 5 V logic. Either will destroy it. Also, the Uno's on-board 3.3 V pin is weak (about 50 mA), and a LoRa transmitter can draw well over 100 mA in bursts at higher power. Power the module from a dedicated 3.3 V regulator with a good capacitor close to the module, and join its ground to the Uno's. **Attach the antenna before you transmit.** Sending without one can damage the radio's output stage.

Steps

  1. 1Check your local rules and pick a legal frequency for your module (433, 868 or 915 MHz).
  2. 2Attach an antenna of the matching band to each module before powering anything.
  3. 3Wire the ESP32 and Uno as shown, powering each module from 3.3 V only.
  4. 4In Library Manager install **LoRa by Sandeep Mistry**.
  5. 5Upload the sender sketch to the Uno and the receiver sketch to the ESP32, then open the receiver Serial Monitor at 115200 baud.

Step 1: The Sender

This sketch works on both the Uno and the ESP32; the pins switch automatically. Three settings matter for a clean, private link:

  • LoRa.begin(FREQUENCY) must use the same frequency on both ends and one that is legal in your region.
  • setSyncWord(0xA5) is a private network identifier. Radios with a different sync word ignore your packets. Do not use 0x34, which is reserved for the public LoRaWAN networks.
  • enableCrc() adds a checksum so corrupted packets are dropped instead of delivered as garbage.

LoRa.endPacket() blocks until the packet has been sent, so the loop takes at least one airtime longer than the delay.

C++

Step 2: The Receiver

The receiver polls parsePacket(). When a packet arrives it prints the payload and two quality metrics:

  • RSSI (received signal strength) in dBm: more negative is weaker. Around -30 dBm is extremely strong, and near -120 dBm is the edge of what SF7 can hear.
  • SNR (signal-to-noise ratio) in dB: LoRa can decode with a negative SNR, down to about -7.5 dB at SF7 and about -20 dB at SF12. A positive SNR means a comfortable link.
C++
Receiver Serial Monitor: each packet with its payload, RSSI and SNR.

Step 3: Send Real Data in Compact Binary

Text like "hello 12" is easy to read but wasteful. Airtime depends directly on payload length, so real sensor nodes send packed binary structs. The example uses a 7-byte packet:

FieldTypeBytesMeaning
nodeIduint8_t1Which node sent it
sequint16_t2Packet counter
tempX10int16_t2Temperature in tenths of a degree (253 = 25.3 C)
battMvuint16_t2Supply voltage in millivolts

The sequence number is the clever part: the receiver compares it with the previous one, and any gap is a lost packet. That gives you a free measure of link quality.

Both ends must use the exact same struct layout, including __attribute__((packed)) so the compiler does not insert padding bytes. The Uno and the ESP32 agree on byte order (both are little-endian), so a struct sent from one can be read directly on the other.

Node (Arduino Uno, TMP36 on A0):

C++

Gateway (ESP32): it ignores any packet whose size is not exactly 7 bytes, decodes the rest, and counts losses using the sequence number. The subtraction is done in uint16_t, so the counter wraps around correctly at 65535.

C++

Tuning the Radio

The library's defaults (SF7, 125 kHz, coding rate 4/5) suit short links and fast updates. To go further, change these identically on both ends:

SettingCallEffect
Spreading factorLoRa.setSpreadingFactor(10)Range up, speed down (7 to 12)
BandwidthLoRa.setSignalBandwidth(62.5E3)Narrower is more sensitive but slower
Coding rateLoRa.setCodingRate4(8)More error correction, longer airtime
TX powerLoRa.setTxPower(17)2 to 20 dBm on PA_BOOST

A good workflow is to start at SF7 with the two boards on the same desk, confirm packets flow, then move apart and raise the SF only if you need more range. If you change a setting on one end and not the other, the link silently fails, because the receiver is listening for a different kind of chirp.

Antennas and Realistic Range

The antenna is the biggest range lever you have, and it costs a few cents. A quarter-wave wire is a good start:

FrequencyQuarter-wave length
433 MHz17.3 cm
865 MHz8.7 cm
868 MHz8.6 cm
915 MHz8.2 cm

Tips: mount the antenna vertically and as high as you can, away from metal and the board itself, and use an antenna cut for the band you actually run.

Range depends on the environment more than the module. Open line of sight can reach kilometres at a high SF, while buildings, trees and walls cut that dramatically, and inside a city a few hundred metres to a kilometre or two is more typical. Rather than trust a headline figure, measure it: walk away with the sender while watching RSSI and SNR on the receiver, and note where packets begin to drop. That is your real range for your setup.

Reading RSSI as Distance (Roughly)

RSSI falls with distance, and you can turn it into a rough estimate with the log-distance model: d = 10 ^ ((RSSI@1m - RSSI) / (10 x n)), where n is the path-loss exponent (about 2 in free space, 2.5 to 3 with obstacles). Using -59 dBm at 1 metre:

RSSIn = 2.0n = 2.5n = 3.0
-50 dBm0.4 m0.4 m0.5 m
-60 dBm1.1 m1.1 m1.1 m
-70 dBm3.5 m2.8 m2.3 m
-80 dBm11.2 m6.9 m5.0 m
-90 dBm35.5 m17.4 m10.8 m

The spread between columns is the point: indoors and around obstacles, RSSI is a very rough distance guide. Use it to compare positions, not to measure metres. The table is a simple model, and LoRa at long range depends on a calibration you must do yourself.

A field test you can run yourself: RSSI and SNR logged against distance, showing where packets begin to drop.

Troubleshooting

SymptomLikely causeFix
"LoRa init failed"Wiring, no 3.3 V power, wrong NSS/RSTRecheck SPI wiring, power from a stable 3.3 V, confirm pins in the sketch.
Init works but nothing is receivedFrequency, sync word or SF mismatchMake every radio setting identical on both ends.
Very short rangeNo antenna, wrong band, poor placementAttach the right antenna, raise it, move away from metal.
Works at 5 cm, fails at 5 mAntenna problem or low supplyCheck the antenna and the 3.3 V rail during transmit.
Uno resets when transmittingSupply sag from the 3.3 V pinUse a dedicated regulator with a capacitor.
Module gets hot or dies5 V applied, or transmitting with no antennaUse 3.3 V only; always connect the antenna.
Garbage charactersCRC off, or a nearby transmitter using another protocolEnable CRC and use a private sync word.
Lost packets reportedWeak link or collisionsCheck RSSI and SNR; raise SF or improve the antenna.
ESP32 SPI conflictAnother device on the same pinsUse a separate chip select, or move the module to the HSPI bus.

Ideas to Extend

  • Multiple nodes. Give each node an ID and let the gateway track them separately using the node ID and sequence number.
  • Acknowledgements. Add a reply from the gateway so nodes can retry if a packet is lost.
  • Bridge to the internet. Forward gateway data to your dashboard with the ESP32 MQTT tutorial.
  • Battery operation. Put the node to sleep between transmissions. See the ESP32 deep sleep guide for the techniques.
  • Compare with the alternatives. For a short-range link without a router, try ESP-NOW; for a wired field bus, see RS485 with MAX485.

FAQ

Do I need a licence to use LoRa? Not for the licence-free ISM bands, provided you stay within your region's limits for frequency, power and duty cycle. Check your local rules.

Which frequency should I buy? The one that is allowed where you live. 433 MHz modules are widely sold; 868 MHz and 915 MHz are common regional choices. The module, the antenna and the code must all match.

Is LoRa secure? Raw LoRa as used here is not encrypted, and the sync word is a filter, not security. Add your own encryption of the payload if the data is sensitive. LoRaWAN includes encryption as part of the protocol.

Can I send images or audio? No. Practical LoRa rates are a few kilobits per second at best. It is meant for small packets of sensor data.

How many bytes can a packet hold? The library allows up to 255 bytes, but shorter is better for airtime and reliability.

Why use a spreading factor of 12 if it is so slow? For the longest range from a very low-power node that sends rarely. At SF12 a 20-byte packet takes over a second, so use it only when you must.

Related on SolderHub

Published on SolderHub

Signal In

Built from what makers search for

The lookups piling up in SolderHub's search bar every day shape what we publish next — new component pages, board guides, and firmware notes, sent out as they go live.

By subscribing you agree to our Privacy Policy. Unsubscribe anytime.