Introduction
Interfacing ESP32 with AdaFruit LoRa RFM69HCW
LoRa technology was developed by a company called Semtech and it is a new wireless protocol designed specifically for long-range, low-power communications. LoRa stands for Long Range Radio and is mainly targeted for M2M and IoT networks. This technology will enable public or multi-tenant networks to connect a number of applications running on the same network. LoRa Alliance was formed to standardize LPWAN (Low Power Wide Area Networks) for IoT and is a non-profit association which features membership from a number of key market shareholders such as CISCO, actility, MicroChip, IBM, STMicro, SEMTECH, Orange mobile and many more. This alliance is key to providing interoperability among multiple nationwide networks.I will also post some tutorial links where I have interfaced Adafruit AdaFruit LoRa RFM69HCW with other microcontrollers. If you have any queries about it, ask in the comments and I will resolve it.
ESP32 has plethora of wireless communication capabilities. It supports WIFI and BLE out of box. ESP32 has capabilities of WIFI and BLE mesh Also. However these wireless features has low range of 50-100meters at best, While Lora practically supports 10km with 100mw of transmit power. So Adding a Lora Module can really make the ESP32 a true Wireless capability both in short and Long Range.
Hardware Introduction
ESP32 MODULE
ESP32-WROOM-32 is a powerful, generic Wi-Fi+BT+BLE MCU module that targets a wide variety of applications, ranging from low-power sensor networks to the most demanding tasks, such as voice encoding, music streaming and MP3 decoding.
At the core of this module is the ESP32-D0WDQ6 chip*. The chip embedded is designed to be scalable and adaptive. There are two CPU cores that can be individually controlled, and the CPU clock frequency is adjustable from 80 MHz to 240 MHz. The chip also has a low-power co-processor that can be used instead of the CPU to save power while performing tasks that do not require much computing power, such as monitoring of peripherals. ESP32 integrates a rich set of peripherals, ranging from capacitive touch sensors, Hall sensors, SD card interface, Ethernet, high-speed SPI, UART, I²S and I²C.
Picture 1. Esp32 devkit pin out
Features
ESP32 Processor
- • Xtensa® single-/dual-core 32-bit LX6 microprocessor(s)
- • CoreMark® score:
- – 1 core at 240 MHz: 504.85 CoreMark; 2.10 CoreMark/MHz
- – 2 cores at 240 MHz: 994.26 CoreMark; 4.14 CoreMark/MHz
- • 448 KB ROM
- • 520 KB SRAMMemory
Security
- Secure boot
- Flash encryption
- 1024-bit OTP, up to 768-bit for customers
- Cryptographic hardware acceleration
Peripherals
- 34 × programmable GPIOs
- 12-bit SAR ADC up to 18 channels
- 2 × 8-bit DAC
- 10 × touch sensors
- 4 × SPI
- 2 × I2S
- 2 × I2C
- 3 × UART
- 1 host (SD/eMMC/SDIO)
- 1 slave (SDIO/SPI)
- Ethernet MAC interface with dedicated DMA and IEEE 1588 support
- TWAI®, compatible with ISO 11898-1 (CAN Specification 2.0)
- RMT (TX/RX)
- Motor PWM
- LED PWM up to 16 channels
- Hall sensor
Power
- 2.7-5.5 volts
ESP32 Pin Description
Sensor

