Introduction: The Super Long Distance WiFi of the ESP32 LoRa With Arduino IDE

About: Do you like technology? Follow my channel on Youtube and my Blog. In them I put videos every week of microcontrollers, arduinos, networks, among other subjects.

This is very useful for debugging and testing your program, due to the display that already accompanies it. Today, we will deal again with the ESP32 LoRa, this time in a project using the BME280 sensor. You already know that I’m a big fan of this chip for the possibility of its use in the field. We will then send requests using LoRa, perform the temperature, pressure, and humidity readings using the BME280 sensor, as well as send the response to the request with the read data.

In addition to collecting the data, the BME280 shows me the time it took to do all the reading and send it to the other ESP. I do all this using the LoRa radio, which goes far, as proven in a test where we reached 6.5 km.

Step 1: Communication

1 - Master sends a packet indicating that it wants to receive Slave data

2 - Slave receives and verifies this package

3 - Slave sends back a packet with data read from the BME280 sensor

4 - The Master receives it, verifies the package, and displays the data

Step 2: Heltec WiFi LoRa 32 Pinout

Step 3: BME280

Temperature, pressure, and humidity sensor.

Step 4: Assembly

Step 5: Library BME280

In the Arduino IDE, go to Sketch-> Include Library-> Manage Libraries ...

Search for BME280 and install the Adafruit BME280 Library

Step 6: Adafruit Unified Sensor Library

In the Arduino IDE, go to Sketch-> Include Library-> Manage Libraries ...

Search for adafruit sensor based libraries and install the Adafruit Unified Sensor

Step 7: Modify LoRa 32 WiFi I2C Pinout

It’s important to mention that the first time I compiled the BME280 in ESP32, it did not work out. So, this tip is imperative: you have to modify a .h from within the arduino compiler. This is an issue that is not quickly discovered, thus I want to explain here what you should do.

In C: \Users\<YOUR_USER>\Documents\Arduino\hardware\heltec\esp32\variants\wifi_lora_32\pins_arduino.h, more specifically in this last folder: pins_arduino.h, Heltec placed SAD and SCL on pins 21 and 22, respectively. However, the Adafruit lib does not accept these pins. The libraries that are accepted are # 4 and # 15. This change is therefore very important.


Open the file:


Change the SDA pins to 4, and the SCL pin to 15.

Step 8: Source Code

This source code is the same as I used in the video: ESP32 LoRa with Arduino IDE Send and Receive TX RX. I just modified it for the BME280.

Step 9: ​LoRaSendReceiveBME280.ino

At the beginning, we will include the libraries and define the destinations of the GPIOs, in addition to the frequency of the radio. Also, we will create constants to inform the Slave about the data work, as well as the return of these to the Master. We also structure the sensor data and point the variable to control the display.

#include <SPI.h>
#include <LoRa.h> #include <Wire.h> #include <SSD1306.h> //Deixe esta linha descomentada para compilar o Master //Comente ou remova para compilar o Slave #define MASTER #define RST 14 // GPIO14 RESET #define DI00 26 // GPIO26 IRQ(Interrupt Request) #define BAND 433E6 //Frequência do radio - exemplo : 433E6, 868E6, 915E6 //Constante para informar ao Slave que queremos os dados const String GETDATA = "getdata"; //Constante que o Slave retorna junto com os dados para o Master const String SETDATA = "setdata="; //Estrutura com os dados do sensor typedef struct { double temperature; double pressure; double humidity; }Data; //Variável para controlar o display SSD1306 display(0x3c, SDA, SCL);

LoRaSendReceiveBME280.ino - setupDisplay

In this first Setup, we will act on the configuration of the display.

