Skip to main content

Unboxing & Configuring Heltec ESP32 V3 LoRa Boards – Easy Peer-to-Peer Communication Setup

 

LoRa (Long Range) is a wireless communication technology designed for long-distance, unlicensed frequency, low-power data transmission. It’s widely used in IoT (Internet of Things) applications where devices need to send small amounts of data over several kilometers without relying on Wi-Fi or cellular networks. With its low power consumption and extended range, LoRa is ideal for smart agriculture, environmental monitoring, and remote sensing projects.

In this guide, I’ll walk you through my LoRa implementation setup using the Heltec ESP32 V3 boards. If you haven’t already installed the Arduino IDE on your computer, make sure to install it first. it’s essential for uploading code to your LoRa boards.

Once the Arduino IDE is successfully installed, it should look like the screenshot below (or similar depending on your version). From there, follow the steps outlined in this tutorial to configure your boards for peer-to-peer communication.


For this project, I used two Heltec ESP32 V3 LoRa boards—one configured as a transmitter and the other as a receiver. These boards are among the most reliable development boards available, with built-in LoRa and OLED display features, making them ideal for peer-to-peer wireless communication.

Below, you can see the unboxed Heltec LoRa board packages, which include the boards, antennas, and USB cables required for setup.


When you unbox the Heltec ESP32 V3 LoRa board, you’ll typically find the following components included:
  • Heltec ESP32 V3 LoRa Board
  • LoRa Antenna
  • Antenna Extension Cable
  • Male Bus Bars (Header Pins)
  • Power Supply Connector

Once you’ve unboxed your Heltec ESP32 V3 LoRa board and its accessories, it's time to assemble and set up everything. In my setup, I didn’t use an external power supply instead, I powered the board directly from my computer using a USB-C cable.
To enable communication between the Heltec board and your computer, you need to add Heltec board support to the Arduino IDE and install the necessary drivers for serial communication.

How to Add the Heltec ESP32 Board to Arduino IDE:
In Arduino IDE 2.x: go to File > Preferences

Add Board Manager URL:
In the “Additional Board Manager URLs” field, paste the following URL:

Install Heltec ESP32 Board Package:
  • Go to Tools > Board > Boards Manager
  • Search for "Heltec ESP32"
  • Click Install on the Heltec ESP32 package (installation may take a few minutes)
Select Your Board:
After installation, go to Tools > Board and select: “Heltec WiFi LoRa 32 (V3)”

Board selection snapshot

To enable your computer to communicate with the Heltec ESP32 V3 LoRa boards over USB, you need to install the correct serial communication driver.

Step-by-Step Instructions:
1. Download the CP210x Driver:
Go to the official Silicon Labs website:
https://www.silabs.com/developer-tools/usb-to-uart-bridge-vcp-drivers?tab=downloads

2. Select & Install the Driver:
Download the “CP210x Windows Drivers” – it’s usually the fourth item in the list.
Once downloaded, run the installer and follow the on-screen instructions.

3. Restart Your Computer:
After installation, it's recommended to restart your PC to ensure the driver loads correctly.

4. Select the Correct COM Port in Arduino IDE:
  • Open Arduino IDE
  • Go to Tools > Port
  • Select the COM port associated with your Heltec board
💡 Tip: You can verify the correct COM port via Device Manager:

Open Device Manager
Look under “Ports (COM & LPT)”
You should see something like:
Silicon Labs CP210x USB to UART Bridge (COMX)


Device manage snapshot

Before you can fully use your Heltec board, you’ll need to activate it the first time you connect it to your computer. This step ensures that the board is registered and ready for development.

Activation Steps:
  1. Connect your Heltec board to your computer using the USB-C cable.
  2. Open your browser and visit the official Heltec activation page: https://resource.heltec.cn/search
  3. On the page, enter the serial number of your board (usually printed on the back of the board or its packaging).
  4. Click the "Confirm" button.
  5. Once verified, you should see your board’s information and status displayed on the screen.

Activating the License Key via AT Command
During the activation process, you'll notice a 32-character license key displayed in the License column as above page. This key is required to register your board with the Heltec system.

