RF (Radio Frequency)

Enter Your Project for a chance to win a Spectrum Analyzer for the Most Innovative RF Project!

Back to The Project14 homepage

Project14 Home
Monthly Themes
Monthly Theme Poll

 

 

After all the building and measuring, it is finally time to put the antenna to the test, using the ESP32 LoRa boards I have available. The plan is to set one board as a transmitter on a fixed location, and the other to be used as a mobile receiver that I can carry around with me. All the antennae will be connected, in turns, to the transmitter, while the receiver will use always the same antenna (for convenience, I will use the small FPC for that). Time to do some programming!

 

Programming ESP32 LoRa Boards

Lets start by introducing the boards: for this project I'm using a TTGO (actually TTGO-compatible) ESP32 LoRa board, which is powered by EspressIf's ESP32 SoC. The heart of the ESP32 is the dual-core Tensilica Xtensa LX6 32bit processor (240 MHz), with 520Kb SRAM, integrated WiFi 802.11 b/g/n/e/i (802.11n @ 2.4 GHz up to 150 Mbit/s) and Bluetooth v4.2 BR/EDR and Bluetooth Low Energy (BLE). The board comes with 128MB of Flash memory and the LoRa functionality is provided by the Semtech SX1278 chip (433MHz, TX power 20dBm).Also, there is a lithium battery charging circuit and interface and the CP2102 USB to serial interface makes it easy to flash the device via your computer USB. Below is the pinout of the board:

 

 

Development Environment Setup

EspressIf provide a quite powerful SDK  to support the ESP32, with a rich set of API, covering all the components of the SoC. The SDK can be used standalone (using the idf.py command line), or can be integrated in several development environments, like Ecplise and Arduino IDE. The integration with Eclipse can be achieved installing the eclipse plugin (Eclipse set up is described here). Although Eclipse is great, and it works brilliantly, I opted for the ArduinoIDE becuase EspressIf SDK does not include libraries for LoRa and for the OLED display (receiver board only), which I need to use and have no time to port over.

 

