The best default setup for a Raspberry Pi LoRa node is the Raspberry Pi 4 Model B paired with the Dragino LoRa/GPS HAT v1.4 (based on the Semtech SX1276 chip), communicating over the hardware SPI0 bus. This combination gives you a robust, stackable physical layer without the breadboard spaghetti that plagues raw SPI module wiring. Whether you are building a remote soil moisture aggregator or a single-channel LoRaWAN gateway prototype, the SX1276 remains the most documented and reliable sub-GHz transceiver for the Pi ecosystem.
This guide gives you the exact pin mapping, the decision framework to pick your hardware, and a complete Python script with the error handling required to survive real-world SPI bus contention.
The Decision Tree: Which Raspberry Pi LoRa Hardware to Pick?
Before buying parts, you need to match the silicon to your use case. The sub-GHz ISM bands (868 MHz in EU/UK, 915 MHz in US/AU) are crowded with different module form factors. Use this decision matrix to lock in your hardware.
| Criteria / Use Case | Hardware Option | Interface | Verdict |
|---|---|---|---|
| Need GPS + clean Pi stack for sensor node | Dragino LoRa/GPS HAT v1.4 (SX1276) | SPI0 | DEFAULT PICK for prototyping and custom nodes. |
| Need multi-channel LoRaWAN Gateway (TTN) | RAKwireless RAK2287 (SX1302) | USB / SPI | Pick this ONLY if building a production gateway. Overkill for simple node-to-node. |
| SPI bus is occupied (e.g., using an LCD) | Ebyte E22-900T22D (SX1262) | UART | Pick this if you need long-range point-to-point and must free up the SPI0 pins. |
| Need ultra-low power sleep modes | Seeed Studio LoRa-E5 (STM32WLE5) | UART (AT Commands) | Pick this for battery-powered remote nodes where the Pi acts only as a bridge. |
Parts List and Exact SPI Pin Mapping
The SX1276 is strictly a 3.3V logic device. Feeding 5V into the MISO/MOSI lines will permanently brick the silicon. The Dragino HAT handles this, but if you are verifying connections, use this exact mapping.
Bill of Materials
- Compute: Raspberry Pi 4 Model B (4GB) running Raspberry Pi OS (Bookworm or Bullseye, 64-bit).
- Radio: Dragino LoRa/GPS HAT v1.4 (SX1276, 915MHz for US/AU or 868MHz for EU).
- Antenna: 915MHz SMA dipole antenna (never transmit without an antenna attached; you will burn out the PA).
- Software: Python 3.9+,
adafruit-blinka, andadafruit-circuitpython-rfm9x.
SPI0 Pin Mapping (BCM Numbering)
| Pi Physical Pin | BCM GPIO | HAT Pin Label | Function |
|---|---|---|---|
| 19 | GPIO 10 | MOSI | Master Out Slave In |
| 21 | GPIO 9 | MISO | Master In Slave Out |
| 23 | GPIO 11 | SCK | Serial Clock |
| 24 | GPIO 8 | CS | Chip Select (CE0) |
| 22 | GPIO 25 | RST | Radio Reset |
| 29 | GPIO 5 | DIO0 | Interrupt / RX Ready |
| 1 | 3.3V | 3.3V | Power (Max 120mA draw during TX) |
| 6 | GND | GND | Common Ground |
Step-by-Step Wiring and Software Setup
Follow these steps to prep the Raspberry Pi OS for SPI communication. Do not skip the SPI enablement step, or the Python library will fail silently or throw a bus error.
- Enable the SPI Bus: Open the terminal and run
sudo raspi-config. Navigate to Interface Options > SPI and select Yes. Alternatively, manually adddtparam=spi=onto the bottom of/boot/firmware/config.txt(or/boot/config.txton older OS versions) and reboot. - Verify SPI Devices: After rebooting, run
ls -l /dev/spi*. You must see/dev/spidev0.0and/dev/spidev0.1. If you don't, your SPI overlay failed to load. - Create a Virtual Environment: Keep your system Python clean. Run
python3 -m venv lora-envfollowed bysource lora-env/bin/activate. - Install Dependencies: Install the Adafruit Blinka compatibility layer and the RFM9x driver. Run:
pip3 install adafruit-blinka adafruit-circuitpython-rfm9x - Attach the Antenna: Screw the SMA antenna onto the HAT. Never power up the TX sequence without the antenna attached.
Complete Python Code with Error Handling
This script targets the Raspberry Pi 4 Model B and the Dragino SX1276 HAT. It initializes the radio, sets the LoRa modulation parameters, and transmits a payload. I have included explicit try/except blocks to catch the exact hardware initialization failures common on the Pi's SPI bus.
import time
import board
import busio
import digitalio
import adafruit_rfm9x
# --- PIN DEFINITIONS (Dragino HAT on Pi 4) ---
# CS maps to SPI0 CE0 (Physical Pin 24, BCM 8)
CS = digitalio.DigitalInOut(board.CE0)
# Reset maps to BCM GPIO 25 (Physical Pin 22)
RESET = digitalio.DigitalInOut(board.D25)
# Initialize the hardware SPI0 bus
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
# --- RADIO INITIALIZATION WITH ERROR HANDLING ---
try:
# 915.0 MHz for US/AU, change to 868.0 for EU/UK
rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, 915.0)
# Configure LoRa Parameters
rfm9x.tx_power = 23 # Max power for SX1276 (5 to 23 dBm)
rfm9x.signal_bandwidth = 125000 # 125 kHz bandwidth
rfm9x.spreading_factor = 7 # SF7 (Fastest airtime, lowest range)
rfm9x.coding_rate = 5 # 4/5 coding rate
rfm9x.preamble_length = 8
rfm9x.enable_crc = True
print('RFM9x LoRa Radio Initialized Successfully.')
print(f'Silicon Version Register: 0x{rfm9x.version:02X}')
except RuntimeError as e:
print(f'FATAL HARDWARE ERROR: {e}')
print('Check SPI enablement, CS pin wiring, and 3.3V power.')
raise
# --- TRANSMISSION LOOP ---
try:
while True:
payload = b'Pi4-Node-01: Soil Moisture 42%'
print(f'Transmitting: {payload.decode("utf-8")}')
rfm9x.send(payload)
time.sleep(10) # Respect local duty cycle limits
except KeyboardInterrupt:
print('\nTransmission halted by user.')
# Put radio in sleep mode to save power on exit
rfm9x.sleep()
Debugging: "RuntimeError: Failed to find RFM9x LoRa radio"
If your script crashes on initialization, you will almost certainly see this exact error string:
RuntimeError: Failed to find RFM9x LoRa radio
This is thrown by the Adafruit library when it attempts to read the SX1276 silicon version register (address 0x42) over SPI and receives 0x00 or 0xFF instead of the expected 0x12. Here are the first three things to check, ranked by probability:
- SPI is Disabled or Contended (80% of cases): You forgot to enable SPI in
raspi-config, or another process (like an LCD screen or an active SPI daemon) has locked/dev/spidev0.0. Runlsmod | grep spito ensurespidevis loaded, and checklsof /dev/spidev0.0for locking processes. - Chip Select (CS) Mapping Error (15% of cases): The Dragino HAT uses CE0 (BCM 8). If your code accidentally defines
CS = digitalio.DigitalInOut(board.CE1)(BCM 7), the Pi will clock data out, but the SX1276 will ignore it because its CS pin is held high. Verify you are usingboard.CE0. - 3.3V Rail Sag or Logic Mismatch (5% of cases): The SX1276 draws up to 120mA during peak TX. If your Pi's 3.3V regulator is sagging below 3.0V due to a poor power supply, the radio will brownout during initialization. Measure the 3.3V pin with a multimeter under load. If it reads below 3.1V, upgrade your Pi's USB-C power supply.
For deeper hardware validation, use a logic analyzer on the MISO line. If you see the Pi sending clock pulses (SCK) and CS dropping low, but MISO remains flat at 0V or 3.3V, the SX1276 module is either dead or not seated properly on the header.
Extending and Simplifying the Build
Once you have raw point-to-point LoRa working, you need to decide how this node fits into your broader architecture.
How to Extend (Adding MQTT and Cloud Integration)
To bridge this LoRa node to a dashboard, add the paho-mqtt library. Wrap the receive loop in a threaded MQTT publisher. When the Pi receives a LoRa packet via rfm9x.receive(), parse the byte string and publish it to a local Mosquitto broker topic like sensors/field/node01. From there, Node-RED can ingest the MQTT stream and log it to InfluxDB. This keeps the RF layer isolated from the network layer, preventing Wi-Fi latency spikes from causing SPI buffer overruns.
How to Simplify (The Gateway Route)
If your actual goal is to connect off-the-shelf LoRaWAN sensors (like Milesight or Dragino commercial sensors) to The Things Network (TTN), stop writing custom Python. Custom SX1276 scripts only handle raw LoRa PHY, not the LoRaWAN MAC layer (OTAA joining, ADR, frame counters). Instead, simplify the build by flashing Raspberry Pi OS Lite and installing LoRa Basics Station or the ChirpStack Gateway Bridge. These pre-compiled binaries handle the LoRaWAN protocol overhead and connect directly to TTN via your Pi's Ethernet or Wi-Fi interface.