Format of the License Key Command
You need to use this license key to send an AT command to the board. The correct format is:
AT+CDKEY=AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD

⚠️ Note: Replace AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD with the actual 32-character key shown on the Heltec website.

Sending the License Key via Arduino IDE
  1. Open the Serial Monitor in Arduino IDE.
  2. Paste the full AT+CDKEY=... line into the input/message field.
  3. Make sure the baud rate is set to the correct value (usually 115200).
  4. Press Enter to send the command to the board.
  5. Once successfully entered, the board will confirm the license activation.

Once you've successfully entered the license key using the AT+CDKEY=... command, your Heltec LoRa board is now activated and ready to use. You can now use the board according to your project requirements, including transmitting and receiving data via LoRa communication.
For my initial test, I used a simple example sketch provided by the official Heltec ESP32 library WiFi_LoRa_32_V3_FactoryTest. This basic program is great for verifying that your board is working correctly and that the LoRa features are functional.

/*
 * HelTec Automation(TM) WIFI_LoRa_32 factory test code, witch include
 * follow functions:
 *
 * - Basic OLED function test;
 *
 * - Basic serial port test(in baud rate 115200);
 *
 * - LED blink test;
 *
 * - WIFI connect and scan test;
 *
 * - LoRa Ping-Pong test (DIO0 -- GPIO26 interrup check the new incoming messages);
 *
 * - Timer test and some other Arduino basic functions.
 *
 * by Aaron.Lee from HelTec AutoMation, ChengDu, China
 * 成都惠利特自动化科技有限公司
 * https://heltec.org
 *
 * this project also realess in GitHub:
 * https://github.com/HelTecAutomation/Heltec_ESP32
*/

#include "Arduino.h"
#include "WiFi.h"
#include "images.h"
#include "LoRaWan_APP.h"
#include <Wire.h>  
#include "HT_SSD1306Wire.h"
/********************************* lora  *********************************************/
#define RF_FREQUENCY                                868000000 // Hz

#define TX_OUTPUT_POWER                             10        // dBm

#define LORA_BANDWIDTH                              0         // [0: 125 kHz,
                                                              //  1: 250 kHz,
                                                              //  2: 500 kHz,
                                                              //  3: Reserved]
#define LORA_SPREADING_FACTOR                       7         // [SF7..SF12]
#define LORA_CODINGRATE                             1         // [1: 4/5,
                                                              //  2: 4/6,
                                                              //  3: 4/7,
                                                              //  4: 4/8]
#define LORA_PREAMBLE_LENGTH                        8         // Same for Tx and Rx
#define LORA_SYMBOL_TIMEOUT                         0         // Symbols
#define LORA_FIX_LENGTH_PAYLOAD_ON                  false
#define LORA_IQ_INVERSION_ON                        false


#define RX_TIMEOUT_VALUE                            1000
#define BUFFER_SIZE                                 30 // Define the payload size here

char txpacket[BUFFER_SIZE];
char rxpacket[BUFFER_SIZE];

static RadioEvents_t RadioEvents;
void OnTxDone( void );
void OnTxTimeout( void );
void OnRxDone( uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr );

typedef enum
{
    LOWPOWER,
    STATE_RX,
    STATE_TX
}States_t;

int16_t txNumber;
int16_t rxNumber;
States_t state;
bool sleepMode = false;
int16_t Rssi,rxSize;

String rssi = "RSSI --";
String packSize = "--";
String packet;
String send_num;
String show_lora = "lora data show";

unsigned int counter = 0;
bool receiveflag = false; // software flag for LoRa receiver, received data makes it true.
long lastSendTime = 0;        // last send time
int interval = 1000;          // interval between sends
uint64_t chipid;
int16_t RssiDetection = 0;


void OnTxDone( void )
{
  Serial.print("TX done......");
  state=STATE_RX;

}

void OnTxTimeout( void )
{
  Radio.Sleep( );
  Serial.print("TX Timeout......");
  state=STATE_TX;
}