Adafruit LoRa RFM69HCW (Sensor Description)
The RFM69HCW is a transceiver module capable of operation over a wide frequency range, including the 315,433,868 and 915MHz license-free ISM (Industry Scientific and Medical) frequency bands. All major RF communication parameters are programmable and most of them can be dynamically set. The RFM69HCW offers the unique advantage of programmable narrow-band and wide- band communication modes.The RFM69HCW is optimized for low power consumption while offering high RF output power and channelized operation. Compliance ETSI and FCC regulations.
In order to better use RFM69HCW modules, this specification also involves a large number of the parameters and functions of its core chip RF69H’s,including those IC pins which are not leaded out. All of these can help customers gain a better understanding of the performance of RFM69HCW modules, and enhance the application skills.
Adafruit LoRa RFM69HCW specification:
- +20 dBm – 100 mW Power Output Capability
- High Sensitivity: down to -120 dBm at 1.2 kbps
- High Selectivity: 16-tap FIR Channel Filter
- Bullet-proof front end: IIP3 = -18 dBm, IIP2 = +35 dBm,80 dB Blocking Immunity, no Image
- Frequency response
- Low current: Rx = 16 mA, 100nA register retention
- Programmable Pout: -18 to +20 dBm in 1dB steps
- Constant RF performance over voltage range of module
- FSK Bit rates up to 300 kb/s
- Fully integrated synthesizer with a resolution of 61 Hz
- FSK, GFSK, MSK, GMSK and OOK modulations
- Built-in Bit Synchronizer performing Clock Recovery
- Incoming Sync Word Recognition
- 115 dB+ Dynamic Range RSSI
- Automatic RF Sense with ultra-fast AFC
- Packet engine with CRC-16, AES-128, 66-byte FIFO
- Built-in temperature sensor
- Module Size:16X16mm
Operating specifications:
All pins going into the breakout have level shifting circuitry to make them 3-5V logic level safe. Use whatever logic level is on Vin!
SCK – This is the SPI Clock pin, its an input to the chip
MISO – this is the Microcontroller In Serial Out pin, for data sent from the radio to your processor, 3.3V logic level
MOSI – this is the Microcontroller Out Serial In pin, for data sent from your processor to the radio
CS – this is the Chip Select pin, drop it low to start an SPI transaction. Its an input to the chip
RST – this is the Reset pin for the radio. It’s pulled high by default which is reset. Pull LOW to turn on the radio
G0 – the radio’s “GPIO 0” pin, also known as the IRQ pin, used for interrupt request notification from the radio to the microcontroller, 3.3V logic level
Schematic Diagram

