RTK Rover - Getting corrections via LoRa

Hi guys,

I’m working on a project to build a RTK Rover. Due to technical limitations I am hoping to use LoRa. The current setup built is attached. I’m also posting the codes for the transmitter and the receiver so far. The receiver code is only printing what’s coming and doing nothing else yet.

To put it simple:

-I have a GPS antena L1/L2/L5 that works as a base, transmiting the correction data;

-A Heltech WiFi LoRa 32 v2 board gets the correction data through serial, separates it into individual RTCM 3 sentences and sends it over via LoRa;

-An expLoRable board receives the data via LoRa and must send it to our rover via qwiic connectors;

-Our rover consists of a SparkFun GPS-RTK2 Board - ZED-F9P (Qwiic) and a Tallysman antena (also L1/L2/L5);

-The endpoint is the SparkFun OpenLog Artemis board, that reads the GPS from another qwiic connection and logs it into a computer.

I found that working with RTCM 3 protocol is not very friendly, our base streams the bytes almost nonstop, so to build each individual sentence before sending it I made a state machine to read the serial buffer until it constructs a full sentence, sends it over via LoRa and then restarts the process for building the next sentence. I build each sentence before sending it to avoid “flooding” the LoRa byte by byte and to lower the probability of losing individual bytes. I saw this state machine technique on another forum and found it quite helpful.

So, here are the problems / doubts:

  • The communication between the boards often loses packets, either from LoRa transmitter to receiver, or even before on the serial connection to the base;

-The loss of bytes is more evident for longer sentences (over 120 bytes). When I get these sentences, the LoRa transmitter usually loses the next 1 or 2 sentences from the machine state. Also, althgough in theory, the settings I put on the transmitter should be able to send packets of around 222 bytes, anything greater than 120 bytes causes the loss of packets and also don’t come compleat on the receiver end;

-Sometimes the longer sentences even cause the LoRa boards to crash or freeze, on both ends, transmitter and receiver;

-The data that gets to the receiver needs to pass through to the GPS board through the qwiic connection, and I’d like some help to code that part, since the expLoRable boards exemples I used sent the data to a LCD screen.

I hope you guys can help me with those points. Thanks in advance!!!

Transmitter Code:

#include <Stream.h>
#include <LoRa.h>
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

//LOCAL PROTOTYPES
void readPacket(void);                                        // read and prepares buffer for LoRa sending
void printData(uint8_t dataBuffer[512], int dataSize);        // prints each individual RTCM3 message or each byte stream packet for debug
bool init_comunicacao_lora(void);                             // initializes LoRa communication

//GLOBAL VARIABLES
uint8_t rtkBuffer[512];                   // RTK receive buffer from byte stream
uint8_t sendBuffer[512];                  // LoRa send packet buffer
size_t count = 0;                         // counter for byte stream size
int idx = 0;                              // keep track of byte index until LoRa packet is complete for sending
uint16_t totalSize;                       // total size for each individual RTCM3 message
bool printPacket = false;                 // flag for printing buffer
bool preparePacket = false;               // flag for reading and preparing LoRa send buffer
const uint8_t startByte = 0xD3;           // start byte
const uint32_t timeout = 600;             // timeout - not currently in use
/* Defines for LoRa */
#define SCK_LORA           5
#define MISO_LORA          19
#define MOSI_LORA          27
#define RESET_PIN_LORA     14
#define SS_PIN_LORA        18
#define HIGH_GAIN_LORA     20  		/* dBm */
#define BAND               915E6    /* 915MHz frequency */
#define BANDWIDTH          250E3    /* 125kHz bandwidth */

// STATE MACHINE STATES
enum BUFFERSTATE
{
  WAITSTART,
  WAITLENGTH,
  WAITLENGTH2ND,
  INPROGRESS,
  COMPLETE,
};
static BUFFERSTATE rcvState = WAITSTART;  // State machine variable

void setup()
{
  Serial.begin(57600);
  while (!Serial);
  /* Try until succeed comunicating with LoRa */
  while(init_comunicacao_lora() == false);
}

bool init_comunicacao_lora(void)
{
    bool status_init = false;
    SPI.begin(SCK_LORA, MISO_LORA, MOSI_LORA, SS_PIN_LORA);           //init LoRa Module
    LoRa.setPins(SS_PIN_LORA, RESET_PIN_LORA, LORA_DEFAULT_DIO0_PIN); //set LoRa board pins
     
    if (!LoRa.begin(BAND)) 
    {
        status_init = false;
    }
    else
    {
        /* Set LoRa gain to 20dBm, which is the highest posible for reaching higher distances */
        LoRa.setTxPower(HIGH_GAIN_LORA); 
        LoRa.setSignalBandwidth(BANDWIDTH);
        LoRa.setSpreadingFactor(8);
        LoRa.setCodingRate4(5);
        LoRa.setSyncWord(0x12);
        LoRa.setPreambleLength(8);
        //LoRa.onTxDone(onTxDone);
        status_init = true;
    }
    
    return status_init;
}

//void onTxDone()
//{
//}

void printData(uint8_t dataBuffer[512], int dataSize)
{
  for (uint8_t cnt = 0; cnt < dataSize; cnt++)
  {
    if (dataBuffer[cnt] < 0x10)
    {
      Serial.print("0");
    }
    Serial.print(dataBuffer[cnt], HEX);
    Serial.print(" ");
  }
  Serial.println();
}