void OnRxDone( uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr )
{
  rxNumber++;
  Rssi=rssi;
  rxSize=size;
  memcpy(rxpacket, payload, size );
  rxpacket[size]='\0';
  Radio.Sleep( );
  Serial.printf("\r\nreceived packet \"%s\" with Rssi %d , length %d\r\n",rxpacket,Rssi,rxSize);
  Serial.println("wait to send next packet");
  receiveflag = true;
  state=STATE_TX;
}


void lora_init(void)
{
  Mcu.begin(HELTEC_BOARD,SLOW_CLK_TPYE);
  txNumber=0;
  Rssi=0;
  rxNumber = 0;
  RadioEvents.TxDone = OnTxDone;
  RadioEvents.TxTimeout = OnTxTimeout;
  RadioEvents.RxDone = OnRxDone;

  Radio.Init( &RadioEvents );
  Radio.SetChannel( RF_FREQUENCY );
  Radio.SetTxConfig( MODEM_LORA, TX_OUTPUT_POWER, 0, LORA_BANDWIDTH,
                                 LORA_SPREADING_FACTOR, LORA_CODINGRATE,
                                 LORA_PREAMBLE_LENGTH, LORA_FIX_LENGTH_PAYLOAD_ON,
                                 true, 0, 0, LORA_IQ_INVERSION_ON, 3000 );

  Radio.SetRxConfig( MODEM_LORA, LORA_BANDWIDTH, LORA_SPREADING_FACTOR,
                                 LORA_CODINGRATE, 0, LORA_PREAMBLE_LENGTH,
                                 LORA_SYMBOL_TIMEOUT, LORA_FIX_LENGTH_PAYLOAD_ON,
                                 0, true, 0, 0, LORA_IQ_INVERSION_ON, true );
  state=STATE_TX;
}


/********************************* lora  *********************************************/

SSD1306Wire  factory_display(0x3c, 500000, SDA_OLED, SCL_OLED, GEOMETRY_128_64, RST_OLED); // addr , freq , i2c group , resolution , rst


void logo(){
  factory_display.clear();
  factory_display.drawXbm(0,5,logo_width,logo_height,(const unsigned char *)logo_bits);
  factory_display.display();
}

void WIFISetUp(void)
{
  // Set WiFi to station mode and disconnect from an AP if it was previously connected
  WiFi.disconnect(true);
  delay(100);
  WiFi.mode(WIFI_STA);
  WiFi.setAutoReconnect(true);
  WiFi.begin("Galaxya72","mobitelqw");//fill in "Your WiFi SSID","Your Password"
  delay(100);

  byte count = 0;
  while(WiFi.status() != WL_CONNECTED && count < 10)
  {
    count ++;
    delay(500);
    factory_display.drawString(0, 0, "Connecting...");
    factory_display.display();
  }

  factory_display.clear();
  if(WiFi.status() == WL_CONNECTED)
  {
    factory_display.drawString(0, 0, "Connecting...OK.");
    factory_display.display();
//    delay(500);
  }
  else
  {
    factory_display.clear();
    factory_display.drawString(0, 0, "Connecting...Failed");
    factory_display.display();
    //while(1);
  }
  factory_display.drawString(0, 10, "WIFI Setup done");
  factory_display.display();
  delay(500);
}

void WIFIScan(unsigned int value)
{
  unsigned int i;
    WiFi.mode(WIFI_STA);

  for(i=0;i<value;i++)
  {
    factory_display.drawString(0, 20, "Scan start...");
    factory_display.display();

    int n = WiFi.scanNetworks();
    factory_display.drawString(0, 30, "Scan done");
    factory_display.display();
    delay(500);
    factory_display.clear();

    if (n == 0)
    {
      factory_display.clear();
      factory_display.drawString(0, 0, "no network found");
      factory_display.display();
      //while(1);
    }
    else
    {
      factory_display.drawString(0, 0, (String)n);
      factory_display.drawString(14, 0, "networks found:");
      factory_display.display();
      delay(500);

      for (int i = 0; i < n; ++i) {
      // Print SSID and RSSI for each network found
        factory_display.drawString(0, (i+1)*9,(String)(i + 1));
        factory_display.drawString(6, (i+1)*9, ":");
        factory_display.drawString(12,(i+1)*9, (String)(WiFi.SSID(i)));
        factory_display.drawString(90,(i+1)*9, " (");
        factory_display.drawString(98,(i+1)*9, (String)(WiFi.RSSI(i)));
        factory_display.drawString(114,(i+1)*9, ")");
        //factory_display.println((WiFi.encryptionType(i) == WIFI_AUTH_OPEN)?" ":"*");
        delay(10);
      }
    }

    factory_display.display();
    delay(800);
    factory_display.clear();
  }
}

