GPS Datalogger

Hello all,

I made a GPS datalogger for some research I am doing based off this. I wanted to show you all, so that, if interested, you could critique my code and offer suggestions/improvements.

Hardware:
Arduino Uno
uSD shield
GPS shield
EM-506 receiver
Murata 5V regulator

What it does: saves gps data to uSD as text files with the date as the name. I put these in trucks and equipment as they operate for up to several days at a time.

Possible improvements that I have thought of:
LED on A0 does not stop blinking if uSD is ejected while powered on
Code does not save a new file at midnight for the next day
Saves a datapoint every second, this could be reduced to every 5 seconds

The possible improvements are things that aren’t necessary but could really improve the operation of my datalogger. These improvements aren’t really things I know how to add in the code yet; if you have suggestions, feel free to let me know. If there are any critical changes I would need to make to the code, feel free to offer them. Thanks all!

// Include libraries
#include <SoftwareSerial.h>
#include <TinyGPS.h>
#include <SD.h>
#include <SPI.h>

// Define variables that will be used
float latitude, longitude, gpsaltitude, gpscourse, gpsspeed;
int year;
byte month, day, hour, minute, second, hundredths;
// Character used to name file on uSD card
char Name[17]; 
// And counter variables (flags)
int GPS_count=1;
int file_init=0;
// LED pin
int led=A0;


// Chip Select (CS) pin on SD shield is pin 8
const int chipSelect=8;

// The GPS TX and RX pins are 2 and 3, respectively. However,
// these pins are needed elsewhere.  Therefore, they will be 
// ported to Arduino DI 9 and 10.
#define RXPIN 9
#define TXPIN 10

//EM-506 default baud rate is 4800
#define GPSBAUD 4800

// Create an instance of the TinyGPS object
TinyGPS gps;

// Initialize the SoftwareSerial library to the pins you defined above.
// This tells the Arduino which pins to read serial.
SoftwareSerial uart_gps(RXPIN, TXPIN);

// This is where you declare prototypes for the functions that will be 
// using the TinyGPS library.
void getgps(TinyGPS &gps);

// In the setup function, you need to initialize two serial ports; the 
// standard hardware serial port (Serial()) to communicate with your 
// terminal program an another serial port (NewSoftSerial()) for your 
// GPS.
void setup()
{
  // This is the serial rate for your terminal program. It must be this 
  // fast because we need to print everything before a new sentence 
  // comes in. If you slow it down, the messages might not be valid and 
  // you will likely get checksum errors.
  Serial.begin(115200);
    // Sets baud rate of your GPS
  uart_gps.begin(GPSBAUD);
    
  //  Set chipSelect pin to output (pin 8)
  pinMode(chipSelect, OUTPUT);
  
  //  LED pin to output (pin A0)
  pinMode(led, OUTPUT);
  
  Serial.println("");
  Serial.println("  GPS Shield and SD Shield Datalogger   ");
  Serial.println("     ...INITIALIZING SD CARD...         ");
  Serial.println();
  
  // Check presence of uSD card, and can be initialized:
  if (!SD.begin(chipSelect)) {
    Serial.println("  **Card failed, or not present**  ");
    Serial.println();
    Serial.println();
  }
  else {
  Serial.println("        **CARD INITIALIZED!**           ");
  Serial.println();
  Serial.println("     ...waiting for GPS lock...           ");
  Serial.println();
  Serial.println();
  }
}

// This is the main loop of the code. All it does is check for data on 
// the RX pin of the ardiuno, makes sure the data is valid NMEA sentences, 
// then jumps to the getgps() function.
void loop()
{
  while(uart_gps.available())     // While there is data on the RX pin...
  {
      int c = uart_gps.read();    // load the data into a variable...
      if(gps.encode(c))           // if there is a new valid sentence...
      {
        getgps(gps);              // then grab the data.
        write_SD();               // write it to uSD card.
      }
  }
}

