[HELP!] Weird GPS data and micro SD card problem

Hello guys,

I am currently using an Arduino Mega 2560 R3 with an iTead GPS Shield v1.1 (with micro-SD slot). I am having trouble with GPS data I am getting.

The first problem is altitude:
When I run my code WITHOUT micro SD card plugged into the shield, the altitude comes out fine, but when I plug in the card and press the reset button, the altitude changes to 1000000.00 and stays at that value for a long time. When I run without the SD card, altitude data appears reasonable, but that value can change by quite a lot (18 to 61) even though I didn’t move it at all. What is going on here?

The second problem is heading (I am assuming its data from GPS.f_course() function):
If I let the program run for a while (~30-40min) without moving it, the heading suddenly changes from 0.00 to some number like 334.08. Why?

Latitude, longitude, time/date, and speed doesn’t experience large changes like these.

Below are the code I am using

/*
Location.txt: Date/Time , Latitude (deg decimal), Longitude, Altitude (m)
Speed.txt: Date/Time, Speed (m/s), Heading (deg)
*/

#include<TinyGPS.h>
#include<SD.h>
#include<SPI.h>

int CS = 53;

long lat,lon;
unsigned long time, date;
int year,tinseconds;
byte month, day, hour, minute, second, hundredths;
unsigned long age;
char dataString[55];
TinyGPS GPS;

struct GPSstruct {
  String dateTime;
  int timeseconds;
  float C_lat;
  float C_lon;
  float C_alt;
  float heading;
  float speedmps;
};

void setup()
{
  Serial.begin(115200);
  Serial1.begin(38400);
  pinMode(CS, OUTPUT);
  if(!SD.begin(CS))
  {
    Serial.println("Card Failure");
  }
}

void loop()
{
while(Serial1.available()){ // check for gps data
   if(GPS.encode(Serial1.read())){ // encode gps data
    GPS.crack_datetime(&year, &month, &day, &hour, &minute, &second, &hundredths, &age);
    GPS.get_position(&lat,&lon); // get latitude and longitude in degree decimals multiplied by 10^5
    sprintf(dataString, "%02d/%02d/%02d %02d:%02d:%02d",month,day,year,hour,minute,second);
    tinseconds = second;
    float C_lat = ((float)lat)/1000000; //to get degree decimals
    float C_lon = ((float)lon)/1000000;
    float C_alt = GPS.f_altitude();
    float heading = GPS.f_course();
    float speedmps = GPS.f_speed_mps();
    GPSstruct A = {dataString,tinseconds,C_lat,C_lon,C_alt,heading,speedmps};

//DEBUG
    Serial.println();
    Serial.print("Date/Time: ");Serial.println(dataString);
    Serial.print("Time (sec): ");Serial.println(tinseconds);
    Serial.print("Lat: ");Serial.println(C_lat);
    Serial.print("Lon: ");Serial.println(C_lon);
    Serial.print("Alt: ");Serial.println(C_alt);
    Serial.print("Heading: ");Serial.println(heading);
    Serial.print("Speed: ");Serial.println(speedmps);
//END of DEBUG

  File dataFile = SD.open("Location.txt", FILE_WRITE); //Records date/time, lat, lon, alt
    if (dataFile)
    {
      dataFile.print(A.dateTime);dataFile.print(" , ");dataFile.print(A.C_lat);dataFile.print(" , ");dataFile.print(A.C_lon);dataFile.print(" , ");dataFile.println(A.C_alt);
      dataFile.close();
    }
    else
    {
       Serial.println("Location.txt cannot be opened!"); 
    }
   File dataFile2 = SD.open("Speed.txt",FILE_WRITE); //Records date/time, speed, heading
   if (dataFile2)
   {
     dataFile2.print(A.dateTime);dataFile2.print(" , ");dataFile2.print(A.speedmps);dataFile2.print(" , ");dataFile2.println(A.heading);
     dataFile2.close();
   }
   else
   {
     Serial.println("Speed.txt cannot be opened!");
   }
   }
}
}

Here are some sample results of what I am experiencing

Altitude changes when I plug in SD card

Date/Time: 08/13/2014 20:54:28
Time (sec): 28
Lat: 37.87
Lon: -122.29
Alt: 25.60
Heading: 0.00
Speed: 0.03
Location.txt cannot be opened!
Speed.txt cannot be opened!

Date/Time: 08/13/2014 20:54:30
Time (sec): 30
Lat: 37.87
Lon: -122.29
Alt: 1000000.00
Heading: 0.00
Speed: 0.07

Date/Time: 08/13/2014 20:54:31
Time (sec): 31
Lat: 37.87
Lon: -122.29
Alt: 1000000.00
Heading: 0.00
Speed: 0.02