Pin Connection Table
Source Code
FOR TRANSMITTER:
#include <SPI.h>
#include <RH_RF95.h>
#define RFM95_CS 34
#define RFM95_RST 27
// Change to 434.0 or other frequency, must match RX's freq!
#define RF95_FREQ 915.0
// Singleton instance of the radio driver
RH_RF95 rf95(RFM95_CS, RFM95_INT);
// Blinky on receipt
#define LED 13
void setup()
{
pinMode(LED, OUTPUT);
pinMode(RFM95_RST, OUTPUT);
digitalWrite(RFM95_RST, HIGH);
while (!Serial);
Serial.begin(9600);
delay(100);
Serial.println("Arduino LoRa RX Test!");
// manual reset
digitalWrite(RFM95_RST, LOW);
delay(10);
digitalWrite(RFM95_RST, HIGH);
delay(10);
while (!rf95.init()) {
Serial.println("LoRa radio init failed");
while (1);
}
Serial.println("LoRa radio init OK!");
// Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM
if (!rf95.setFrequency(RF95_FREQ)) {
Serial.println("setFrequency failed");
while (1);
}
Serial.print("Set Freq to: "); Serial.println(RF95_FREQ);
// Defaults after init are 434.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on
// The default transmitter power is 13dBm, using PA_BOOST.
// If you are using RFM95/96/97/98 modules which uses the PA_BOOST transmitter pin, then
// you can set transmitter powers from 5 to 23 dBm:
rf95.setTxPower(23, false);
}
void loop()
{
if (rf95.available())
{
// Should be a message for us now
uint8_t buf[RH_RF95_MAX_MESSAGE_LEN];
uint8_t len = sizeof(buf);
if (rf95.recv(buf, &len))
{
digitalWrite(LED, HIGH);
RH_RF95::printBuffer("Received: ", buf, len);
Serial.print("Got: ");
Serial.println((char*)buf);
Serial.print("RSSI: ");
Serial.println(rf95.lastRssi(), DEC);
// Send a reply
uint8_t data[] = "And hello back to you";
rf95.send(data, sizeof(data));
rf95.waitPacketSent();
Serial.println("Sent a reply");
digitalWrite(LED, LOW);
}
else
{
Serial.println("Receive failed");
}
}
}
FOR RECEIVER:
#include <SPI.h>
#include <RH_RF95.h>
#define RFM95_CS 34
#define RFM95_RST 27
// Change to 434.0 or other frequency, must match RX's freq!
#define RF95_FREQ 915.0
// Singleton instance of the radio driver
RH_RF95 rf95(RFM95_CS, RFM95_INT);
// Blinky on receipt
#define LED 13
void setup()
{
pinMode(LED, OUTPUT);
pinMode(RFM95_RST, OUTPUT);
digitalWrite(RFM95_RST, HIGH);
while (!Serial);
Serial.begin(9600);
delay(100);
Serial.println("Arduino LoRa RX Test!");
// manual reset
digitalWrite(RFM95_RST, LOW);
delay(10);
digitalWrite(RFM95_RST, HIGH);
delay(10);
while (!rf95.init()) {
Serial.println("LoRa radio init failed");
while (1);
}
Serial.println("LoRa radio init OK!");
// Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM
if (!rf95.setFrequency(RF95_FREQ)) {
Serial.println("setFrequency failed");
while (1);
}
Serial.print("Set Freq to: "); Serial.println(RF95_FREQ);
// Defaults after init are 434.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on
// The default transmitter power is 13dBm, using PA_BOOST.
// If you are using RFM95/96/97/98 modules which uses the PA_BOOST transmitter pin, then
// you can set transmitter powers from 5 to 23 dBm:
rf95.setTxPower(23, false);
}
void loop()
{
if (rf95.available())
{
// Should be a message for us now
uint8_t buf[RH_RF95_MAX_MESSAGE_LEN];
uint8_t len = sizeof(buf);
if (rf95.recv(buf, &len))
{
digitalWrite(LED, HIGH);
RH_RF95::printBuffer("Received: ", buf, len);
Serial.print("Got: ");
Serial.println((char*)buf);
Serial.print("RSSI: ");
Serial.println(rf95.lastRssi(), DEC);
// Send a reply
uint8_t data[] = "And hello back to you";
rf95.send(data, sizeof(data));
rf95.waitPacketSent();
Serial.println("Sent a reply");
digitalWrite(LED, LOW);
}
else
{
Serial.println("Receive failed");
}
}
}
CODE EXPLANATION:
For both Transmitter and Receiver the void Setup Code will be the same. Import the libraries SPI and RF_RH95. Define the pins and initialize.
Coming the loop of transmitter and receiver:
TRANSMISSION CODE:
void loop()
{
delay(1000); // Wait 1 second between transmits, could also 'sleep' here!
Serial.println("Transmitting..."); // Send a message to rf95_server
char radiopacket[20] = "Hello World # ";
itoa(packetnum++, radiopacket+13, 10);
Serial.print("Sending "); Serial.println(radiopacket);
radiopacket[19] = 0;
Serial.println("Sending..."); delay(10);
rf95.send((uint8_t *)radiopacket, 20);
Serial.println("Waiting for packet to complete..."); delay(10);
rf95.waitPacketSent();
If you are using the transmitter, this code will wait 1 second, then transmit a packet with “Hello World #” and an incrementing packet number. Its pretty simple, the delay does the waiting, you can replace that with low power sleep code. Then it generates the packet and appends a number that increases every tx. Then it simply calls send to transmit the data, and passes in the array of data and the length of the data.
Note that this does not do any addressing or subnetworking – if you want to make sure the packet goes to a particular radio, you may have to add an identifier/address byte on your own!
Then you call waitPacketSent() to wait until the radio is done transmitting. You will not get an automatic acknowledgement from the other radio unless it knows to send back a packet. Think of it like the ‘UDP’ of radio – the data is sent, but it’s not certain it was received! Also, there will not be any automatic retries.
RECEIVER CODE:
void loop()
{
if (rf95.available())
{
// Should be a message for us now
uint8_t buf[RH_RF95_MAX_MESSAGE_LEN];
uint8_t len = sizeof(buf);
if (rf95.recv(buf, &len))
{
digitalWrite(LED, HIGH);
RH_RF95::printBuffer("Received: ", buf, len);
Serial.print("Got: ");
Serial.println((char*)buf);
Serial.print("RSSI: ");
Serial.println(rf95.lastRssi(), DEC);
The Receiver has the same exact setup code, but the loop is different. Instead of transmitting, it is constantly checking if there’s any data packets that have been received. available() will return true if a packet with proper error-correction was received. If so, the receiver prints it out in hex and also as a ‘character string’
It also prints out the RSSI which is the receiver signal strength indicator. This number will range from about -15 to about -100. The larger the number (-15 being the highest you’ll likely see) the stronger the signal.
Once done it will automatically reply, which is a way for the radios to know that there was an acknowledgement.
GITHUB LINK
https://github.com/iottrends/iottrends/tree/main/LoRa%20Modules/Adafruit%20RFM69HCW