Time to Complete: 45 minutes
Target Board Variant: LILYGO TTGO LoRa32 V2.1_1.6.1 (ESP32 + SX1276 + 0.96" OLED)
Getting a LoRa ESP32 node running is a rite of passage for remote telemetry builders. But the ecosystem is flooded with confusing board revisions, regional frequency mismatches, and silent SPI bus failures. If you just bought a generic 'LoRa32' board and are staring at a serial monitor that refuses to initialize the radio, you are in the right place.
This guide cuts through the datasheet noise. We will make a concrete hardware decision, map the exact pins for the most common board revision, flash a bulletproof point-to-point telemetry script, and debug the exact error strings that halt 90% of first-time builds.
The LoRa ESP32 Hardware Decision Tree
Do not buy a board until you have run your use case through this decision matrix. The LoRa silicon inside the ESP32 module dictates your range, power draw, and library compatibility.
| Your Requirement | Silicon Choice | Recommended Board | Why? |
|---|---|---|---|
| Budget < $30, range < 5km, standard Arduino libraries | SX1276 / SX1278 | LILYGO TTGO LoRa32 V2.1_1.6.1 | Mature ecosystem, supported by Sandeep Mistry's LoRa library out of the box. Cheapest entry point. |
| Range > 10km, high RF interference, strict power budgets | SX1262 | Heltec WiFi LoRa 32 (V3) | Higher TX power (+22dBm), better RX sensitivity, lower sleep current. Requires RadioLib. |
| Need raw module to design custom PCB later | SX1268 / SX1276 | AI-Thinker Ra-02 (Bare Module) | No ESP32 onboard. You wire it to a bare ESP32-WROOM-32 via SPI. Best for custom form factors. |
TTGO LoRa32 V2.1 Pin Mapping and Parts List
The biggest trap with LILYGO boards is the revision number. The V2.1_1.6.1 revision moved the SPI pins compared to the older V1.0 boards. If you copy-paste code meant for V1.0 into a V2.1 board, the radio will fail to initialize.
Spec-Sheet: SX1276 SPI & I2C Pinout (V2.1_1.6.1)
| Component | Function | ESP32 GPIO Pin | Notes |
|---|---|---|---|
| SX1276 | NSS (Chip Select) | GPIO 18 | Must be pulled high when inactive |
| SX1276 | RST (Reset) | GPIO 23 | Active low |
| SX1276 | DIO0 (Interrupt) | GPIO 26 | Used for TX/RX done interrupts |
| SX1276 | SCK (SPI Clock) | GPIO 5 | Shared SPI bus |
| SX1276 | MISO | GPIO 19 | Shared SPI bus |
| SX1276 | MOSI | GPIO 27 | Shared SPI bus |
| SSD1306 OLED | SDA (I2C Data) | GPIO 21 | I2C bus, not SPI |
| SSD1306 OLED | SCL (I2C Clock) | GPIO 22 | I2C bus, not SPI |
| MicroSD Slot | SD CS | GPIO 13 | Must be pulled HIGH if not using SD |
Required Parts
- Microcontroller: LILYGO TTGO LoRa32 V2.1_1.6.1 (SX1276)
- Antenna: 915MHz (or 868MHz) SMA male antenna, 2dBi to 5dBi gain. Never use a 2.4GHz WiFi antenna.
- Power: 1x 18650 Li-ion cell (3.7V nominal, e.g., Samsung 30Q or Panasonic NCR18650B)
- Cable: High-quality USB-C data cable (charge-only cables will fail to flash)
Wiring and Flashing the Point-to-Point Telemetry Code
We will build a simple transmitter that reads the ESP32's internal hall sensor (or a simulated telemetry value) and broadcasts it via LoRa. We are using the standard LoRa library by Sandeep Mistry, which is the most stable choice for the SX1276.
Step-by-Step Setup
- Install Board Definitions: In Arduino IDE, go to File > Preferences and add
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.jsonto Additional Board Manager URLs. Install the latest 'esp32' core. - Select Board: Tools > Board > ESP32 Arduino > TTGO LoRa32-OLED (or 'ESP32 Dev Module' if TTGO isn't listed, but ensure Flash Size is 4MB).
- Install Library: Tools > Manage Libraries. Search for
LoRaby Sandeep Mistry and install it. - Attach Antenna: Screw the 915MHz antenna into the SMA connector. Hand-tighten only.
- Flash Code: Copy the code below, verify, and upload.
Complete Compilable Transmitter Code
/*
* LoRa ESP32 Point-to-Point Transmitter
* Target: LILYGO TTGO LoRa32 V2.1_1.6.1 (SX1276)
* Library: Sandeep Mistry LoRa (v0.8.0+)
*/
#include
#include
// --- PIN DEFINITIONS FOR TTGO V2.1_1.6.1 ---
#define SCK 5
#define MISO 19
#define MOSI 27
#define SS 18
#define RST 23
#define DI0 26
// --- LORA CONFIGURATION ---
// Use 915E6 for US/AU, 868E6 for EU, 433E6 for specific regions
#define LORA_FREQ 915E6
#define SYNC_WORD 0xF3 // Must match receiver
#define TX_POWER 17 // dBm (Max 20, but 17 is safer for small antennas)
int packetCounter = 0;
void setup() {
Serial.begin(115200);
while (!Serial); // Wait for serial monitor
delay(1000);
Serial.println("Initializing LoRa Transmitter...");
// 1. Override default SPI pins for the TTGO V2.1 board
SPI.begin(SCK, MISO, MOSI, SS);
// 2. Set LoRa module pins
LoRa.setPins(SS, RST, DI0);
// 3. Initialize LoRa with error handling
if (!LoRa.begin(LORA_FREQ)) {
Serial.println("LoRa Initialization Failed!");
Serial.println("Check: 1. Antenna attached? 2. SPI pins correct? 3. Board revision matches V2.1?");
// Halt execution safely
while (1) {
delay(1000);
}
}
// 4. Configure RF parameters
LoRa.setSyncWord(SYNC_WORD);
LoRa.setTxPower(TX_POWER, PA_OUTPUT_PA_BOOST_PIN);
Serial.println("LoRa Initializing OK!");
}
void loop() {
// Read a sensor value (using ESP32 internal hall sensor for demo)
int sensorValue = hallRead();
Serial.print("Sending packet: ");
Serial.println(packetCounter);
// Begin packet construction
LoRa.beginPacket();
LoRa.print("NODE_01,");
LoRa.print(packetCounter);
LoRa.print(",");
LoRa.print(sensorValue);
// End packet and transmit (blocking until TX done)
int txResult = LoRa.endPacket();
if (txResult == 0) {
Serial.println("TX Timeout or Failed");
} else {
Serial.println("TX Success");
}
packetCounter++;
delay(5000); // 5-second TX interval
}
Debugging: Initialization Failures and Timeout Errors
When working with LoRa ESP32 modules, the serial monitor will usually tell you exactly where the hardware or software broke. Here is the decision path for the two most common failure modes.
Error 1: "LoRa Initialization Failed!"
This exact string prints when LoRa.begin() returns 0. It means the ESP32 cannot communicate with the SX1276 over the SPI bus, or the radio's internal oscillator failed to start.
- Wrong Board Revision Selected (Most Common): You are using V2.1 hardware but the code has V1.0 pins (e.g., SS on pin 18 vs SS on pin 18, but DIO0 on 26 vs 2). Fix: Verify the silkscreen on the back of your board says V2.1_1.6.1 and match the #define block exactly.
- SPI Bus Collision (The SD Card Trap): The TTGO board shares the SPI bus between the LoRa module and the MicroSD slot. If the SD CS pin (GPIO 13) is floating, the SD card controller can hijack the MISO line. Fix: If not using an SD card, add
pinMode(13, OUTPUT); digitalWrite(13, HIGH);in your setup() to deselect the SD card. - Missing or Loose Antenna: Some SX1276 modules have hardware protection that prevents initialization if the VSWR is infinite (no antenna). Fix: Screw the antenna on tightly.
Error 2: Transmitter says "TX Success" but Receiver gets nothing
The radio initialized, the packet was pushed to the FIFO buffer, but the receiver on your desk is deaf.
- Frequency Mismatch: You flashed 915E6 on the TX but the RX is listening on 868E6. LoRa will not bridge this gap. Both must match exactly.
- Sync Word Mismatch: The default sync word in the Mistry library is
0x12. If one node uses0x12and the other uses0xF3, they will ignore each other. Explicitly setLoRa.setSyncWord(0xF3)on both. - Spreading Factor (SF) / Bandwidth (BW) Mismatch: If you changed
LoRa.setSpreadingFactor(10)on the TX, the RX must also be set to 10. Default is 7.
The First 3 Things to Check When It Fails
Before rewriting your code, perform this physical and IDE checklist:
- Is the antenna physically attached? (Prevents hardware damage and VSWR lockouts).
- Did you select the correct COM port and 'TTGO LoRa32-OLED' board in the IDE? (Selecting 'ESP32 Dev Module' sometimes defaults to the wrong partition scheme, causing boot loops).
- Are the SPI pins explicitly defined in code? (Never rely on the default
SS, MOSI, MISO, SCKmacros in the ESP32 Arduino core; they map to the standard ESP32 DevKit pins, not the TTGO's custom routing).
Extending the Build: Mesh Networks and Power Optimization
Once your point-to-point link is stable, you will inevitably want to push the hardware further. Here is how to scale the build without starting from scratch.
How to Simplify (Headless Node)
If you are deploying this in a waterproof enclosure and do not need the OLED screen, you can reclaim I2C pins and save roughly 15mA of current draw. Simply do not initialize the Wire or SSD1306 libraries. To physically disable the screen on the TTGO board without desoldering, you can cut the 3.3V trace leading to the display's VCC pad on the underside of the PCB.
How to Extend (Meshtastic and RadioLib)
- Mesh Networking: If you want to build an off-grid text messaging mesh, stop writing custom Arduino C++ and flash Meshtastic firmware via the ESP32 Web Flasher. The TTGO V2.1 is a natively supported Tier-1 device in the Meshtastic ecosystem.
- Advanced RF Control: If you outgrow the Mistry library and need to implement LoRaWAN (joining a gateway via OTAA/ABP) or switch to the newer SX1262 silicon, migrate to the RadioLib library. RadioLib handles the complex state machines required for LoRaWAN MAC layers and supports virtually every Semtech chip variant.
- Deep Sleep: To run the node on a single 18650 cell for months, wrap the TX code in an ESP32 deep sleep cycle. Use
esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP)and shut down the SPI bus completely between pings. A properly optimized SX1276 node can draw under 10µA in deep sleep.
By locking in the correct hardware revision, explicitly routing the SPI bus, and respecting RF impedance rules, your LoRa ESP32 build will transition from a workbench curiosity to a reliable field-deployed sensor node.