// The getgps function will get and print the values we want.
void getgps(TinyGPS &gps)
{
  // To get all of the data into variables that you can use in your code, 
  // all you need to do is define variables and query the object for the 
  // data. To see the complete list of functions see keywords.txt file in 
  // the TinyGPS and NewSoftSerial libs.
  
  // Assign variables
  gpsaltitude=gps.f_altitude();
  gpscourse=gps.f_course();
  gpsspeed=gps.f_speed_kmph();
  // Print GPS counter
  Serial.print("Point #: ");
  Serial.println(GPS_count);
  
  // Function for latitude and longitude
  gps.f_get_position(&latitude, &longitude);
  Serial.print("Lat/Long:     "); 
  Serial.print(latitude,5); 
  Serial.print(", "); 
  Serial.println(longitude,5);
  
  // Date and time
  gps.crack_datetime(&year,&month,&day,&hour,&minute,&second,&hundredths);
  Serial.print("Date:         "); 
  Serial.print(month, DEC);        Serial.print("/"); 
  Serial.print(day, DEC);          Serial.print("/"); 
  Serial.println(year);
  Serial.print("Time:         "); 
  Serial.print(hour, DEC);         Serial.print(":"); 
  Serial.print(minute, DEC);       Serial.print(":"); 
  Serial.print(second, DEC);       Serial.print("."); 
  Serial.println(hundredths, DEC); 
  
  // Elevation, course and speed
  Serial.print("Altitude (m): ");  Serial.println(gpsaltitude);  
  Serial.print("Course (deg): ");  Serial.println(gpscourse); 
  Serial.print("Speed (kmph): ");  Serial.println(gpsspeed);
  Serial.println();
  
  // Counter increases once per loop iteration.
  GPS_count++;
   
}




// Write_SD takes care of saving data to uSD card.
void write_SD()
{
  
  //  Change global variables to local, used for naming uSD card
  int lmonth=month;
  int lday=day;
  
  // If year is valid and file has not been saved yet, save a file     
  if (year >= 2015 && file_init==0)
  {
    //Build a file name string from local variables
    sprintf(Name, "%02d%02d.txt", lmonth, lday);
    Serial.println(Name);  //  Print serial name to check
        
    // Counter increases so that filename is only changed once
    file_init++;
  }
  
  // If filesave is valid, write to it:
  if (file_init > 0)  
  {
    // Write to uSD card
    File dataFile = SD.open(Name, FILE_WRITE);
    
    // Error
    if (!dataFile)
    {
      Serial.println("Error opening datalog.txt");
      return;
    }
    
    // GPS counter
    dataFile.print(GPS_count);        dataFile.print(",");
    // Date
    dataFile.print(year);             dataFile.print(",");
    dataFile.print(month, DEC);       dataFile.print(",");
    dataFile.print(day, DEC);         dataFile.print(",");
    //Time
    dataFile.print(hour, DEC);        dataFile.print(",");
    dataFile.print(minute, DEC);      dataFile.print(",");
    dataFile.print(second, DEC);      dataFile.print(",");
    //Location
    dataFile.print(latitude, DEC);    dataFile.print(",");
    dataFile.print(longitude, DEC);   dataFile.print(",");
    //Altitude, speed, heading
    dataFile.print(gpsaltitude, DEC); dataFile.print(",");
    dataFile.print(gpscourse, DEC);   dataFile.print(",");
    dataFile.print(gpsspeed, DEC);
    dataFile.println();
    dataFile.close();   //close file 
    
    // Blink an LED to indicate 
    digitalWrite(led, HIGH);   // turn the LED on
    delay(500);               // wait for a second
    digitalWrite(led, LOW);    // turn the LED off 
    
  }
      
}

Not critical but there are a number of variables, such as year, that could be bytes rather than ints.

 if (year >= 2015 && file_init==0)

How about logging the fact if this test should fail for any reason ? Come to think of it, file_init is incremented but never seems to be reset to zero so this test will only succeed once. How is a new file created for the next day ? What have I missed ?

 Serial.println("        **CARD INITIALIZED!**           ");

etc, etc
Do yourself a favour and use the F() macro to save memory.

 Serial.println(F("        **CARD INITIALIZED!**           "));

float latitude, longitude, gpsaltitude, gpscourse, gpsspeed;

Do latitude and longitude in the program differ from that of the gps? Why do some names have gps and not others?

// Character used to name file on uSD card
char Name[17];

To hold a name in 8.3 format?

       getgps(gps);              // then grab the data.

You've already got the data. Now, it's time to parse the data.

 // Counter increases once per loop iteration.
  GPS_count++;

No, it doesn't.

 //  Change global variables to local, used for naming uSD card
  int lmonth=month;
  int lday=day;

Why is this necessary?

   dataFile.print(year);             dataFile.print(",");
    dataFile.print(month, DEC);       dataFile.print(",");
    dataFile.print(day, DEC);         dataFile.print(",");

