The Decision Path: Hardware Selection for Long-Range Nodes
When browsing lists of great Arduino projects, long-range environmental sensors consistently rank at the top. But moving from a blinking LED to a field-deployable LoRa node requires navigating a minefield of voltage mismatches and sensor degradation. Before writing a single line of code, you must lock in your hardware stack. Use the decision matrix below to select your components.
| Decision Point | Option A | Option B | Verdict & Default Pick |
|---|---|---|---|
| Soil Sensor Type | Resistive (Nickel-plated) | Capacitive (v1.2 / 555-timer) | Capacitive v1.2. Resistive probes corrode via electrolysis within 3 weeks in wet soil. Capacitive probes measure dielectric changes and last for years. |
| LoRa Frequency | 868 MHz | 915 MHz | 915 MHz for North/South America and Australia. 868 MHz for Europe/UK. Match your regional ISM band to avoid legal and interference issues. |
| Microcontroller | Arduino Uno Rev3 (5V logic) | Arduino Nano 33 IoT (3.3V logic) | Uno Rev3. While 3.3V boards skip level-shifting, the Uno's robust 5V regulator and physical layout make it the superior bench and prototyping standard. |
Parts List and Spec Sheet
Sourcing the exact variants matters. Generic clones often lack the precise SPI timing or voltage regulation required for stable LoRa transmission.
- Microcontroller: Arduino Uno Rev3 (SKU: ABX00066) — ~$27.00
- LoRa Module: Adafruit RFM95W LoRa Radio Breakout (PID: 3072, 915MHz) — ~$21.50
- Level Shifter: Adafruit 4-channel I2C-safe Bi-directional Logic Level Converter - BSS138 (PID: 757) — ~$3.95. Critical: The RFM95W uses 3.3V logic. Feeding 5V from the Uno's SPI pins will permanently brick the SX1278 silicon.
- Sensor: Capacitive Soil Moisture Sensor v1.2 (Generic, look for the 555 timer IC on the back) — ~$4.00
- Passives: 10kΩ pull-up resistor, 0.1µF ceramic decoupling capacitor.
Pin Mapping and Level-Shifting the SPI Bus
The most common point of failure in great Arduino projects involving RF modules is the SPI bus. The Arduino Uno outputs 5V on its SPI lines (MOSI, SCK, CS). The RFM95W expects 3.3V. We use the BSS138 to shift these signals safely. Note the directional flow of MISO (Master In, Slave Out) — data travels from the radio to the Uno.
| Arduino Uno Rev3 (5V) | BSS138 Level Shifter (HV Side) | BSS138 Level Shifter (LV Side) | Adafruit RFM95W (3.3V) |
|---|---|---|---|
| 5V Pin | HV | - | - |
| 3.3V Pin | - | LV | VIN (or 3.3V out) |
| GND | GND (HV side) | GND (LV side) | GND |
| Pin 13 (SCK) | HV1 | LV1 | SCK |
| Pin 11 (MOSI) | HV2 | LV2 | MOSI |
| Pin 12 (MISO) | HV3 | LV3 | MISO |
| Pin 10 (SS/CS) | HV4 | LV4 | CS |
| Pin 9 | - (Direct wire OK for RST) | - | RST |
| Pin 2 (INT) | - (Direct wire OK for DIO0) | - | DIO0 (IRQ) |
Bench Tip: Place the 0.1µF ceramic capacitor directly across the VIN and GND pins of the RFM95W breakout. LoRa transmission spikes can draw up to 120mA for milliseconds; without local decoupling, the Arduino's 3.3V regulator will brown out and reset the radio mid-packet.
Complete Compilable Code (Target: Arduino Uno Rev3)
This code targets the Arduino Uno Rev3 and relies on the RadioLib library (install via Arduino Library Manager, version 6.x or newer). It reads the capacitive sensor, maps the 10-bit ADC value to a moisture percentage, and transmits it via LoRa.
#include <RadioLib.h>
#include <SPI.h>
// --- Pin Definitions ---
#define LORA_CS 10
#define LORA_DIO0 2
#define LORA_RST 9
#define SOIL_PIN A0
// Initialize SX1278 module (RFM95W uses the SX1278 chip)
SX1278 radio(new Module(LORA_CS, LORA_DIO0, LORA_RST, RADIOLIB_NC));
// Calibration values for Capacitive Soil Sensor v1.2
// Measure these in your specific setup using Serial.println(analogRead(SOIL_PIN));
const int AIR_VALUE = 580; // ADC reading when sensor is completely dry in air
const int WATER_VALUE = 290; // ADC reading when sensor is submerged in water
void setup() {
Serial.begin(115200);
while (!Serial && millis() < 3000); // Wait for serial monitor (with timeout)
Serial.println(F("[INIT] Starting LoRa Soil Node..."));
// Initialize LoRa radio at 915.0 MHz
int state = radio.begin(915.0, 125.0, 9, 7, 0x18, 10);
if (state != RADIOLIB_ERR_NONE) {
Serial.print(F("SX1278 initialization failed, code "));
Serial.println(state);
// Halt execution if radio fails to prevent infinite error loops
while (true) {
delay(1000);
}
}
Serial.println(F("[INIT] Radio online. Starting sensor loop."));
}
void loop() {
// Read sensor and apply exponential moving average to smooth noise
int raw_adc = analogRead(SOIL_PIN);
// Map ADC to percentage (constrain ensures we don't exceed 0-100% bounds)
int moisture_pct = map(raw_adc, AIR_VALUE, WATER_VALUE, 0, 100);
moisture_pct = constrain(moisture_pct, 0, 100);
// Build payload string
char payload[32];
snprintf(payload, sizeof(payload), "MOIST:%d%%,RAW:%d", moisture_pct, raw_adc);
Serial.print(F("[TX] Sending: "));
Serial.println(payload);
// Transmit packet and check for errors
int tx_state = radio.transmit(payload);
if (tx_state == RADIOLIB_ERR_NONE) {
Serial.println(F("[TX] Success!"));
} else {
Serial.print(F("[TX] Failed, code "));
Serial.println(tx_state);
}
// Deep sleep or delay (using delay for Uno Rev3 simplicity; use LowPower lib for production)
delay(60000); // Transmit every 60 seconds
}
Debugging: "SX1278 initialization failed, code -2"
If your serial monitor outputs the exact string SX1278 initialization failed, code -2, the Arduino cannot communicate with the SX1278 silicon over SPI. In RadioLib, error code -2 maps to RADIOLIB_ERR_CHIP_NOT_FOUND. Do not immediately assume the module is dead. Follow this ranked cause list.
The First 3 Things to Check
- Verify the SPI CS Pin Definition: The hardware SPI bus on the Uno uses pins 11, 12, and 13. However, the Chip Select (CS) pin can be any digital pin. If your wiring uses Pin 10 for CS, but your code defines
#define LORA_CS 8, the radio will ignore all SPI traffic. Ensure the#definematches your physical jumper wire exactly. - Check BSS138 Power Rails: The BSS138 level shifter requires two separate power references. If you forgot to wire the Arduino's 3.3V output to the LV pin on the shifter, the MOSFETs will not have a reference voltage to pull the 3.3V side low. The RFM95W will see floating logic levels and fail to respond to the SPI handshake.
- Inspect the MISO Direction: MISO is the only SPI line where data flows from the Radio to the Arduino. If you accidentally swapped HV3 and LV3, or wired MISO to the MOSI channel on the level shifter, the Uno will transmit commands but receive garbage (or 0x00) back. The SX1278 version register read will fail, triggering code -2.
Secondary Failure Modes
If the first three checks pass, measure the voltage on the RFM95W VIN pin with a multimeter. It must read between 3.2V and 3.6V. If it reads 5V, you bypassed the level shifter's power routing and have likely fried the module's internal voltage regulator. If it reads 0V, check your breadboard power rails for continuity breaks.
Extending and Simplifying the Build
Once you have a stable baseline, you can adapt this node to fit specific deployment constraints.
How to Extend: Solar Power and Deep Sleep
The Arduino Uno Rev3 is power-hungry, drawing ~45mA at idle. To run this great Arduino project off a 5W solar panel and a 18650 Li-ion cell, you must swap the Uno for an Arduino Nano 33 IoT or an Adafruit Feather M0. These boards support true deep sleep via the ArduinoLowPower library. You would replace the delay(60000); block with LowPower.sleep(60000);, dropping idle current from 45mA down to ~150µA, allowing a 2000mAh cell to survive weeks without sun.
How to Simplify: Local UART Display
If you don't need long-range LoRa telemetry and just want a local greenhouse monitor, strip out the RadioLib dependencies entirely. Replace the LoRa transmission block with an I2C OLED display (SSD1306, 128x64). Wire the OLED to the Uno's A4 (SDA) and A5 (SCL) pins. This eliminates the need for the BSS138 level shifter (most SSD1306 breakouts are 5V tolerant) and reduces the code footprint by roughly 60%, leaving plenty of SRAM for logging historical moisture data to an external EEPROM.
For more details on SPI bus mechanics and timing constraints, refer to the official Arduino SPI Communication Guide. If you are designing custom PCBs for these nodes, consult the Adafruit RFM95W Breakout documentation for exact antenna matching network schematics.