void readPacket(){
  // read the current byte buffer
  for (uint8_t cnt = 0; cnt < count; cnt++)
  {
    sendBuffer[idx] = rtkBuffer[cnt];
    // state machine
    switch (rcvState)
    {
      case WAITSTART:
        if (sendBuffer[idx] == startByte)
        {
          rcvState = WAITLENGTH;
          idx++;
        }
        break;
      case WAITLENGTH:
        totalSize = sendBuffer[idx];
        totalSize <<= 8;
        rcvState = WAITLENGTH2ND;
        idx++;
        break;
      case WAITLENGTH2ND:
        totalSize = totalSize | sendBuffer[idx];
        totalSize = totalSize + 0x05;
        rcvState = INPROGRESS;
        idx++;
        break;
      case INPROGRESS:
        if (idx == totalSize)
        {
          rcvState = COMPLETE;
        }
        idx++;
        break;
    }
    if (rcvState == COMPLETE)
    {
		// sends the packet
        LoRa.beginPacket();
        LoRa.write(sendBuffer, idx);
        LoRa.endPacket();
        delay(3.3*idx);

        // cleanup for next packet
        memset(sendBuffer, 0x00, idx);
        idx = 0;
        totalSize = 0x0000;
        rcvState = WAITSTART;
    }
  }
  preparePacket = false;
}

void loop()
{
  if (Serial.available())
  {
    //printPacket = true;
    preparePacket = true;
    while (Serial.available())
    {
      rtkBuffer[count++] = Serial.read();
      if (count == sizeof(rtkBuffer)) break;
    }
  }
  else
  {
    //printPacket = false;
    preparePacket = false;
  }
  if (preparePacket) readPacket();
  if (printPacket) printData(rtkBuffer, count);
  count = 0;
}

Receiver code:

// include the library
#include <RadioLib.h>

// SX1262 has the following connections:
// NSS pin:   10
// DIO1 pin:  2
// NRST pin:  3
// BUSY pin:  9
//SX1262 radio = new Module(10, 2, 3, 9);
SX1262 radio = new Module(D36, D40, D44, D39, SPI1);

// or using RadioShield
// https://github.com/jgromes/RadioShield
//SX1262 radio = RadioShield.ModuleA;

// or using CubeCell
//SX1262 radio = new Module(RADIOLIB_BUILTIN_MODULE);

void setup() {
  Serial.begin(9600);

  // initialize SX1262 with default settings
  Serial.print(F("[SX1262] Initializing ... "));
  int state = radio.begin(915.0, 250.0, 8, 5, 0x12, 20, 8, 0, false);
  if (state == RADIOLIB_ERR_NONE) {
    Serial.println(F("success!"));
  } else {
    Serial.print(F("failed, code "));
    Serial.println(state);
    while (true);
  }

  // set the function that will be called
  // when new packet is received
  radio.setDio1Action(setFlag);

  // start listening for LoRa packets
  Serial.print(F("[SX1262] Starting to listen ... "));
  state = radio.startReceive();
  if (state == RADIOLIB_ERR_NONE) {
    Serial.println(F("success!"));
  } else {
    Serial.print(F("failed, code "));
    Serial.println(state);
    while (true);
  }

  // if needed, 'listen' mode can be disabled by calling
  // any of the following methods:
  //
  // radio.standby()
  // radio.sleep()
  // radio.transmit();
  // radio.receive();
  // radio.readData();
  // radio.scanChannel();
}

// flag to indicate that a packet was received
volatile bool receivedFlag = false;

// this function is called when a complete packet
// is received by the module
// IMPORTANT: this function MUST be 'void' type
//            and MUST NOT have any arguments!
#if defined(ESP8266) || defined(ESP32)
  ICACHE_RAM_ATTR
#endif
void setFlag(void) {
  // we got a packet, set the flag
  receivedFlag = true;
}

void loop() {
  // check if the flag is set
  if(receivedFlag) {
    // reset flag
    receivedFlag = false;

    // you can read received data as an Arduino String
    byte str[512];
    uint16_t totalSize;
    int state = radio.readData(str, 0);
    totalSize = ((str[1] <<= 8) | str[2]) + 0x06;

    // you can also read received data as byte array
    /*
      byte byteArr[8];
      int state = radio.readData(byteArr, 8);
    */

    if (state == RADIOLIB_ERR_NONE) {
      // packet was successfully received
      //Serial.println(F("[SX1262] Received packet!"));

      // print data of the packet
      //Serial.print(F("[SX1262] Data:\t\t"));
      for (int i = 0; i<totalSize; i++)
      {
        if (str[i] < 0x10)
        {
          Serial.print("0");
        }
        Serial.print(str[i], HEX);
        Serial.print(" ");
      }
      Serial.println();
      memset(str, 0x00, totalSize);
      
    } else if (state == RADIOLIB_ERR_CRC_MISMATCH) {
      // packet was received, but is malformed
      Serial.println(F("CRC error!"));

    } else {
      // some other error occurred
      Serial.print(F("failed, code "));
      Serial.println(state);

    }

    // put module back to listen mode
    radio.startReceive();
  }
}