void setupDisplay(){
//O estado do GPIO16 é utilizado para controlar o display OLED pinMode(16, OUTPUT); //Reseta as configurações do display OLED digitalWrite(16, LOW); //Para o OLED permanecer ligado, o GPIO16 deve permanecer HIGH //Deve estar em HIGH antes de chamar o display.init() e fazer as demais configurações, //não inverta a ordem digitalWrite(16, HIGH); //Configurações do display display.init(); display.flipScreenVertically(); display.setFont(ArialMT_Plain_16); display.setTextAlignment(TEXT_ALIGN_LEFT); }

LoRaSendReceiveBME280.ino - setupLoRa

Here, we’ll look at the initial LoRa settings.

//Configurações iniciais do LoRa
void setupLoRa(){ //Inicializa a comunicação SPI.begin(SCK, MISO, MOSI, SS); LoRa.setPins(SS, RST, DI00); //Inicializa o LoRa if (!LoRa.begin(BAND, true)){ //Se não conseguiu inicializar, mostra uma mensagem no display display.clear(); display.drawString(0, 0, "Erro ao inicializar o LoRa!"); display.display(); while (1); } //Ativa o crc LoRa.enableCrc(); //Ativa o recebimento de pacotes LoRa.receive(); }

Step 10: Master.ino

Master.ino – setup

In this part, we do the configurations that involve the Master, the configurations on the compilation, the interval between shipments, as well as the time of the last shipment. We also call up the initial display and LoRa settings.

//Compila apenas se MASTER estiver definido no arquivo principal
#ifdef MASTER //Intervalo entre os envios #define INTERVAL 500 //Tempo do último envio long lastSendTime = 0; void setup(){ Serial.begin(115200); //Chama a configuração inicial do display setupDisplay(); //Chama a configuração inicial do LoRa setupLoRa(); display.clear(); display.drawString(0, 0, "Master"); display.display(); }

Master.ino - loop

In this Loop, we define the sending of the package to inform the Slave that we’d like to receive data and also check if there are packages to be received.

void loop(){
//Se passou o tempo definido em INTERVAL desde o último envio if (millis() - lastSendTime > INTERVAL){ //Marcamos o tempo que ocorreu o último envio lastSendTime = millis(); //Envia o pacote para informar ao Slave que queremos receber os dados send(); } //Verificamos se há pacotes para recebermos receive(); }

Master.ino - send

We initialize the package and send what is contained in "GETDATA", to finalize and send the package later.

void send(){
//Inicializa o pacote LoRa.beginPacket(); //Envia o que está contido em "GETDATA" LoRa.print(GETDATA); //Finaliza e envia o pacote LoRa.endPacket(); }

Master.ino - receive

In this step, we check if the packet has the minimum length of characters that we expect and store a string. We also analyze if the header is what we expect, and we begin to read the data and show it on the display.

void receive(){
//Tentamos ler o pacote int packetSize = LoRa.parsePacket(); //Verificamos se o pacote tem o tamanho mínimo de caracteres que esperamos if (packetSize > SETDATA.length()){ String received = ""; //Armazena os dados do pacote em uma string for(int i=0; i

Master.ino - showData

Finally, we show the time the Master took to create the package, send it, receive the Slave, read it, create a new package and send it to the Master that receives and reads it. This is printed on the display.

void showData(Data data){
//Tempo que demorou para o Master criar o pacote, enviar o pacote, //o Slave receber, fazer a leitura, criar um novo pacote, enviá-lo //e o Master receber e ler String waiting = String(millis() - lastSendTime); //Mostra no display os dados e o tempo que a operação demorou display.clear(); display.drawString(0, 0, String(data.temperature) + " C"); display.drawString(0, 16, String(data.pressure) + " Pa"); display.drawString(0, 32, String(data.humidity) + "%"); display.drawString(0, 48, waiting + " ms"); display.display(); } #endif

Step 11: Slave.ino

Starting the Slave code, we compiled only if the Master is not defined in the main file. We have included the libraries and pointed out the person responsible for reading the temperature, pressure, and humidity.

//Compila apenas se MASTER não estiver definido no arquivo principal
#ifndef MASTER #include <adafruit_sensor.h> #include <adafruit_bme280.h> //Responsável pela leitura da temperatura, pressão e umidade Adafruit_BME280 bme;

Slave.ino - Setup

In Slave Setup, we call up the initial display and LoRa settings.

void setup(){
Serial.begin(115200); //Chama a configuração inicial do display setupDisplay(); //Chama a configuração inicial do LoRa setupLoRa(); //0x76 se pino SDO do sensor estiver no GND //0x77 se pino SDO do sensor estiver no 3.3v if (!bme.begin(0x76)) { display.clear(); display.drawString(0, 0, "Sensor não encontrado"); display.display(); while(1); } display.clear(); display.drawString(0, 0, "Slave esperando..."); display.display(); }

Slave.ino - Loop

As in the Master, in this part of the Slave Loop, we check if it has the expected number of characters and store the data in a String. We also simulated the reading of the data. We create the package, finalize it, and send it. Finally, we show the information on the display.

void loop(){
//Tenta ler o pacote int packetSize = LoRa.parsePacket(); //Verifica se o pacote possui a quantidade de caracteres que esperamos if (packetSize == GETDATA.length()) { String received = ""; //Armazena os dados do pacote em uma string while(LoRa.available()) { received += (char); } if(received.equals(GETDATA)) { //Faz a leitura dos dados Data data = readData(); Serial.println("Criando pacote para envio"); //Cria o pacote para envio LoRa.beginPacket(); LoRa.print(SETDATA); LoRa.write((uint8_t*)&data, sizeof(data)); //Finaliza e envia o pacote LoRa.endPacket(); showSentData(data); } } }

Slave.ino - readData

In this part, we deal with the function where the data is read.

//Função onde se faz a leitura dos dados
Data readData() { Data data; data.temperature = bme.readTemperature(); data.pressure = bme.readPressure(); data.humidity = bme.readHumidity(); return data; }

Slave.ino - showSentData

Finally, the data is displayed.

void showSentData(Data data)
{ //Mostra no display display.clear(); display.drawString(0, 0, "Enviou:"); display.drawString(0, 16, String(data.temperature) + " C"); display.drawString(0, 32, String(data.pressure) + " Pa"); display.drawString(0, 48, String(data.humidity) + "%"); display.display(); } #endif

Step 12: Download the Files: