Sparkfun GPS Logger Shield w/ TinyGPSPlus MPH (speed) issues - slow to update

Hey all!

Long time lurker but first time poster. I’ve created an app I’m going to use for logging data from my motorcycle for review later.

Everything seems to be working well expect for the speed. It seems to stick to one speed for a long time and updates very, very slowly. I am trying to determine if this is an issue with my refresh rate and the speed at which TinyGPSPlus operates, or something else.

I set my refresh rate at 10Hz and am only allowing for the RMC string to speed things up.

If anyone could provide any guidance, that would be amazing!

Thanks in advance!

#include <SdFat.h>
#include <TinyGPS++.h>
#include <Wire.h>
#include "I2Cdev.h"
#include "RTIMUSettings.h"
#include "RTIMU.h"
#include "RTFusionRTQF.h"
#include "RTPressure.h"
#include "CalLib.h"
#include <EEPROM.h>
#include <math.h>

#define SD_CHIP_SELECT 53
#define ss Serial1
#define LOG_COLUMN_COUNT 8

void doSomeWork();

SdFat sdcard;
SdFile logfile;
RTIMU *imu;                                           // the IMU object
RTPressure *pressure;                                 // the pressure object
RTFusionRTQF fusion;                                  // the fusion object
RTIMUSettings settings;                               // the settings object
RTVector3 vec;                                        // the vector object

TinyGPSPlus gps;

const char *log_col_names[LOG_COLUMN_COUNT] = {
  "timer", "time", "latitude", "longitude", "mph", "roll", "pitch", "heading"
}; // log_col_names is printed at the top of the file.
unsigned long lastDisplay;
unsigned long lastRate;
int counter = 0;
const int UTC_offset = -8;   // Pacific Standard Time
char filename[15];

void setup()
{
  int errcode;

  Serial.begin(115200);

  ss.begin(9600);
  ss.println(F("$PMTK251,38400*27"));  // 38400 baud
  ss.end();
  ss.begin(38400);
  delay(5000);
  ss.println(F("$PMTK314,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*28"));     // RMC and GGA
  ss.println(F("$PMTK220,100*2F"));     // 10 Hz

  Wire.begin();

  imu = RTIMU::createIMU(&settings);                        // create the imu object
  pressure = RTPressure::createPressure(&settings);         // create the pressure sensor

  if (pressure == 0) {
    Serial.println("No pressure sensor has been configured - terminating");
    while (1) ;
  }

  Serial.print("ArduinoIMU10 starting using IMU "); Serial.print(imu->IMUName());
  Serial.print(", pressure sensor "); Serial.println(pressure->pressureName());
  if ((errcode = imu->IMUInit()) < 0) {
    Serial.print("Failed to init IMU: "); Serial.println(errcode);
  }

  if ((errcode = pressure->pressureInit()) < 0) {
    Serial.print("Failed to init pressure sensor: "); Serial.println(errcode);
  }

  if (imu->getCalibrationValid())
    Serial.println("Using compass calibration");
  else
    Serial.println("No valid compass calibration data");

  lastDisplay = lastRate = millis();
  
  // Slerp power controls the fusion and can be between 0 and 1
  // 0 means that only gyros are used, 1 means that only accels/compass are used
  // In-between gives the fusion mix.

  fusion.setSlerpPower(0.50);

  // use of sensors in the fusion algorithm can be controlled here
  // change any of these to false to disable that sensor

  fusion.setGyroEnable(true);
  fusion.setAccelEnable(true);
  fusion.setCompassEnable(true);

  delay(500);
  initializeSD();
  delay(500);
  printHeader(filename);
}


void loop()
{

  while (ss.available() > 0) {
    if (gps.encode(ss.read())) {
      doSomeWork();
    }
  }
}

void doSomeWork()
{

  unsigned long now = millis();
  unsigned long delta;
  float latestPressure;
  float latestTemperature;
  int loopCount = 1;
    
  while (imu->IMURead()) {                                // get the latest data if ready yet
    // this flushes remaining data in case we are falling behind
    if (++loopCount >= 10)
      continue;

    fusion.newIMUData(imu->getGyro(), imu->getAccel(), imu->getCompass(), imu->getTimestamp());

    vec = fusion.getFusionPose();
  }

    //Logs all the things
    logfile.open(filename, O_CREAT | O_WRITE | O_APPEND);
    logfile.print( counter );
    logfile.print(',');
    logfile.print( gps.time.hour() );
    logfile.print(':');
    logfile.print( gps.time.minute() );
    logfile.print(':');
    logfile.print( gps.time.second() );
    logfile.print(',');
    logfile.print( gps.location.lat(), 6 );
    logfile.print(',');
    logfile.print( gps.location.lng(), 6 );
    logfile.print(',');
    logfile.print( gps.speed.mph(), 1 );
    logfile.print(',');
    logfile.print(vec.x() * (180.0 / M_PI));
    logfile.print(',');
    logfile.print(vec.y() * (180.0 / M_PI));
    logfile.print(',');
    logfile.println(vec.z() * (180.0 / M_PI));
/*
  //prints all the things
  Serial.print( counter );
  Serial.print(',');
  Serial.print( gps.time.hour() );
  Serial.print(':');
  Serial.print( gps.time.minute() );
  Serial.print(':');
  Serial.print( gps.time.second() );
  Serial.print(',');
  Serial.print( gps.location.lat(), 6 );
  Serial.print(',');
  Serial.print( gps.location.lng(), 6 );
  Serial.print(',');
  Serial.print( gps.speed.mph(), 1 );
  Serial.print(',');
  Serial.print( vec.x() * (180.0 / M_PI) );
  Serial.print(',');
  Serial.print( vec.y() * (180.0 / M_PI) );
  Serial.print(',');
  Serial.println( vec.z() * (180.0 / M_PI) );
  */

  logfile.close(); // close the file
  counter++;
}

void printHeader(char f[15])
{
  if ( ! logfile.open(f, O_CREAT | O_WRITE | O_EXCL) ) {
    int i = 0;
    for (; i < LOG_COLUMN_COUNT; i++)
    {
      logfile.print(log_col_names[i]);
      if (i < LOG_COLUMN_COUNT - 1) // If it's anything but the last column
        logfile.print(','); // print a comma
      else // If it's the last column
        logfile.println(); // print a new line
    }
  }
  logfile.close(); // close the file
}

void initializeSD()
{

  Serial.print(F("\n\nInitializing SD card..."));
  if (!sdcard.begin(SD_CHIP_SELECT, SPI_FULL_SPEED)) {
    Serial.println(F("Card init. failed!"));
    while (true) {}
  }
  Serial.println(F("done."));

  Serial.print(F("Opening file..."));
  
  strcpy(filename, "GPSLOG00.CSV");
  for (uint8_t i = 0; i < 100; i++) {
    filename[6] = '0' + i / 10;
    filename[7] = '0' + i % 10;
    if (! sdcard.exists(filename)) {
      break;
    }
  }

  if ( ! logfile.open(filename, O_CREAT | O_WRITE | O_EXCL) ) {
    Serial.print("Couldnt create ");
    Serial.println(filename);
    while (true) {}
  }
  Serial.print(" Writing to ");
  Serial.println(filename);
}

Well, I see you are opening a log file to append data to it. How often do you do this? Do you realize each time the library must READ the entire file to find the end, and then add the new data to it.

Paul

How often do you do this?

It's doing this 10x per second based on the refresh rate of the GPS.

Again, everything else is working great. The IMU is reporting correctly and the time, lat, long are all reporting correctly... it's just an issue with the speed from the TinyGPSPlus library...

Do you think it could be the library itself being slow in encoding the speed?

Thanks!!!

If anyone could provide any guidance, that would be amazing!

What Arduino are you using?

it’s just an issue with the speed from the TinyGPSPlus library…

It looks like you started with a NeoGPS example. The NeoGPS library is almost twice as fast as other libraries, so why did you switch from NeoGPS?

I think Paul_KD7HB is correct: don’t open and close the file at 10Hz. The NeoGPS example, NMEASDlog.ino shows how to reliably log GPS information at high data rates. You may need to use NeoHWSerial to avoid losing GPS data while the SD card is writing (depends on which Arduino you are using).

Also, you are writing GPS fields that may not be valid. If the GPS device does not know the current time, lat/lon or speed, it does not send anything to the Arduino. NeoGPS provides validity flags that let you know when those fields are really valid.

I’m not sure why you’re using an SdFile for the logfile, instead of a File. If you just open a File once, in initializeSD, that would save a lot of processing time. You can “flush” the file periodically to avoid losing data when you remove the card. I would suggest implementing some way to close the file: either watch for a character to to become availble from Serial (send from the Serial Monitor window), or add a push button that you can press.

It looks like you might want to shift the time to PST from UTC. The NeoGPS example NMEAtimezone.ino shows how to do that, and how to accommodate DST changes (for the US and EU).

I have attached a NeoGPS version of your sketch, with all those suggested changes. If you want to try it, NeoGPS is available from the Arduino Library Manager, under the menu Sketch-> Include Library-> Manage Libraries.

Your original sketch uses 26968 bytes of program space and 1716 bytes of RAM.
The NeoGPS version uses 24914 bytes of program space and 1623 bytes of RAM, a significant savings.

You may also be interested in another motorcycle logging project by powergravity. Long thread starts here, last version of his software here.

Cheers,
/dev

crshovrd.ino (8.35 KB)

Hey dev!!

I'm using an Arduino Mega for this project with the GPS on Serial1 and the IMU on the I2C bus. Those are the only sensors connected.

I switched back to TinyGPSPlus as a trial as I already had some nice working code with it already...path of least resistance you know :slight_smile:

Thanks so much for the info on SDfat and my implementation. This library is new to me and I'm still working my way through this. Your insights are insanely helpful!

You are correct about the time needing to be shifted, I appreciate that!

I am about to have a look at what you've put together and will report back soon enough!!!!

Thanks so much for your help!!!

Alright, I reviewed the changes you've made and they all look really interesting and well thought out.

Based on the if statement in the loop for the .flush() command, it appears it will flush after every 15 iterations? Is that the case? Was there a reason for the hex there or is it just to be fancy?

Also, the if ((counter & 0x0F)==0) logfile.flush(); command simply pushes the last 15 lines out of the buffer and in on to the card, is that correct? This way, we are not constantly opening and closing the file but simply buffering a number of lines and then pushing them to the SD card all at once. Is that correct?

It appears you've also put in a lot of checks during the buffering to prevent false items from being written, this seems logical.

This is actually for a motorcycle datalogger for track racing so I will probably implement a push button system that shuts the logging down before removing power (turning off the bike).

I will try this out tomorrow while driving in to the office and let you know the results!

Also, one other question: would it be more efficient to store everything in to one giant string and then write it to the file or is it fine to write it piecemeal like I am now?

Thanks!!!

crshovrd:
would it be more efficient to store everything in to one giant string

No, never. Writing the pieces to a buffer, then copying the buffer to Serial all at once are unnecessary steps. Just write the pieces to Serial and skip the buffering step. This saves RAM and execution time.

Based on the if statement in the loop for the .flush() command, it appears it will flush after every 15 iterations? Is that the case? Was there a reason for the hex there or is it just to be fancy?

It will flush once every 16 iterations. And’ing with 15 (a fancy trick) is the same as % 16 (mod’ing?). The & result will cycle through 0, 1, 2, 3, … 14, 15, 0, … etc. So it only equals 0 once every 16 iterations.

You can use % 10, but it’s a lot slower. You could use a timestamp:

    // approx. once per second (Arduino millis drifts agains GPS clock)
    if (millis() - lastFlush > 1000) {
      lastFlush = millis();
      logfile.flush();
    }

… or the fractional GPS seconds

    // *Exactly* once per second, probably on the hh:mm:ss.00 hundredths
    if (fix.valid.time && (fix.dateTime_cs < 10)) {
      logfile.flush();
    }

Well -dev,

You're a genius sir! Switching back to your library and flushing the log every 16 iterations resolved the stuck speed issue.

I added a few more if statements within the logging to add some zeros to the time.

My next issue is unfortunately with the DashWare application mapping my GPS coordinates in a line as opposed to an actual path... but that's not for this forum!

I would say we can consider this case closed. Thank you for your wonderful help and wonderful library!