EM-406A GPS From Scratch - Part 3


Interpreting Data


At the last post we got GPS data in the form of sentences and proved their correctness. All we need to do now is pick out the data we care about. Hmmm. Easier said than done. I'm a visual thinker, so here's a diagram.

Character Array Diagram

Across the top you see 100 boxes representing the character array defined in the program.

static const byte GPS_BUFFER_SIZE = 100;
char sentence[GPS_BUFFER_SIZE];

The getGPSData() function fills the array and in doing so increments the sentenceIndex variable. When an entire NMEA sentence has been received from the GPS, the sentence[] array is partially filled with characters and the sentenceIndex variable tells us where along the array that sentence ends. In the diagram 'length of the data' points to the array position after the last character. Not being mindful of this often causes 'off by one' errors in array manipulation.
It's important to know the whole 100 characters is always filled with data; see the grey characters? Even when newly defined, it's full of 'garbage'. If you don't know where the end of the valid data is, you'll start reading junk. Worse, if you don't know where the array ends (the 100 limit we've defined) you'll be reading memory that belongs to some other part of the program!

As we iterate through the array, our first priority is to stop at the right place. The first part of the function to read data from the sentence is therefore a loop. I'll use a For loop to iterate through the array from index 0 to one less than the length of the sentence.

void doSentence()
{
  for (int i = 0; i < sentenceIndex; i++)
  {
    Serial.print(sentence[i]);
  }
  Serial.println();
}


At each point along the array, we need to check if the current character is a comma. If it is, we've found the delimiter between two values. Once we've located the start and end index of a value, we need to do something with the value. Actually, while we're checking every character along the array, we might as well extract a value into it's own array. Like this:

void doSentence()
{
  printSentence(sentence, sentenceIndex);
  Serial.println();  
  
  char value[10];  // new array just big enough for a value
  byte valueIndex = 0;  // index for the value[] array
  byte valuePosition = 0;
  
  for (int i = 0; i < sentenceIndex; i++)
  {
    if (sentence[i] == ',')
    {
      // end of the value
      printValue(value, valueIndex, valuePosition);
      valueIndex = 0;
      valuePosition++;
    }
    else
    {
      value[valueIndex++] = sentence[i];
    }
    //Serial.print(sentence[i]);
  }

  Serial.println();
}

I've written a lot of code in the background here. It's time I gave you the whole file. If you run this sketch, you'll see results like this:


Start!
$GPGGA,094118.759,,,,,0,00,,,M,0.0,M,,0000
[0|$GPGGA] [1|094118.759] [2|] [3|] [4|] [5|] [6|0] [7|00] [8|] [9|] [10|M] [11|0.0] [12|M] [13|] 
$GPGSA,A,1,,,,,,,,,,,,,,,
[0|$GPGSA] [1|A] [2|1] [3|] [4|] [5|] [6|] [7|] [8|] [9|] [10|] [11|] [12|] [13|] [14|] [15|] [16|] 
INVALID: $GPRMC,094118.759,V,,,,,,,230412$GPGGA,094119.764,,,,,0,00,,,M,0.0,M,,0000
$GPGSA,A,1,,,,,,,,,,,,,,,
[0|$GPGSA] [1|A] [2|1] [3|] [4|] [5|] [6|] [7|] [8|] [9|] [10|] [11|] [12|] [13|] [14|] [15|] [16|] 


So, look at the first NMEA sentence on line 2; it's printed out holus bolus. On line 3 the same sentence is picked apart by the doSentence() function. Each value is printed in square brackets. First the position in the sentence, you can see that number ascending as you move left to right, then the content of the value is displayed after a pipe sign. Value 0 on line 3 is $GPGGA.

Interestingly, you can see INVALID: comes up - a sentence has failed validation. Why is that? Well, while we're debugging our code, we're spending a lot of time printing text to the Serial Monitor and all this is happening at a lazy 4800 baud. So at some point in the program we're taking so much time analysing the content of a sentence and printing it out, that the buffer holding the incoming data gets full. When it fills up characters get dropped. Then when the sentence is being read from the buffer, the XOR calculation doesn't equal the check digits and the whole sentence gets dropped. That's why you don't see the the GPRMC sentence above being picked apart.

I had planned on showing how to interpret the text values in the NMEA sentence into numbers and enumerations, but that would make this post too long. I'll make that the subject of next post.

That is all.

Comments

  1. Fantastic! Thanks Christian, you have made my day ... no, my week!
    I have been trying all sorts of code from many many sources and yours is the only example that:
    a. works, and
    b. I can easily understand, and
    c. it works with SoftwareSerial (I'm not using shields).

    My project is to multiplex NMEA data from various instruments and display the output on an android tablet (or phone). To reliably collect the data streams I think I will need an Arduino Nano for each nmea input (talker). The nano will check the data and then send it to a master arduino when this master requests data ie handshaking is implemented.
    Maybe I'm aiming too high but hey, I got the time!!
    Thanks again ... great blog.
    Looking forward to the next chapter??
    John Weatherley
    weatherley dot john at gmail.com

    ReplyDelete
  2. what about tinygps library ? will it make it simpler to program?

    ReplyDelete
  3. i cannot download your program file, its giving sort of error , cant be opened

    ReplyDelete
  4. I'm sure TinyGPS will work. My goal here was to build a "TinyGPS" from scratch. I'll see if I can fix the file issue. Thanks for the note.

    ReplyDelete
  5. Hi Christian. These are three great posts. I'm trying to figure out how to write the code for the smallest possible GPS data logging to and SD card that I can. I went through Jeremy Blum's tutorial but he uses a Mega and with an Uno the sketch gets close enough to memory capacity that it glitches and gets a Programmer Not in Sync code. I got rid of the bootloader on the chip by using a progammer on the ISCP pins but still wan't enough. I don't need most of the formatting and such.

    My goal is a very small sketch that writes NMEA data to the SD card with the ability to change sampling intervals in the software (something like your Part I sketch but writing to an SD card). I'm working on a small, long duty cycle device that is as spare as possible. If you have any ideas, that would be great. I'm going to work off of what you've done in these three post and see if I can make progress.

    So far I've been trying to eliminate things from the bloated sketches but your minimalist approach is likely more sound.

    Milton
    ingenuityarts at gmail dot com

    ReplyDelete
    Replies
    1. PS - I just compiled your code and it's just over 3000 bytes which is wonderfully efficient. Nice work.

      Delete
  6. Milton, I've had a fun afternoon playing with the code and trying out a solution for what you're after. Please check out my latest post called Basic GPS Data Logger.
    http://arduinoetcetera.blogspot.com.au/2013/01/basic-gps-data-logger.html
    Hope it's useful. Chris.

    ReplyDelete
  7. This comment has been removed by the author.

    ReplyDelete

Post a Comment