As opposed to OCT or HEX? Why is it necessary to specify the default base for some variables?

   dataFile.print(latitude, DEC);    dataFile.print(",");
    dataFile.print(longitude, DEC);   dataFile.print(",");

The second argument does something different for floats. Printing a float to 10 decimals places does NOT improve the accuracy of the float.

   // Blink an LED to indicate 
    digitalWrite(led, HIGH);   // turn the LED on
    delay(500);               // wait for a second
    digitalWrite(led, LOW);    // turn the LED off

A half second delay() when the GPS sends data every second is probably not a good idea.

Not critical but there are a number of variables, such as year, that could be bytes rather than ints.

Kind of hard to fit 2015 in a byte, eh?

PaulS:
Kind of hard to fit 2015 in a byte, eh?

I had in mind using just 2 digits for the year unless the program will be used in the next century. Still better to be future proof though I suppose.

This is the code I came up with to fix some of the issues.

I think you guys had some really good suggestions, and also some good questions. Unfortunately I didn’t know the answers to all of them.

Code: [Select]
dataFile.print(year); dataFile.print(",");
dataFile.print(month, DEC); dataFile.print(",");
dataFile.print(day, DEC); dataFile.print(",");

As opposed to OCT or HEX? Why is it necessary to specify the default base for some variables?

If I don’t specify DEC, it doesn’t save the data. It will just save a “0” in place of whatever the date should be.

Code: [Select]
// Blink an LED to indicate
digitalWrite(led, HIGH); // turn the LED on
delay(500); // wait for a second
digitalWrite(led, LOW); // turn the LED off

A half second delay() when the GPS sends data every second is probably not a good idea.

You have me concerned here. What’s wrong with a half second delay? I have been getting data without missing any points so I think it’s working.

If you notice, I now have the file_init to reset to 0 when a “new day” is seen. This is set at 5AM GMT, which is midnight CST.

If you have any other concerns or suggestions for the code, I would love to hear them. Thanks for the help so far.

// Include libraries
#include <SoftwareSerial.h>
#include <TinyGPS.h>
#include <SD.h>
#include <SPI.h>

// Define variables that will be used
float latitude, longitude, gpsaltitude, gpscourse, gpsspeed;
int year;
byte month, day, hour, minute, second, hundredths;
// Character used to name file on uSD card
char Name[17]; 
// And counter variables (flags)
int GPS_count=1;
int file_init=0;
// LED pin
int led=A0;

// Chip Select (CS) pin on SD shield is pin 8
const int chipSelect=8;

// The GPS TX and RX pins are 2 and 3, respectively. However,
// these pins are needed elsewhere.  Therefore, they will be 
// ported to Arduino DI 9 and 10.
#define RXPIN 9
#define TXPIN 10

//EM-506 default baud rate is 4800
#define GPSBAUD 4800

// Create an instance of the TinyGPS object
TinyGPS gps;

// Initialize the SoftwareSerial library to the pins you defined above.
// This tells the Arduino which pins to read serial.
SoftwareSerial uart_gps(RXPIN, TXPIN);

// This is where you declare prototypes for the functions that will be 
// using the TinyGPS library.
void getgps(TinyGPS &gps);

// In the setup function, you need to initialize two serial ports; the 
// standard hardware serial port (Serial()) to communicate with your 
// terminal program an another serial port (NewSoftSerial()) for your 
// GPS.
void setup()
{
  // This is the serial rate for your terminal program. It must be this 
  // fast because we need to print everything before a new sentence 
  // comes in. If you slow it down, the messages might not be valid and 
  // you will likely get checksum errors.
  Serial.begin(115200);
    // Sets baud rate of your GPS
  uart_gps.begin(GPSBAUD);
    
  //  Set chipSelect pin to output (pin 8)
  pinMode(chipSelect, OUTPUT);
  
  //  LED pin to output (pin A0)
  pinMode(led, OUTPUT);
  
  Serial.println("");
  Serial.println(F("  GPS Shield and SD Shield Datalogger   "));
  Serial.println(F("     ...INITIALIZING SD CARD...         "));
  Serial.println();
  
  // Check presence of uSD card, and can be initialized:
  if (!SD.begin(chipSelect)) {
    Serial.println(F("  **Card failed, or not present**  "));
    Serial.println();
    Serial.println();
  }
  else {
  Serial.println(F("        **CARD INITIALIZED!**           "));
  Serial.println();
  Serial.println(F("     ...waiting for GPS lock...           "));
  Serial.println();
  Serial.println();
  }
}

// This is the main loop of the code. All it does is check for data on 
// the RX pin of the ardiuno, makes sure the data is valid NMEA sentences, 
// then jumps to the getgps() function.
void loop()
{
  while(uart_gps.available())     // While there is data on the RX pin...
  {
      int c = uart_gps.read();    // load the data into a variable...
      if(gps.encode(c))           // if there is a new valid sentence...
      {
        getgps(gps);              // then grab the data.
        write_SD();               // write it to uSD card.
      }
  }
}

// The getgps function will get and print the values we want.
void getgps(TinyGPS &gps)
{
  // To get all of the data into variables that you can use in your code, 
  // all you need to do is define variables and query the object for the 
  // data. To see the complete list of functions see keywords.txt file in 
  // the TinyGPS and NewSoftSerial libs.
  
  // Assign variables
  gpsaltitude=gps.f_altitude();
  gpscourse=gps.f_course();
  gpsspeed=gps.f_speed_kmph();
  // Print GPS counter
  Serial.print("Point #: ");
  Serial.println(GPS_count);
  
  // Function for latitude and longitude
  gps.f_get_position(&latitude, &longitude);
  Serial.print("Lat/Long:     "); 
  Serial.print(latitude,5); 
  Serial.print(", "); 
  Serial.println(longitude,5);
  
  // Date and time
  gps.crack_datetime(&year,&month,&day,&hour,&minute,&second,&hundredths);
  Serial.print("Date:         "); 
  Serial.print(month, DEC);        Serial.print("/"); 
  Serial.print(day, DEC);          Serial.print("/"); 
  Serial.println(year);
  Serial.print("Time:         "); 
  Serial.print(hour, DEC);         Serial.print(":"); 
  Serial.print(minute, DEC);       Serial.print(":"); 
  Serial.print(second, DEC);       Serial.print("."); 
  Serial.println(hundredths, DEC); 
  
  // Elevation, course and speed
  Serial.print("Altitude (m): ");  Serial.println(gpsaltitude);  
  Serial.print("Course (deg): ");  Serial.println(gpscourse); 
  Serial.print("Speed (kmph): ");  Serial.println(gpsspeed);
  Serial.println();
  
  // Counter increases once per loop iteration.
  GPS_count++;
   
}




// Write_SD takes care of saving data to uSD card.
void write_SD()
{
  
  // If year is valid (ie good gps data) and file has not been saved yet, save a file     
  if (year >= 2015 && file_init==0)
  {
    //Build a file name string from local variables
    sprintf(Name, "%02d%02d.txt", month, day);
    Serial.println(Name);  //  Print serial name to check
        
    // Counter increases so that filename is only changed once
    file_init++;
  }
  
  // If filesave is valid, write to it:
  if (file_init > 0)  
  {
    // Write to uSD card
    File dataFile = SD.open(Name, FILE_WRITE);
    
    // Error
    if (!dataFile)
    {
      Serial.println("Error opening datalog.txt");
      return;
    }
    
    // GPS counter
    dataFile.print(GPS_count);        dataFile.print(",");
    // Date
    dataFile.print(year);             dataFile.print(",");
    dataFile.print(month, DEC);       dataFile.print(",");
    dataFile.print(day, DEC);         dataFile.print(",");
    //Time
    dataFile.print(hour, DEC);        dataFile.print(",");
    dataFile.print(minute, DEC);      dataFile.print(",");
    dataFile.print(second, DEC);      dataFile.print(",");
    //Location
    dataFile.print(latitude, DEC);    dataFile.print(",");
    dataFile.print(longitude, DEC);   dataFile.print(",");
    //Altitude, speed, heading
    dataFile.print(gpsaltitude, DEC); dataFile.print(",");
    dataFile.print(gpscourse, DEC);   dataFile.print(",");
    dataFile.print(gpsspeed, DEC);
    dataFile.println();
    dataFile.close();   //close file 
    
    // Blink an LED to indicate 
    digitalWrite(led, HIGH);   // turn the LED on
    delay(100);               // wait for a second
    digitalWrite(led, LOW);    // turn the LED off 

    // Check for new day, in order to start a new file
    if (hour==4 && minute==59 && second==59)
    {
    file_init=0;
    }

  }
      
}

GPS_to_SD_2015.06.05_test.ino (6.68 KB)