Pages: [1]   Go Down
Author Topic: Validating incoming GPS data  (Read 654 times)
0 Members and 1 Guest are viewing this topic.
UK
Offline Offline
Jr. Member
**
Karma: 2
Posts: 90
It was like it when I found it
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I'm having trouble with what should be a simple bit of code to validate GPS sentences on a MEGA.

First some background.... GPS units generate a NMEA 'sentence' containing navigation data (position, speed, altitiude etc), the last two bytes (after the asterix) are checksum validation codes that can be used to confirm that no data has been corrupted.

This code has a very simple function, it reads the GPS sentences from port 1, calculates the checksum validation codes and compares them with the transmitted validation code and then sends the sentence to the screen. The problem is that about 10% of the records don't get validated correctly. I've worked out that the issue seems to be because the case statement that processes each GPS character as it's recieved is occasionally missing the "$" character that indicates the start of a new record.

Heres' the code (much of it cribbed from TinyGPS in case it looks familiar)....

Code:

char _nmea_checksum[2+1];          // NMEA sentence checksum (last two characters after the asterix)
char _nmea_rec[100+1];             // NMEA record (aka sentence)
byte _parity;
unsigned long _good_recs;          // Number of records that pass checksum validation
unsigned long _bad_recs;           // Number of records that fail checksum validation
bool _sentence_comp;               // Set to true once the end of a a NMEA sentence is detected
byte _term_offset;                 // pointer indicating how far along a NMEA sentence the process is.                 


void setup()
{
  Serial.begin(115200);            // Baud rate for screen
 
  Serial.println("##################################################################");
  Serial.println("####################     START     ###############################");
  Serial.println("##################################################################");
 
  Serial1.begin(57600);          // Baud rate for GPS unit
}

void loop() {
 
  if (Serial1.available())
  {
    Serial.print(Serial1.read(), BYTE);
  }
 
  //Check that there's data waiting to be read
  while (Serial1.available())
  {
    //process current byte from the logger
    if (processGpsChar(Serial1.read()))
    {
      //return true;
    }
  }
}


bool processGpsChar(char c)
{
  // Firstly, check for possible corruption.
  // NMEA data should (I think) be limited to 0-9, A-Z and the following punctiation .,$* (and of course a space, carriage return and line feed)
  if ( (c>=48 && c<=57) || (c>=65 && c<=90) || (c>=42 && c<=46) || c==36 || c==32 || c==10 || c==13  )
  {
    // The character recieved from the GPS is VALID, display it
    Serial.print(c);
  }
  else
  {
    // Invalid character
    Serial.println("ERROR - Received unexpected ascii code <%d> from GPS");
    Serial.print("ERROR - Received unexpected ascii code <");Serial.print(c);Serial.println("> from GPS");
  }

  // Read in NMEA sentences one character at a time.
  switch(c)
  {
  case '\r': 
    //Linefeed - ignore this
    break;
  case '\n': 
    //Carriage return - marks the end of a record
    //Check that the record is valid, if it is then
    //process it.
    // Check that the buffer hasn't overflowed
    if ( strlen(_nmea_rec) > 100 )
    {
      Serial.print("ERROR - NMEA record too long");
    }
   
    byte checksum2;
   
    // Convert the transmitted checksum from a two digit string to a decimal.
    checksum2 = 16 * from_hex(_nmea_checksum[0]) + from_hex(_nmea_checksum[1]);

    Serial.print("nmea_checksum=<");Serial.print(_nmea_checksum);
    Serial.print(">, checksum2=<");Serial.print(checksum2,DEC);
    Serial.print(">, parity=<");Serial.print(_parity,DEC);Serial.println(">");

    //Check the calculated checksum (_parity) matches the transmitted checksum (checksum2).
    if (checksum2 == _parity)
    {
      _good_recs++;       // increment the good record counter
      Serial.print("-Y-");
    }
    else
    {
      _bad_recs++;       // increment the bad record counter
      Serial.print("-N-");
    }
   
    // display the calculated checksum plus the good and bad record count
    Serial.print(_nmea_checksum);Serial.print("-");Serial.print(_good_recs);Serial.print("-");Serial.println(_bad_recs);
    break;   
  case '*':
    // This marks the end of the NMEA sentence, and the begining of the
    // validation characters.
    if (_term_offset < sizeof(_nmea_rec))
    {
      _nmea_rec[_term_offset] = 0;  //put a null terminator at the end of the string
    }
    _sentence_comp = true;          // Flag the fact that the NMEA sentence is now complete
    _term_offset = 0;               // reset the pointer, it can now be used to populate the checksum
    break;
  case '$': // Start of record
    Serial.println("nmea_start");
    // Flush the stuff that needs flushing
    nmea_start();
    break;
  default :
    //any other character.
    if (_sentence_comp == false)
    {
      //If the sentence is incomplete, add the character to the end of the current sentence
      if (_term_offset < sizeof(_nmea_rec) - 1)
      {
        _nmea_rec[_term_offset] = c;
        _nmea_rec[_term_offset+1] = 0;
        _term_offset++;
      }
      // The parity check is a bitwise XOR of all characters received so far.
      _parity = _parity ^ c;   //bitwise exclusive OR
    }
    else
    {
      // The sentence IS complete, so these characters must be part of the checksum
      if (_term_offset < 2)
      {
        // this character must be part of the checksum
        _nmea_checksum[_term_offset++] = c;
      }
    }
    break;
 }

 //return false - indicates a character returned, but the sentence is still incomplete
 return false;
}

void nmea_start(void)
{
  // flushes and resets stuff when a new record starts
  _parity = 0;               // reset the parity checker
  _nmea_checksum[0] = 0;     // Reset the checksum
  _nmea_checksum[1] = 0;
  _nmea_checksum[2] = 0;
  _sentence_comp = false;    // reset the flag for detecting the end of the sentence
  _term_offset = 0;          // reset the pointer
}


int from_hex(char a)
{
  // Returns 0-9 for an input of '0'-'9' and 10-15 for an input of 'A'-'F'
  if (a >= 'A' && a <= 'F')
    return a - 'A' + 10;
  else if (a >= 'a' && a <= 'f')
    return a - 'a' + 10;
  else
    return a - '0';
}
 


And here are some examples of correctly processed records as they appear on the screen…
Quote
$nmea_start
GPGLL,,,,,184219.340,V,N*7A
nmea_checksum=<7A>, checksum2=<122>, parity=<122>
-Y-7A-1-1
$nmea_start
GPGSA,A,1,,,,,,,,,,,,,,,*1E
nmea_checksum=<1E>, checksum2=<30>, parity=<30>
-Y-1E-2-1
$nmea_start
GPGSV,1,1,00*79
nmea_checksum=<79>, checksum2=<121>, parity=<121>
-Y-79-3-1
Each NMEA record starts with the “$”, then the record itself gets displayed along with some variables.

Here’s and example of a record that has failed the checksum validation….
Quote
$GPGGA,184221.940,,,,,0,0,,,M,,M,,*49
nmea_checksum=<49>, checksum2=<73>, parity=<50>
-N-49-61-13

The big difference  (apart from it not working!) is the absence of “nmea_start” between the “$” and the rest of the sentence. This seems to indicate an issue with the case statement in my code, if the start of the sentence isn’t recognized, then the various variables don’t get reset properly and the sentence fails validation .

My first guess was that there was interfacing issue between the GPS module and the Mega, but since the GPS data is being correctly displayed on the screen, then it must be getting read successfully, therefore the problem must be with my code.

Any suggestions? 

The GPS unit is a Locosys LS20031 http://www.sparkfun.com/products/8975 connected to a Mega2560.
Logged

Seattle, WA USA
Offline Offline
Brattain Member
*****
Karma: 654
Posts: 50931
Seattle, WA USA
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Code:
if (Serial1.available())
  {
    Serial.print(Serial1.read(), BYTE);
  }
 
  //Check that there's data waiting to be read
  while (Serial1.available())
  {
    //process current byte from the logger
    if (processGpsChar(Serial1.read()))
On each pass through loop, you throw away a character read from the serial port, if one is available. Why is that?
Logged

UK
Offline Offline
Jr. Member
**
Karma: 2
Posts: 90
It was like it when I found it
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

There's a simple explaination for that... I'm a bl00dy idiot!

Thanks for that I've just made the change and processed 5000 records without a glitch - it's amazing what a 2nd pair of eyes will do!

P.S. I think you've just broken your (already very good) record for posting a solution.

Cheers
Logged

Pages: [1]   Go Up
Jump to: