GPS Speed Latency

I have an Adafruit Ultimate GPS wired up to an arduino mega. When I run this code, the GPS generally gives accurate data, but there can be a 10-15 second delay on speed updates. I am using this setup to measure speed, so it's essential that it can detect changes and display them with low latency. Any ideas? (p.s. commented out some code that is not essential for speed measurement, also I'm transmitting this via an Adafruit LoRa 433MHz radio, but it's not the radio causing latency, I've tested without)



#include <Adafruit_GPS.h>
#include <SoftwareSerial.h>

// you can change the pin numbers to match your wiring:
SoftwareSerial mySerial(10, 9);
Adafruit_GPS GPS(&mySerial);

#define GPSECHO  true
#include <SPI.h>
#include <RH_RF95.h>

#define RFM95_CS 4
#define RFM95_RST 2
#define RFM95_INT 3

// Change to 434.0 or other frequency, must match RX's freq!
#define RF95_FREQ 434.0

// Singleton instance of the radio driver
RH_RF95 rf95(RFM95_CS, RFM95_INT);

void setup()
{

  // connect at 115200 so we can read the GPS fast enough and echo without dropping chars
  // also spit it out
  Serial.begin(115200);
  delay(1000);
  Serial.println("Adafruit GPS library basic parsing test!");

  // 9600 NMEA is the default baud rate for Adafruit MTK GPS's- some use 4800
  GPS.begin(9600);

  // uncomment this line to turn on RMC (recommended minimum) and GGA (fix data) including altitude
  GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA);


  // Set the update rate
  GPS.sendCommand(PMTK_SET_NMEA_UPDATE_5HZ);   
  // For the parsing code to work nicely and have time to sort thru the data, and
  // print it out we don't suggest using anything higher than 1 Hz

  // Request updates on antenna status, comment out to keep quiet
  GPS.sendCommand(PGCMD_ANTENNA);

  delay(1000);
  // Ask for firmware version
  mySerial.println(PMTK_Q_RELEASE);

  pinMode(RFM95_RST, OUTPUT);
  digitalWrite(RFM95_RST, HIGH);

  while (!Serial);
  delay(100);

  Serial.println("Arduino LoRa TX Test!");

  // manual reset
  digitalWrite(RFM95_RST, LOW);
  delay(10);
  digitalWrite(RFM95_RST, HIGH);
  delay(10);

  while (!rf95.init()) {
    Serial.println("LoRa radio init failed");
    delay(500);
  }
  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);

}




String fix = "--";
String qual = "--";
String lat = "--";
String lon = "--";
String speed = "--";
String angle = "--";
String altitude = "--";
String satellites = "--";
String antenna = "--";
String b1 = "--";
String b2 = "--";
String b3 = "--";
String b4 = "--";