Example of heading changes suddenly and stays at 334.08

Date/Time: 08/13/2014 20:44:15
Time (sec): 30
Lat: 37.87
Lon: -122.29
Alt: 61.00
Heading: 0.00
Speed: 0.05

Date/Time: 08/13/2014 20:44:16
Time (sec): 31
Lat: 37.87
Lon: -122.29
Alt: 61.00
Heading: 334.08
Speed: 0.77

Date/Time: 08/13/2014 20:44:17
Time (sec): 31
Lat: 37.87
Lon: -122.29
Alt: 61.00
Heading: 334.08
Speed: 0.14

Can anyone give me some suggestions on how to fix these problems or why they happen?
Thank you.

    sprintf(dataString, "%02d/%02d/%02d %02d:%02d:%02d",month,day,year,hour,minute,second);

The output is 20 bytes.

char dataString[55];

The array to hold it is 55 bytes. Why?

    GPSstruct A = {dataString,tinseconds,C_lat,C_lon,C_alt,heading,speedmps};

Then, you piss away resources by storing the NULL terminated char array in a String. Why?

      dataFile.print(A.dateTime);dataFile.print(" , ");dataFile.print(A.C_lat);dataFile.print(" , ");dataFile.print(A.C_lon);dataFile.print(" , ");dataFile.println(A.C_alt);

There REALLY does not seem to be a benefit from using a struct.

Why is tinseconds stored in the struct but not written to the file?

Isyourspacekeyand/orcarriagereturnbroken?

    GPS.get_position(&lat,&lon); // get latitude and longitude in degree decimals multiplied by 10^5
    float C_lat = ((float)lat)/1000000; //to get degree decimals

So, you divide by 10^6. OK by me...

Pin 53, on the Mega, needs to be OUTPUT for SPI to work. But, I'm not sure that it can be used as a chip select pin, too. You might want to try a different pin.

The reason that the "heading" is nonsense, is because the gps module will only calculate the heading when it is moving, and usually only when moving at a speed greater than about 4 km/hr. Unlike a compass, it has no means of knowing which way you are facing when you are standing still. It can only determine the direction of your apparent motion when it can compare the position now, to the position around 1 second ago.

GPSstruct A = {dataString,tinseconds,C_lat,C_lon,C_alt,heading,speedmps};

This doesn't even look like a valid c/C++ operation to me at all. The C_alt variable which you have declared in loop( ) is not the same as the field in your struct.

Hello PaulS,

This is my first time working with C (no base in C++/other common language either), so I don't completely get what you mean by

piss away resources by storing the NULL terminated char array in a String. Why?

I will change the dataString array to hold 20 bytes instead of 55. As to why I am using a struct, I am planning on using the parts of the code that extract gps data as a function (this here is just testing if it works), and from what I've read online, C doesn't allow you to return more than one type of data, so you have to use struct to get around it. Please let me know if I am wrong about that.

The tinseconds is not logged into the datafile because I only need it for calculation later on, which I realized should be total time duration, not just seconds part from time/date data.

So for the CS select pin, I should use pins other than 53? Do I need to set pin 53 as anything?

Thank you.

Hello michinyon,

The reason that the "heading" is nonsense, is because the gps module will only calculate the heading when it is moving

So shouldn't the heading always be at 0.00? The results I got suddenly changes after a while.

GPSstruct A = {dataString,tinseconds,C_lat,C_lon,C_alt,heading,speedmps};

This doesn't even look like a valid c/C++ operation to me at all. The C_alt variable which you have declared in loop( ) is not the same as the field in your struct.

Sorry, this is my first time working with C so can you explain more on that? All I am trying to do is collect all the data I need into one struct so I can use it later on. I am using a struct because this code is meant to be in a separate function (see my reply to PaulS).

Thanks!

so I don't completely get what you mean by

A String is a class. Each instance of the class has data and methods. You are pissing away resources using an instance of a class when ALL you care about is the data.

Make the struct look like:

struct GPSstruct {
  char dateTime[20];
  int timeseconds;
  float C_lat;
  float C_lon;
  float C_alt;
  float heading;
  float speedmps;
};

Then, create an instance of the struct:

GPSstruct A;

Then, populate the struct:

A.C_lat = C_lat;

etc.

To populate the dateTime field,

strcpy(A.dateTime, dateTime);

Yes, it uses a few more lines, but the sketch will be smaller and will not fragment memory.

Hi PaulS,

Thank you.
I have followed your advice and modified my code.
Now I have a new problem when I put them into custom functions.

The location data log gives me

, 0.00 , 0.00 , 0.00
, 0.00 , 0.00 , 0.00
, 0.00 , 0.00 , 0.00
, 0.00 , 0.00 , 0.00
, 0.00 , 0.00 , 0.00
, 0.00 , ovf , 0.00
, 0.00 , ovf , 0.00
, 0.00 , ovf , 0.00
, 0.00 , ovf , 0.00
, 0.00 , ovf , 0.00
, 0.00 , ovf , 0.00
, 0.00 , ovf , 0.00

and the speed.txt also gave me similar things.

What am I doing wrong? Why is there overflow?
It works fine if I just put it all in the loop() function.
The main code that these will be implemented into is quite long (with its own custom functions), so I don’t really want to put it in the loop function.

Here is my code:

#include<TinyGPS.h>
#include<SD.h>
#include<SPI.h>

int CS = 53;
long lat,lon;
unsigned long time, date;
int year,tinseconds;
byte month, day, hour, minute, second, hundredths;
unsigned long age;
char dataString[20];
TinyGPS GPS;

struct GPSstruct {
  char dateTime[20];
  int timeseconds;
  float C_lat;
  float C_lon;
  float C_alt;
  float heading;
  float speedmps;
};

//Function Prototypes
struct GPSstruct GPSdata(void);
void SDlogger(struct GPSstruct);

void setup()
{
  Serial.begin(115200);
  Serial1.begin(38400);
  pinMode(CS, OUTPUT);
  SD.begin(CS);
}

//GPS data function:
struct GPSstruct GPSdata(void) {  
 if(Serial1.available()){ // check for gps data
   if(GPS.encode(Serial1.read())){ // encode gps data
    GPS.crack_datetime(&year, &month, &day, &hour, &minute, &second, &hundredths, &age);
    GPS.get_position(&lat,&lon); // get latitude and longitude in degree decimals multiplied by 10^5
    sprintf(dataString, "%02d/%02d/%02d %02d:%02d:%02d",month,day,year,hour,minute,second);
    tinseconds = second;
    float C_lat = ((float)lat)/1000000; //to get degree decimals
    float C_lon = ((float)lon)/1000000;
    float C_alt = GPS.f_altitude();
    float heading = GPS.f_course();
    float speedmps = GPS.f_speed_mps();
    GPSstruct A;
    strcpy(A.dateTime, dataString);
    A.C_lat = C_lat;
    A.C_lon = C_lon;
    A.C_alt = C_alt;
    A.heading = heading;
    A.speedmps = speedmps;
    free(dataString); //free the memory allocated to string??

//DEBUG
    Serial.print(F("Date/Time: "));Serial.println(dataString);
    Serial.print(F("Time (sec): "));Serial.println(tinseconds);
    Serial.print(F("Lat: "));Serial.println(C_lat);
    Serial.print(F("Lon: "));Serial.println(C_lon);
    Serial.print(F("Alt: "));Serial.println(C_alt);
    Serial.print(F("Heading: "));Serial.println(heading);
    Serial.print(F("Speed: "));Serial.println(speedmps);
    Serial.println();
//END of DEBUG

    return A;
  }
 }
}

//SD data logging funct:
void SDlogger(struct GPSstruct A)
{
   File dataFile = SD.open("Location.txt", FILE_WRITE); //Records date/time, lat, lon, alt
    if (dataFile)
    {
      dataFile.print(A.dateTime);dataFile.print(" , ");dataFile.print(A.C_lat);dataFile.print(" , ");dataFile.print(A.C_lon);dataFile.print(" , ");dataFile.println(A.C_alt);
      dataFile.close();
    }
    else
    {
       Serial.println(F("Location.txt cannot be opened!")); 
    }
   File dataFile2 = SD.open("Speed.txt",FILE_WRITE); //Records date/time, speed, heading
   if (dataFile2)
   {
     dataFile2.print(A.dateTime);dataFile2.print(" , ");dataFile2.print(A.speedmps);dataFile2.print(" , ");dataFile2.println(A.heading);
     dataFile2.close();
   }
   else
   {
     Serial.println(F("Speed.txt cannot be opened!"));
   } 
} 
void loop()
{
  struct GPSstruct Gdata = GPSdata();
  SDlogger(Gdata);
}

Your GPSdata function only populates and returns struct A if there is data coming in from the gps. If there wasn't, which will often be the case because serial data transfer is slow, loop will pick up whatever happened to be on the stack and treat it as an instance of GPSstruct. As you see, that stack content is garbage.

You must ensure that GPSdata always returns a valid struct - make the return the very last line of the function. Make struct A global, or better, a static variable in GPSdata.

    free(dataString); //free the memory allocated to string??

free() is the required counter-part to malloc(). You didn't call malloc(). There is nothing to free().

Hello guys,

I changed the GPSstruct A to a static variable, moved the return A command to the last line, and took out the unnecessary free() command, but it is still no good. The data files no longer have any “ovf”, but all the data is 0 and the date/time is completely missing. The serial.println in GPSdata function also doesn’t get printed to the serial monitor.

Is it memory problem? But the other one works fine.

#include<TinyGPS.h>
#include<SD.h>
#include<SPI.h>

int CS = 53;
long lat,lon;
unsigned long time, date;
int year,tinseconds;
byte month, day, hour, minute, second, hundredths;
unsigned long age;
char dataString[20];
TinyGPS GPS;

struct GPSstruct {
  char dateTime[20];
  int timeseconds;
  float C_lat;
  float C_lon;
  float C_alt;
  float heading;
  float speedmps;
};

//Function Prototypes
struct GPSstruct GPSdata(void);
void SDlogger(struct GPSstruct);

void setup()
{
  Serial.begin(115200);
  Serial1.begin(38400);
  pinMode(CS, OUTPUT);
  SD.begin(CS);
}

//GPS data function:
struct GPSstruct GPSdata(void) {
static GPSstruct A;

 if(Serial1.available()){ // check for gps data
   if(GPS.encode(Serial1.read())){ // encode gps data
    GPS.crack_datetime(&year, &month, &day, &hour, &minute, &second, &hundredths, &age);
    GPS.get_position(&lat,&lon); // get latitude and longitude in degree decimals multiplied by 10^5
    sprintf(dataString, "%02d/%02d/%02d %02d:%02d:%02d",month,day,year,hour,minute,second);
    tinseconds = second;
    float C_lat = ((float)lat)/1000000; //to get degree decimals
    float C_lon = ((float)lon)/1000000;
    float C_alt = GPS.f_altitude();
    float heading = GPS.f_course();
    float speedmps = GPS.f_speed_mps();
    strcpy(A.dateTime, dataString);
    A.C_lat = C_lat;
    A.C_lon = C_lon;
    A.C_alt = C_alt;
    A.heading = heading;
    A.speedmps = speedmps;

//DEBUG
    Serial.print(F("Date/Time: "));Serial.println(dataString);
    Serial.print(F("Time (sec): "));Serial.println(tinseconds);
    Serial.print(F("Lat: "));Serial.println(C_lat);
    Serial.print(F("Lon: "));Serial.println(C_lon);
    Serial.print(F("Alt: "));Serial.println(C_alt);
    Serial.print(F("Heading: "));Serial.println(heading);
    Serial.print(F("Speed: "));Serial.println(speedmps);
    Serial.println();
//END of DEBUG
  }
 }
 return A;
}

//SD data logging funct:
void SDlogger(struct GPSstruct A)
{
   File dataFile = SD.open("Location.txt", FILE_WRITE); //Records date/time, lat, lon, alt
    if (dataFile)
    {
      dataFile.print(A.dateTime);dataFile.print(" , ");dataFile.print(A.C_lat);dataFile.print(" , ");dataFile.print(A.C_lon);dataFile.print(" , ");dataFile.println(A.C_alt);
      dataFile.close();
    }
    else
    {
       Serial.println(F("Location.txt cannot be opened!")); 
    }
   File dataFile2 = SD.open("Speed.txt",FILE_WRITE); //Records date/time, speed, heading
   if (dataFile2)
   {
     dataFile2.print(A.dateTime);dataFile2.print(" , ");dataFile2.print(A.speedmps);dataFile2.print(" , ");dataFile2.println(A.heading);
     dataFile2.close();
   }
   else
   {
     Serial.println(F("Speed.txt cannot be opened!"));
   } 
} 
void loop()
{
  struct GPSstruct Gdata = GPSdata();
  SDlogger(Gdata);
}

Is it memory problem?

No. It is a coding problem.

Look at your loop() function:

void loop()
{
  struct GPSstruct Gdata = GPSdata();
  SDlogger(Gdata);
}

On every pass through loop(), you assume that the GPSdata() function will return a struct that contains a complete record. Then, you log it.

But, look at GPSData():

struct GPSstruct GPSdata(void) {
static GPSstruct A;

 if(Serial1.available()){ // check for gps data
   if(GPS.encode(Serial1.read())){ // encode gps data
      // Snip happens
 }
 return A;
}

If there is no data from the GPS, return some garbage.
If there is data, but the data is not a complete sentence, return some garbage.

You REALLY need to get over the concept that the use of a struct is some kind of magic bullet.

Quit making the function return a struct. Create a global instance of the struct. Populate that WHEN there is data to populate it with. Make the function return a boolean - true means that the struct contains new data, while false means that it does not.

Log data only when GPSData() returns true.

Ah, okay!

Thank you! It works fine now.