Setting up the ArduinoIDE is pretty straightforward: all is needed is to add the ESP32 boards using the Board repository URL (for EspressIf, use https://dl.espressif.com/dl/package_esp32_index.json ), then use the Board Manager to install "ESP32 by EspressIf Systems". This will install the toolchain and all the libraries needed by the ESP32. We also need to install the libraries for LoRa and for the OLED, which can be done by using the usual "Manage Libraries...". The libraries needed are:

 

This is pretty much all is needed, now let's code

 

The Transmitter

The LoRa library comes with some example sketches for transmitters, which I'm going to use as base for my own. The communication with the LoRa component is carried via SPI interface (see the code for the pin numbers).The transmitter itself is very simple: all it need to do is to keep sending messages indefinitely, at regular intervals.For the LoRa message, I use a sequence of byte (4), the first 2 being the "magic number" to identify the message as my own, and the other 2 bytes for the message ID (a simple counter). This means, the transmitter board can be use "as is", without any further component. Here is the code:

 

#include <SPI.h>
#include <LoRa.h>
#include <Wire.h>  


// Pin definetion of WIFI LoRa 32
#define SCK     5    // GPIO5  -- SX127x's SCK
#define MISO    19   // GPIO19 -- SX127x's MISO
#define MOSI    27   // GPIO27 -- SX127x's MOSI
#define SS      18   // GPIO18 -- SX127x's CS
#define RST     14   // GPIO14 -- SX127x's RESET
#define DI00    26   // GPIO26 -- SX127x's IRQ(Interrupt Request)

#define LED     25

#define BAND    433E6  //you can set band here directly,e.g. 868E6,915E6
#define PABOOST true

float BW[]={7.8E3, 10.4E3, 15.6E3, 20.8E3, 31.25E3, 41.7E3, 62.5E3, 125E3, 250E3};
int   SF[]={6,7,8,9,10,11,12};
int   CR[]={5,8};

typedef struct {
  char mb[2]={'O','Y'};
  uint16_t packet_id=0;
} msg_t;

msg_t counter;

void setup()
{ 

  Serial.begin(115200);
  
  SPI.begin(SCK,MISO,MOSI,SS);
  LoRa.setPins(SS,RST,DI00);
  LoRa.setTxPower(20,PA_OUTPUT_PA_BOOST_PIN);
  LoRa.setSignalBandwidth(BW[0]);
  LoRa.setSpreadingFactor(SF[6]);
  LoRa.setCodingRate4(CR[0]);
  LoRa.enableCrc(); 
  if (!LoRa.begin(BAND)) {
    Serial.println("Starting LoRa failed!");
    while (1);
  }
  counter.mb[0]='O';
  counter.mb[1],'Y';
  counter.packet_id=0;
  Serial.println("Started LoRa!");
}


void loop()
{
  // send packet
  LoRa.beginPacket();
  LoRa.write((uint8_t*)&counter, sizeof(counter));
  LoRa.endPacket();

  counter.packet_id++;
  delay(5000);// wait for 5 second
}

 

 

The Receiver

To make it easier to collect data about distance, for the receiver, I have decided to add a GPS module. Since the power requirement for the GPS are contained, I will use the on board +3.3V and GND pins for powering it. The GPS needs also a serial connection for communicating with the controller (RX/TX). This can be done using a new hardware defined serial, using the pin 17 and 23 (see connections in the image below). From the software side, we need a library to manage the GPS. I have opted for TinyGPSPlus, a very helpful library which offers the functionalities I need.

 

The receiver will wait for the GPS signal to lock, then it will listen for incoming LoRa messages. The transmitter's latitude and longitude coordinates are hard-coded in the receiver, so once a new set of coordinates are read from the GPS, the distance can be correctly calculated and displayed. I also keep track of the maximum distance, and that will be displayed as well. Other information are added to the display, as the RSSI and S/N reading, to understand the strength and quality of the signal received.

 

Here is the code for the receiver:

 

#include <TinyGPS++.h>
#include <WiFi.h>
#include <SPI.h>
#include <LoRa.h>
#include <Wire.h>  
#include <HardwareSerial.h>
#include <bt.h>
#include "SSD1306.h" 

#define SCK     5    // GPIO5  -- SX127x's SCK
#define MISO    19   // GPIO19 -- SX127x's MISO
#define MOSI    27   // GPIO27 -- SX127x's MOSI
#define SS      18   // GPIO18 -- SX127x's CS
#define RST     14   // GPIO14 -- SX127x's RESET
#define DI00    26   // GPIO26 -- SX127x's IRQ(Interrupt Request)


#define GPS_TX  23  // GPIO23 -- GPS TX
#define GPS_RX  17  // GPIO17 -- GPS_RX

#define LED 25

#define BAND    433E6  //you can set band here directly,e.g. 868E6,915E6
#define PABOOST true

float BW[]={7.8E3, 10.4E3, 15.6E3, 20.8E3, 31.25E3, 41.7E3, 62.5E3, 125E3, 250E3};
int   SF[]={6,7,8,9,10,11,12};
int   CR[]={5,8};

SSD1306 display(0x3c, 4, 15);

typedef struct {
  char mb[2]={'O','Y'};
  uint16_t packet_id=0;
} msg_t;

const uint8_t GPS_REFRESH_1HZ[] = {
                          0xB5,0x62,0x06,0x08,0x06,0x00,0xE8,0x03,
                          0x01,0x00,0x01,0x00,0x01,0x39};

const uint8_t GPS_REFRESH_5HZ[] = {
                          0xB5,0x62,0x06,0x08,0x06,0x00,0xC8,0x00,
                          0x01,0x00,0x01,0x00,0xDE,0x6A,0xB5,0x62,
                          0x06,0x08,0x00,0x00,0x0E,0x30};
                          
const uint8_t GPS_REFRESH_10S[] = {
                          0xB5,0x62,0x06,0x08,0x06,0x00,0x10,0x27,
                          0x01,0x00,0x01,0x00,0x4D,0xDD,0xB5,0x62,
                          0x06,0x08,0x00,0x00,0x0E,0x30};

const uint8_t GPS_REFRESH_20S[] = {
                          0xB5,0x62,0x06,0x08,0x06,0x00,0x20,0x4E,
                          0x01,0x00,0x01,0x00,0x84,0x00,0xB5,0x62,
                          0x06,0x08,0x00,0x00,0x0E,0x30};

const uint8_t GPS_LOW_POWER[] = { 0xB5,0x62,0x06,0x04,0x04,0x00,
                                  0x00,0x00,0x08,0x00,0x16,0x74};

const uint8_t GPS_HOT_START[] = { 0xB5,0x62,0x06,0x04,0x04,0x00,
                                  0x00,0x00,0x02,0x00,0x10,0x68};
                                  
String rssi = "RSSI --";
String packSize = "--";
String packet ;

HardwareSerial *serialGPS;
TinyGPSPlus *gps;

float originLat = <put_TX_latitude_here>; // TX Antenna latitude
float originLon =<put_TX_longitude_here>; // TX Antenna longitude
unsigned long distMax = 0;
bool gps_started = false;

void setup()
{

  serialGPS = new HardwareSerial(1);  
  gps       = new TinyGPSPlus();

  WiFi.mode(WIFI_OFF);
  delay(10); 
  btStop();
  delay(10);
  
  serialGPS->begin(9600, SERIAL_8N1, GPS_TX, GPS_RX);
  delay(100);
  serialGPS->write(GPS_REFRESH_1HZ,sizeof(GPS_REFRESH_1HZ));
  delay(100);
  serialGPS->write(GPS_HOT_START,sizeof(GPS_HOT_START));
  delay(1000);

  Serial.begin(115200);
  delay(50);

  pinMode(16,OUTPUT);
  digitalWrite(16, LOW);    // set GPIO16 low to reset OLED
  delay(50); 
  digitalWrite(16, HIGH); // while OLED is running, must set GPIO16 in high、
  
  pinMode(LED,OUTPUT);
  digitalWrite(LED,LOW);
  
  display.init();
  display.flipScreenVertically();  
  display.setFont(ArialMT_Plain_10);
  display.setTextAlignment(TEXT_ALIGN_LEFT);
  
  display.clear();
  
  SPI.begin(SCK,MISO,MOSI,SS);
  LoRa.setPins(SS,RST,DI00);
  LoRa.setSignalBandwidth(BW[0]);
  LoRa.setSpreadingFactor(SF[6]);
  LoRa.setCodingRate4(CR[0]);//8
  LoRa.enableCrc();
  
  if (!LoRa.begin(BAND)) {
    display.drawString(0, 0, "Starting LoRa failed!");
    display.display();
    while (1);
  }
  display.drawString(0, 0, "LoRa Initial success!");
  display.drawString(0, 10, "Wait for data...");
  display.display();
  delay(1000);  
}

void loop()
{
  float llat,llon=0;
  gps_started=false;
  int num=0;
  do {
    while (serialGPS->available() > 0) {
        gps->encode(serialGPS->read());
    }
  
    llat=gps->location.lat();
    llon=gps->location.lng();
    gps_started = ((llat!=0) && (llon!=0));
    num+=1;
    if(num==100000){
      Serial.print("Satellites locked: ");
      Serial.println(gps->satellites.value());
      num=0;
    }
  } while(!gps_started);
  
  if(gps_started){

    uint8_t *buffer=NULL;
    unsigned long distance = TinyGPSPlus::distanceBetween(llat, llon, originLat, originLon);      
    int packetSize = LoRa.parsePacket();

    if (distance > distMax)
      distMax = distance;
  
    if (packetSize) // Only read if there is some data to read..
    {
      String recvData = "RSSI " + String(LoRa.packetRssi(), DEC) + " - SN " + String(LoRa.packetSnr(), 2);
      String info=String("S: ")+String(packetSize,DEC) + " - ID: UNKNOWN";
      String pktData="M: "+String(distMax);
  
      buffer=(uint8_t *)malloc(packetSize);
      LoRa.readBytes((uint8_t *)buffer, packetSize); 
      if(packetSize==sizeof(msg_t)){
        msg_t *data=(msg_t *)buffer;
        if((data->mb[0]=='O') && (data->mb[1]=='Y')) {
          info = String("S: ")+String(packetSize,DEC) + " - ID: " + String(data->packet_id, DEC);
          pktData += " - D: " + String(distance, DEC);
        }
        display.drawString(0 , 26, pktData);   
      }
      display.clear();
      display.drawString(0, 0, recvData);  
      display.drawString(0 , 13, info);
      display.drawString(0 , 26, pktData);
      display.display();  
    }
  
    free(buffer);

    Serial.print("Distance: ");
    Serial.println(distance);
  }
}

 

Below you can see the information on the display (S:number of bytes received - ID:message counter - M: maximum distance - D: current distance):

 

 

Lets show the boards, ready for action (image below). The video explains the boards and how the testing will be carried.To make it transportable, the receiver is powered by a battery pack.

 

 

 

Testing the antennae

With both the transmitter and the receiver code completed, time to test the antennae. The procedure is pretty simple: I will set up the transmitter using one different antenna at time, then pick the receiver and go for a walk. Once I reach the maximum distance (no more signal received) I go back to base, change antenna and do the same round again. The bonus from this testing plan is I get the exercise!

 

I live in a town, so let me start by stressing there will be lots of noise and reflections (lots of 2 or 3 storey buildings and lots of trees), so the range will be limited as result. This is one of the reason I chose the minimum bandwidth and the maximum spreading factor for configuring the LoRa communication. The transmitting antenna is always located in the same place, with the same orientation, so that all the conditions are kept the same while repeating the test.

 

The GPS is not the most precise around, so allow +/- 10m on the measure.

 

Below you can find my results:

 

AntennaMaximum distance (m)
Helical303
Molex FPC (on poly)504
Center Loaded Coil327
Small FPC347
Quarter-wave Monopole583

 

The numbers speak for themselves. I'm very impressed with the result! Looks like the poor's man antenna works quite well .

 

Conclusions

At the beginning, I was very doubtful about what would be able to achieve by building an antenna from scratch, considering the lack of appropriate equipment, and I wasn't sure I would have enjoyed doing it as much I usually enjoy taking part to RoadTests. It turns out that, after all, I have really enjoyed doing this project, and I have learned a lot along the way as well! Thank you again tariq.ahmad for sending the swag pack with the antenna kit, and for the gentle nudge that finally convinced me to take part to a Project14 contest.

 

---------------------------------------------------------------

Other articles of this series:

Building a poor man's quarter-wave 433MHz antenna: Introduction

Building a poor man’s quarter-wave 433MHz antenna: Antenna’s

Building a poor man’s quarter-wave 433MHz antenna: Comparing Antennae