bool resendflag=false;
bool deepsleepflag=false;
bool interrupt_flag = false;
void interrupt_GPIO0()
{
  interrupt_flag = true;
}
void interrupt_handle(void)
{
  if(interrupt_flag)
  {
    interrupt_flag = false;
    if(digitalRead(0)==0)
    {
      if(rxNumber <=2)
      {
        resendflag=true;
      }
      else
      {
        deepsleepflag=true;
      }
    }
  }

}
void VextON(void)
{
  pinMode(Vext,OUTPUT);
  digitalWrite(Vext, LOW);
 
}

void VextOFF(void) //Vext default OFF
{
  pinMode(Vext,OUTPUT);
  digitalWrite(Vext, HIGH);
}
void setup()
{
  Serial.begin(115200);
  VextON();
  delay(100);
  factory_display.init();
  factory_display.clear();
  factory_display.display();
  logo();
  delay(300);
  factory_display.clear();

  WIFISetUp();
  WiFi.disconnect(); //
  WiFi.mode(WIFI_STA);
  delay(100);

  WIFIScan(1);

  chipid=ESP.getEfuseMac();//The chip ID is essentially its MAC address(length: 6 bytes).
  Serial.printf("ESP32ChipID=%04X",(uint16_t)(chipid>>32));//print High 2 bytes
  Serial.printf("%08X\n",(uint32_t)chipid);//print Low 4bytes.

  attachInterrupt(0,interrupt_GPIO0,FALLING);
  lora_init();
  packet ="waiting lora data!";
  factory_display.drawString(0, 10, packet);
  factory_display.display();
  delay(100);
  factory_display.clear();
  pinMode(LED ,OUTPUT);
  digitalWrite(LED, LOW);  
}


void loop()
{
interrupt_handle();
 if(deepsleepflag)
 {
  VextOFF();
  Radio.Sleep();
  SPI.end();
  pinMode(RADIO_DIO_1,ANALOG);
  pinMode(RADIO_NSS,ANALOG);
  pinMode(RADIO_RESET,ANALOG);
  pinMode(RADIO_BUSY,ANALOG);
  pinMode(LORA_CLK,ANALOG);
  pinMode(LORA_MISO,ANALOG);
  pinMode(LORA_MOSI,ANALOG);
  esp_sleep_enable_timer_wakeup(600*1000*(uint64_t)1000);
  esp_deep_sleep_start();
 }

 if(resendflag)
 {
  state = STATE_TX;
  resendflag = false;
 }

if(receiveflag && (state==LOWPOWER) )
{
  receiveflag = false;
  packet ="R_data:";
  int i = 0;
  while(i < rxSize)
  {
    packet += rxpacket[i];
    i++;
  }
  packSize = "R_Size: ";
  packSize += String(rxSize,DEC);
  packSize += " R_rssi: ";
  packSize += String(Rssi,DEC);
  send_num = "send num: ";
  send_num += String(txNumber,DEC);
  factory_display.drawString(0, 0, show_lora);
  factory_display.drawString(0, 10, packet);
  factory_display.drawString(0, 20, packSize);
  factory_display.drawString(0, 50, send_num);
  factory_display.display();
  delay(10);
  factory_display.clear();

  if((rxNumber%2)==0)
  {
   digitalWrite(LED, HIGH);  
  }
}
switch(state)
  {
    case STATE_TX:
      delay(1000);
      txNumber++;
      sprintf(txpacket,"hello %d,Rssi:%d",txNumber,Rssi);
      Serial.printf("\r\nsending packet \"%s\" , length %d\r\n",txpacket, strlen(txpacket));
      Radio.Send( (uint8_t *)txpacket, strlen(txpacket) );
      state=LOWPOWER;
      break;
    case STATE_RX:
      Serial.println("into RX mode");
      Radio.Rx( 0 );
      state=LOWPOWER;
      break;
    case LOWPOWER:
      Radio.IrqProcess( );
      break;
    default:
      break;
  }
}

Comments

Most Popular Topics

ESP32 with Neo-6M GPS Module: Hardware Serial Code & Practical Implementation Guide

In this project, I used the ESP32 WROOM-32 development board in combination with the Neo-6M GPS module to acquire real-time satellite-based location data. The hardware components were interconnected using hardware serial (UART) to ensure reliable communication between the ESP32 and the GPS module. The wiring setup is illustrated below. Neo-6M GPS Module     |      ESP32 WROOM-32        Vcc      =====>   VIN (3.3V) GND   =====>     GND     TX      =====>    GPIO16     RX     =====>   GPIO17 Snapshot of the NEO-6M GPS Module used in this project: Front View of the NEO-6M GPS Module Backside of the NEO-6M GPS Module Snapshot of the ESP32 WROOM-32 Arduino board used in this project: After the interconnecting these two boards you have to follow following procedures. Install TinyGPS++ library: Open Ardui...

Monitor Docker Services Using Grafana, Prometheus & cAdvisor — Without Touching the CLI

  If you're running Docker containers on a server, monitoring their performance using command-line tools can be time-consuming and difficult to visualize. In this guide, I’ll show you how to monitor your Docker services using Grafana, Prometheus, and cAdvisor—all through a web interface, without relying on CLI tools after setup. You’ll be able to: Track real-time Docker container metrics Visualize system performance through a Grafana dashboard Use Prometheus to scrape and expose container metrics Deploy everything using simple Docker commands Step 1: Run Your Docker Containers Make sure your Docker containers are already running. Here’s a snapshot of my currently active containers: Step 2: Install cAdvisor in a Docker Container To collect container metrics in real time, we’ll run cAdvisor inside a dedicated Docker container. Run the cAdvisor Container: sudo docker run   --volume=/:/rootfs:ro   --volume=/var/run:/var/run:rw   --volume=/sys:/sys:ro   --volume=/var...

Analyzing Seismic Waves from Human Footfalls Using IoT Vibration Sensors and AI

  In this test, I used an ESP32 development board, an ADS1115 16-bit analog-to-digital converter, and an SM-24 geophone sensor to capture seismic signals generated by human footfalls. Rather than relying on an external analog bandpass filter, which often introduces additional electrical noise. I implemented a digital bandpass filter directly on the ESP32 microcontroller. This software-based signal conditioning approach enabled more precise control over frequency filtering and reduced hardware complexity. The filter was designed to pass frequencies in the 5 Hz to 60 Hz range, which corresponds to the dominant frequency band of human footfall vibrations. The complete sensor setup for footfall vibration acquisition is illustrated below: Based on insights from prior research studies, the SM-24 geophone sensor exhibits higher accuracy and signal stability when buried at a depth of approximately 20 centimeters. To maximize seismic wave sensitivity, the sensor in this experiment was embed...

How to Design and Simulate a 4-Phase Interleaved Boost Converter Using MATLAB Simulink

In this case study, I investigate the performance of a 4-phase interleaved boost converter using MATLAB Simulink. This type of DC-DC converter architecture is widely used in high-efficiency power systems due to its improved current sharing and reduced output ripple. Each phase of the converter is symmetrically designed using identical passive components: Inductors: L₁ = L₂ = L₃ = L₄ = 1.5 mH Capacitors: C₁ = C₂ = 200 μF The converter supplies an inductive load that draws a constant output current of 5 A. The input voltage is fixed at 30 V, and the converter is operated at a duty cycle (D) of 0.6. The primary simulation objectives are to analyze: The voltage gain of the converter, The output voltage ripple characteristics, And the phase current sharing behavior under these operating conditions. Simulink design of 4-phase interleaved boost converter Simulink inputs and output of design Through this design and simulation, the following steady state waveforms can be evaluated: Load voltage...

Scientific Writing - Research Article

  Significant sections of the Research Article How to write an Abstract? The abstract section is very important for the scientific paper because it represents all the contents briefly of a paper and the reader will decide whether or not to read the whole article. This is written by summarizing the introduction section. It has to briefly include the  problem, method, findings, and conclusion usually in the factual or past tense. How to write an Introduction? Start with a broad overview of the research scope. You have to provide a general background of the field or topic. Then you write a  literature survey about the topic. It does a critical analysis of the existing research on a specific topic. The next step is explaining a problem statement or research gaps . Then you can be writing  research objectives to provide the solution to the given problem statement or research gaps. Finally write scope and limitations that explain how the limitations may have affec...

The Example two story house project scheduling in Project Management

First, we must identify the various activities that must be carried out in constructing a two-story house and group them into suitable work breakdown structures (WBS). The following figure indicates the WBS of this sample project and appropriate implementation periods for each activity. Note that the project has to complete within 12 months as a condition. Therefore, we have to be very careful when scheduling the duration of activities in order to complete the project on time. The next step is to build a project using a computer program. Here I used MS Project software and the following figure shows the Project plan and the status of activities completed. The red color activities and arrows indicate the critical path of the project. Path activities have to be completed between the exact period in order to hand over the project on time.

What is a Network Operation Center (NOC)? Key Functions, Roles, and Benefits

A Network Operation Center (NOC) is a centralized facility where IT professionals monitor, manage, and maintain network systems around the clock. Acting as the command center for network performance and security, a NOC ensures seamless communication, stability, and efficiency across an organization’s infrastructure. NOCs are responsible for a range of critical tasks, including: Monitoring network devices and data flows Maintaining system health and software updates Troubleshooting and identifying network anomalies Rectification of errors and service disruptions Escalating issues to higher-level support teams Supervising and organizing system operations A NOC can be either internally managed by the organization or outsourced to a managed service provider (MSP). The choice depends on factors such as company size, network complexity, and operational budget. NOC Performances Metrics and KPIs Who Needs a NOC? Organizations with large-scale or mission-critical networks—such as telecom pr...

How MPLS Powers Modern Telecom Infrastructure: Architecture, Case Study, and Enterprise Benefits

Abstract Multiprotocol Label Switching (MPLS) is a versatile network technology that enhances the performance, reliability, and scalability of data traffic management. This article explores a case study of MPLS, detailing its architecture and key components, such as Provider Edge (PE) Routers and Provider (P) Routers. It highlights the benefits of MPLS including simplified connectivity deployment, protocol efficiency, and improved performance. Through practical analysis and examples, this content provides a comprehensive understanding of MPLS networks, their implementation, and their role in modern telecom infrastructure. Introduction Multi-Protocol Label Switching (MPLS) is a pivotal routing technique primarily utilized in telecommunication networks. In this case study, all telecom network nodes are located in specialized core rooms that maintain strict environmental conditions and uninterrupted power to ensure reliable performance. These core rooms are geographically distributed to m...

How to make the network connectivity between CentOS VM and Windows host computer | Deploy a simple website on Apache server

  In this instructional video, I guide you through the crucial process of establishing network connectivity between a CentOS Virtual Machine (VM) running on VMware Player and a Windows 10 host computer. This skill is fundamental for various practical and industrial applications, ensuring seamless communication between systems. Subsequently, I demonstrate the step-by-step installation of the Apache server, a pivotal component for web hosting. To enhance your understanding, I walk you through the deployment of a straightforward webpage on the Apache server, providing practical insights into web hosting capabilities. Mastering these essential processes empowers you to optimize connectivity and elevate your server's functionality for diverse applications.