uint32_t timer = millis();
void loop()                     // run over and over again
{

  char c = GPS.read();
  if (GPS.newNMEAreceived()) {
    char *rawNMEA = GPS.lastNMEA();
    Serial.println(rawNMEA); // Print the raw NMEA sentence for debugging
    if (!GPS.parse(rawNMEA))
    {
      return;
    }
  }

  if (millis() - timer > 200) {
    timer = millis(); // reset the timer
    //Serial.print("Fix: "); Serial.print((int)GPS.fix);
    //Serial.print(" quality: "); Serial.println((int)GPS.fixquality);
    fix = String(GPS.fix);
    qual = String(GPS.fixquality);
    if (GPS.fix) {
      //Serial.print("Location: ");
      //Serial.print(GPS.latitude, 4); Serial.print(GPS.lat);
      //Serial.print(", ");
      //Serial.print(GPS.longitude, 4); Serial.println(GPS.lon);

      //Serial.print("Speed (knots): "); Serial.println(GPS.speed);
      //Serial.print("Angle: "); Serial.println(GPS.angle);
      //Serial.print("Altitude: "); Serial.println(GPS.altitude);
      //Serial.print("Satellites: "); Serial.println((int)GPS.satellites);
      //Serial.print("Antenna status: "); Serial.println((int)GPS.antenna);


      //lat = String(GPS.latitude) + String(GPS.lat);
      //lon = String(GPS.longitude) + String(GPS.lon);
      speed = String(GPS.speed);
      //angle = String(GPS.angle);
      //altitude = String(GPS.altitude);
      satellites = String(GPS.satellites);
      //antenna = String(GPS.antenna);
    }
    float then = millis();
    //String radiopacket = lat + " , " + lon + " , " + speed + " , " + angle + " , " + altitude + " , " + fix + " , " + qual  + " , " + satellites + " , " + b1  + " , " + b2  + " , " + b3  + " , " + b4  + " , ";
    String radiopacket = speed + " , " + fix + " , " + qual  + " , " + satellites + " , " + b1  + " , " + b2  + " , " + b3  + " , " + b4  + " , ";
    Serial.println(radiopacket);
    Serial.println(speed);
    const char *radiopacketCharArray = radiopacket.c_str();
    rf95.send((uint8_t *)radiopacketCharArray, strlen(radiopacketCharArray));
    rf95.waitPacketSent();
    Serial.println((millis() - then));
    
    
  }
  
}

At a 5Hz update rate, you'll want to minimize the number of different NMEA sentences being sent. You could probably get by w/o GCGGA and just send GPRMC (which has the speed field you say you want). Also, you could try up'ing your GPS serial speed to get the sentences through faster.

But you'll likely still see instances where the GPS will seem to miss speed or direction changes for a few seconds due to a bad constellation arrangement or lack of sky visibility. Some firmware seems to be programmed to "carry on", likely in the hope that the signal degradation will only be momentary.

Since you are using a Mega, it would be much better to use one of the hardware serials instead of softwareSerial.

Are you reliably seeing all the NMEA sentences in the serial monitor? Are any of the sentences corrupt?

1 Like

I'll look into hardware serial. Basically all of the sentences are fine, usually the first one is a bit funky but the rest print normally when I uncomment the line to print them.

I'll change it to RMCONLY. Do you think the baud rate could cause such a huge delay (10+ seconds)?

That long would surprise me and have me looking for another problem. I missed that you were using SoftwareSerial to talk with the GPS. Switching to a hardware serial port won't hurt; might as well use one since you've got them.

Do you have the serial monitor set to show timestamps? I would be concerned you might not be calling GPS.read() often enough. I have not looked at the Adafruit library, but since you are only printing the NMEA sentence when newNMEAreceived() is true, you may never see corrupt sentences.

Definitely use hardware serial ports. You have four of them and SoftwareSerial has absolutely dismal performance.

Try a different GPS library. The Adafruit GPS library is ancient bloatware and although I've never used it, I've read that it has some serious drawbacks.

NeoGPS is the smallest, fastest and most configurable, and TinyGPSPlus is also very good and quite popular.

Finally, get rid of all the Strings. The String class is a time bomb that causes memory problems and crashes on AVR-based Arduinos.

Would you expect these other libraries to work well with an Adafruit device? Surely their own library would be optimized for use with it. Also I'm a bit of a noob so I'm not sure how well I'd fare trying to use a different library if it's quite a process.

I'm calling GPS.read() in loop with no delay, so I'd have thought that would be as often as possible. I could go in literally a few minutes and test hardwareserial just echoing NMEA sentences no parsing whatsoever and see the results.

Switched over to hardware and currently just echoing raw NMEA sentences. I'll test rates just doing this

Also might I ask what the issues are with strings? I'm just using them to transmit the data using the radio.

Okay I've got this example code, and I've just changed it to RMCONLY and 5Hz. I might go test this an see how the results go, and if they work I'll re-incorporate the radio.

#include <Adafruit_GPS.h>

// what's the name of the hardware serial port?
#define GPSSerial Serial1

// Connect to the GPS on the hardware port
Adafruit_GPS GPS(&GPSSerial);

// Set GPSECHO to 'false' to turn off echoing the GPS data to the Serial console
// Set to 'true' if you want to debug and listen to the raw GPS sentences
#define GPSECHO false

uint32_t timer = millis();


void setup()
{
  //while (!Serial);  // uncomment to have the sketch wait until Serial is ready

  // connect at 115200 so we can read the GPS fast enough and echo without dropping chars
  // also spit it out
  Serial.begin(115200);
  Serial.println("Adafruit GPS library basic parsing test!");

  // 9600 NMEA is the default baud rate for Adafruit MTK GPS's- some use 4800
  GPS.begin(9600);
  // uncomment this line to turn on RMC (recommended minimum) and GGA (fix data) including altitude
  GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCONLY);
  // uncomment this line to turn on only the "minimum recommended" data
  //GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCONLY);
  // For parsing data, we don't suggest using anything but either RMC only or RMC+GGA since
  // the parser doesn't care about other sentences at this time
  // Set the update rate
  GPS.sendCommand(PMTK_SET_NMEA_UPDATE_5HZ); // 1 Hz update rate
  // For the parsing code to work nicely and have time to sort thru the data, and
  // print it out we don't suggest using anything higher than 1 Hz

  // Request updates on antenna status, comment out to keep quiet
  GPS.sendCommand(PGCMD_ANTENNA);

  delay(1000);

  // Ask for firmware version
  GPSSerial.println(PMTK_Q_RELEASE);
}

void loop() // run over and over again
{
  // read data from the GPS in the 'main loop'
  char c = GPS.read();
  // if you want to debug, this is a good time to do it!
  if (GPSECHO)
    if (c) Serial.print(c);
  // if a sentence is received, we can check the checksum, parse it...
  if (GPS.newNMEAreceived()) {
    // a tricky thing here is if we print the NMEA sentence, or data
    // we end up not listening and catching other sentences!
    // so be very wary if using OUTPUT_ALLDATA and trying to print out data
    Serial.print(GPS.lastNMEA()); // this also sets the newNMEAreceived() flag to false
    if (!GPS.parse(GPS.lastNMEA())) // this also sets the newNMEAreceived() flag to false
      return; // we can fail to parse a sentence in which case we should just wait for another
  }

  // approximately every 2 seconds or so, print out the current stats
  if (millis() - timer > 200) {
    timer = millis(); // reset the timer

    if (GPS.fix) {
      Serial.print("Speed (knots): "); Serial.println(GPS.speed);
    }
  }
}

Adafruit libraries are the epitome of bloatware. It apparently never occurred to Adafruit to optimize any of their software.

The Adafruit Ultimate GPS is just a stock GPS device that sends data over UART serial. Both NeoGPS and TinyGPSPlus work fine with it.

Also might I ask what the issues are with Strings?

As I mentioned, Strings cause program crashes and memory problems. Your code WILL crash at some point, if you use them, and Strings are never necessary.

Use C-strings, sprintf() and <string.h> functions instead. It will be some work to switch over, but the advantage is that your program won't crash.

1 Like

Never heard of these, but I'll look into em, and replace the strings in my code (program crash = bad)

EDIT: also does the library being bloatware matter? That just means it's a large file most of which is garbage right? On the other hand if it works poorly then I'll go find replacements.

It has many other problems, according to reports I've seen.

These have fewer:

I've been using a Neo 6M GPS receiver as a speedometer for some time. I've always seen a lag between the actual speed and the displayed speed as produced by my code. It's more like 5 seconds lag rather than your 15 seconds (operating the neo at 1Hz).

It's as if there's a buffer somewhere that stores the speed values -- but there isn't any such buffer. I view it as a characteristic of the neo that I can't control and accept it. It's not a significant issue for my use.

Okay. I tested using the below code, and it worked really nicely! Didn't feel like much of a noticeable delay. I'm going to save this code and re-introduce the radio, and see how it works.

#include <Adafruit_GPS.h>

// what's the name of the hardware serial port?
#define GPSSerial Serial1

// Connect to the GPS on the hardware port
Adafruit_GPS GPS(&GPSSerial);

// Set GPSECHO to 'false' to turn off echoing the GPS data to the Serial console
// Set to 'true' if you want to debug and listen to the raw GPS sentences
#define GPSECHO false

uint32_t timer = millis();


void setup()
{
  //while (!Serial);  // uncomment to have the sketch wait until Serial is ready

  // connect at 115200 so we can read the GPS fast enough and echo without dropping chars
  // also spit it out
  Serial.begin(115200);
  Serial.println("Adafruit GPS library basic parsing test!");

  // 9600 NMEA is the default baud rate for Adafruit MTK GPS's- some use 4800
  GPS.begin(9600);
  // uncomment this line to turn on RMC (recommended minimum) and GGA (fix data) including altitude
  GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCONLY);
  // uncomment this line to turn on only the "minimum recommended" data
  //GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCONLY);
  // For parsing data, we don't suggest using anything but either RMC only or RMC+GGA since
  // the parser doesn't care about other sentences at this time
  // Set the update rate
  GPS.sendCommand(PMTK_SET_NMEA_UPDATE_5HZ); // 1 Hz update rate
  // For the parsing code to work nicely and have time to sort thru the data, and
  // print it out we don't suggest using anything higher than 1 Hz

  // Request updates on antenna status, comment out to keep quiet
  GPS.sendCommand(PGCMD_ANTENNA);

  delay(1000);

  // Ask for firmware version
  GPSSerial.println(PMTK_Q_RELEASE);
}

void loop() // run over and over again
{
  // read data from the GPS in the 'main loop'
  char c = GPS.read();
  // if you want to debug, this is a good time to do it!
  if (GPSECHO)
    if (c) Serial.print(c);
  // if a sentence is received, we can check the checksum, parse it...
  if (GPS.newNMEAreceived()) {
    // a tricky thing here is if we print the NMEA sentence, or data
    // we end up not listening and catching other sentences!
    // so be very wary if using OUTPUT_ALLDATA and trying to print out data
    //Serial.print(GPS.lastNMEA()); // this also sets the newNMEAreceived() flag to false
    if (!GPS.parse(GPS.lastNMEA())) // this also sets the newNMEAreceived() flag to false
      return; // we can fail to parse a sentence in which case we should just wait for another
  }

  // approximately every 2 seconds or so, print out the current stats
  if (millis() - timer > 200) {
    timer = millis(); // reset the timer

    if (GPS.fix) {
      Serial.print("Speed (km/h est): "); Serial.println(GPS.speed *1.85);
    }
  }
}

I would be interested in knowing how many milliseconds this takes to execute. I'm not sure why you would need waitPacketSent() at this point, it will be at least 200mS before you send the next packet, and if it takes that long the send() function calls waitPacketSent() to ensure the previous message is sent before sending the new message.
You need to be careful of anything that causes delays, at 9600 baud the GPS can overflow the serial buffer in a little over 65mS.

    float then = millis();
    String radiopacket = speed + " , " + fix + " , " + qual  + " , " + satellites + " , " + b1  + " , " + b2  + " , " + b3  + " , " + b4  + " , ";
    Serial.println(radiopacket);
    Serial.println(speed);
    const char *radiopacketCharArray = radiopacket.c_str();
    rf95.send((uint8_t *)radiopacketCharArray, strlen(radiopacketCharArray));
    rf95.waitPacketSent();
    Serial.println((millis() - then));

Well. Currently this isn't actually working. The GPS light would indicate a fix, but the transmitted data would just be series of "--". As soon as I reuploaded old code with no radio, it worked.

New code (that doesn't work. yet.):

#include <Adafruit_GPS.h>

// what's the name of the hardware serial port?
#define GPSSerial Serial1

// Connect to the GPS on the hardware port
Adafruit_GPS GPS(&GPSSerial);

// Set GPSECHO to 'false' to turn off echoing the GPS data to the Serial console
// Set to 'true' if you want to debug and listen to the raw GPS sentences
#define GPSECHO false
#include <SPI.h>
#include <RH_RF95.h>

#define RFM95_CS 4
#define RFM95_RST 2
#define RFM95_INT 3

// Change to 434.0 or other frequency, must match RX's freq!
#define RF95_FREQ 434.0

// Singleton instance of the radio driver
RH_RF95 rf95(RFM95_CS, RFM95_INT);

uint32_t timer = millis();


void setup()
{
  //while (!Serial);  // uncomment to have the sketch wait until Serial is ready

  // connect at 115200 so we can read the GPS fast enough and echo without dropping chars
  // also spit it out
  Serial.begin(115200);
  Serial.println("Adafruit GPS library basic parsing test!");

  // 9600 NMEA is the default baud rate for Adafruit MTK GPS's- some use 4800
  GPS.begin(9600);
  // uncomment this line to turn on RMC (recommended minimum) and GGA (fix data) including altitude
  GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCONLY);
  // uncomment this line to turn on only the "minimum recommended" data
  //GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCONLY);
  // For parsing data, we don't suggest using anything but either RMC only or RMC+GGA since
  // the parser doesn't care about other sentences at this time
  // Set the update rate
  GPS.sendCommand(PMTK_SET_NMEA_UPDATE_5HZ); // 1 Hz update rate
  // For the parsing code to work nicely and have time to sort thru the data, and
  // print it out we don't suggest using anything higher than 1 Hz

  // Request updates on antenna status, comment out to keep quiet
  GPS.sendCommand(PGCMD_ANTENNA);

  delay(1000);

  // Ask for firmware version
  GPSSerial.println(PMTK_Q_RELEASE);
  pinMode(RFM95_RST, OUTPUT);
  digitalWrite(RFM95_RST, HIGH);

  while (!Serial);
  delay(100);

  Serial.println("Arduino LoRa TX Test!");

  // manual reset
  digitalWrite(RFM95_RST, LOW);
  delay(10);
  digitalWrite(RFM95_RST, HIGH);
  delay(10);

  while (!rf95.init()) {
    Serial.println("LoRa radio init failed");
    delay(500);
  }
  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);

}
char speed[5] = "--";
char b1[5] = "--";
char b2[5] = "--";
char b3[5] = "--";
char b4[5] = "--";
void loop() // run over and over again
{
  // read data from the GPS in the 'main loop'
  char c = GPS.read();
  // if you want to debug, this is a good time to do it!
  if (GPSECHO)
    if (c) Serial.print(c);
  // if a sentence is received, we can check the checksum, parse it...
  if (GPS.newNMEAreceived()) {
    // a tricky thing here is if we print the NMEA sentence, or data
    // we end up not listening and catching other sentences!
    // so be very wary if using OUTPUT_ALLDATA and trying to print out data
    //Serial.print(GPS.lastNMEA()); // this also sets the newNMEAreceived() flag to false
    if (!GPS.parse(GPS.lastNMEA())) // this also sets the newNMEAreceived() flag to false
      return; // we can fail to parse a sentence in which case we should just wait for another
  }

  // approximately every 2 seconds or so, print out the current stats
  if (millis() - timer > 200) {
    timer = millis(); // reset the timer

    if (GPS.fix) {
      snprintf(speed, sizeof(speed), "%.2f", GPS.speed);
    }
    float then = millis();
    char radiopacket[100];
    snprintf(radiopacket, sizeof(radiopacket), "%s, %s, %s, %s, %s, ", speed, b1, b2, b3, b4);
    Serial.println(radiopacket);
    const char *radiopacketCharArray = radiopacket;
    rf95.send((uint8_t *)radiopacketCharArray, strlen(radiopacketCharArray));
    rf95.waitPacketSent();
    //Serial.println((millis() - then));
  }
  
}