4

enter image description herei am trying to parse the incoming GPGGA NMEA GPS string using Arduino uno and below code. What i am trying to do is that i am using only GPGGA NMEA string to get the values of Latitude, longitude and altitude.In my below code, i had put certain checks to check if incoming string is GPGGA or not, and then store the further string in a array which can be further parsed suing strtok function and all the 3 GPS coordinates can be easily find out.

But i am unable to figure out how to store only GPGGA string and not the further string.I am using a for loop but it isn't working.

I am not trying to use any library.I had came across certain existing codes like this.

Here is the GPGGA string information link

i am trying to have following functionlity i) Check if incoming string is GPGGA ii) If yes, then store the following string upto EOL or upto * (followed by checksum for the array) in a array, array length is variable(i am unable to find out solution for this) iii) Then parse the stored array(this is done, i tried this with a different array)

 #include <SoftwareSerial.h>

    SoftwareSerial mySerial(10,11);  // 10 RX / 11 TX

    void setup()
    {
    Serial.begin(9600);
    mySerial.begin(9600);
    }

    void loop()
    {
    uint8_t x;
    char gpsdata[65];

    if((mySerial.available()))
    {
    char c = mySerial.read();
    if(c == '$')
      {char c1 = mySerial.read();
       if(c1 == 'G')
         {char c2 = mySerial.read();
          if(c2 == 'P')
            {char c3 = mySerial.read();
             if(c3 == 'G')
               {char c4 = mySerial.read();
                if(c4 == 'G')
                   {char c5 = mySerial.read();
                    if(c5 == 'A')
                       {for(x=0;x<65;x++)
                        { 
                        gpsdata[x]=mySerial.read();


    while (gpsdata[x] == '\r' || gpsdata[x] == '\n')
                    {
                    break;
                    }

                        }

                       }
                       else{
                          Serial.println("Not a GPGGA string");
                        }
                   }
               }

            }     

         }

      }

    }

    Serial.println(gpsdata);
    }

Edit 1: Considering Joachim Pileborg, editing the for loop in the code.

I am adding a pic to show the undefined output of the code.

Input for the code:

$GPGGA,092750.000,5321.6802,N,00630.3372,W,1,8,1.03,61.7,M,55.2,M,,*76
$GPGSA,A,3,10,07,05,02,29,04,08,13,,,,,1.72,1.03,1.38*0A
$GPGSV,3,1,11,10,63,137,17,07,61,098,15,05,59,290,20,08,54,157,30*70
$GPGSV,3,2,11,02,39,223,19,13,28,070,17,26,23,252,,04,14,186,14*79
$GPGSV,3,3,11,29,09,301,24,16,09,020,,36,,,*76
$GPRMC,092750.000,A,5321.6802,N,00630.3372,W,0.02,31.66,280511,,,A*43
$GPGGA,092751.000,5321.6802,N,00630.3371,W,1,8,1.03,61.7,M,55.3,M,,*75
$GPGSA,A,3,10,07,05,02,29,04,08,13,,,,,1.72,1.03,1.38*0A
$GPGSV,3,1,11,10,63,137,17,07,61,098,15,05,59,290,20,08,54,157,30*70
$GPGSV,3,2,11,02,39,223,16,13,28,070,17,26,23,252,,04,14,186,15*77
$GPGSV,3,3,11,29,09,301,24,16,09,020,,36,,,*76
$GPRMC,092751.000,A,5321.6802,N,00630.3371,W,0.06,31.66,280511,,,A*45
shailendra
  • 271
  • 2
  • 6
  • 18
  • You have an off-by-one error. In the inner `for` loop you iterate from `0` to (and including) 65, that is 66 entries for an array containing 65 entries. That leads to undefined behavior. – Some programmer dude Nov 08 '13 at 20:45
  • Also, depending on the data, and your `Serial.println` function, if the `Serial.println` function expects a string (i.e. that the data is zero-terminated) and `gpsdata` is not zero-terminated, then you have undefined behavior again. Also, if `gpsdata` is not all textual, but binary, then it may contain an embedded zero which is treated as a string terminator. And as `gpsdata` is a local variable, it is *not* filled with zeroes (all its values are indeterminate until you initialize it) using it in case of errors is also undefined behavior. – Some programmer dude Nov 08 '13 at 20:47
  • Use the return value from `mySerial.available()` to get number of bytes in pipe to read, allocate memory for a string array, read it in, and test for validity. See detail in answer below. – ryyker Nov 08 '13 at 21:43
  • Regarding your question below, _is due to the array size which i had defined for gpsdata[65]?_. If the number of bytes indicated by the return value of `mySerial.available()` is bigger than the number of array elements contained in `gpsdata[]`, ***OR*** you read a '\n\r' somewhere, and do not append the string with a '\0', this could be a problem. See answer below. – ryyker Nov 08 '13 at 21:53
  • Can you cut and paste a segment of the lines you show as your input into a text and add it to your post. I would like to test it against my code to see where it is breaking. – ryyker Nov 08 '13 at 22:04
  • Updated my answer to initialize the `char * myString` to have all `0`s before the read starts (used `calloc()` instead of `malloc()`), As @joachim said in his post. un-initialized space in a string could be what is giving you unexpected results. – ryyker Nov 08 '13 at 22:10
  • @ryyker: My input is directly coming to the software serial, well i am copying the content from web which can be taken as a Input. – shailendra Nov 08 '13 at 22:14
  • Okay, well, from what I have researched about using the `arduino` libraries, ***if*** you check the number of char in the pipe (i.e. using `numchars = mySerial.available()`), ***and if*** you test for '\n\r' along the way of reading (as I have coded) ***AND if*** you properly terminate with '\0' once you detect '\n\r' or reach end of input, ***then*** you should have a valid C string. Unless you can post any thing different, or specific to what you _ARE_ getting back, I don't know how else to proceed. I have addressed all the concerns I can think of. – ryyker Nov 08 '13 at 22:23

6 Answers6

3

After a quick check of the linked article on the NMEA 0183 protocol, this jumped out at me:

<CR><LF> ends the message.

This means, that instead of just read indiscriminately from the serial port, you should be looking for that sequence. If found, you should terminate the string, and break out of the loop.

Also, you might want to zero-initialize the data string to begin with, to easily see if there actually is any data in it to print (using e.g. strlen).

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • Nice answer. Do you think `if((mySerial.available()` may be checking for non-null response? (regarding your suggesting to check by using `strlen`) – ryyker Nov 08 '13 at 21:09
  • Same question - answered ***[here](http://arduino.cc/en/Reference/SoftwareSerialAvailable)*** – ryyker Nov 08 '13 at 21:17
  • I used and in my code and it worked out for me for first 20-25 outpurts over COM16 port but after that same undefined output started coming.Is is due to the array size which i had defined for gpsdata[65]? – shailendra Nov 08 '13 at 21:21
  • @shailendra Yes, you might want to increase the array size. – Some programmer dude Nov 09 '13 at 11:00
1

You could use some functions from the C library libnmea. Theres functions to split a sentence into values by comma and then parse them.

jack-xtal
  • 31
  • 2
0

Offering this as a suggestion in support of what you are doing...

Would it not be useful to replace all of the nested if()s in your loop with something like:

EDIT added global string to copy myString into once captured

char globalString[100];//declare a global sufficiently large to hold you results


void loop() 
{
    int chars = mySerial.available();
    int i;
    char *myString;
    if (chars>0)
    {
        myString = calloc(chars+1, sizeof(char));
        for(i=0;i<chars;i++)
        {
            myString[i] = mySerial.read();
            //test for EOF
            if((myString[i] == '\n') ||(myString[i] == '\r'))
            {
                //pick this...
                myString[i]=0;//strip carriage - return line feed(or skip)
                //OR pick this... (one or the other. i.e.,I do not know the requirements for your string)
                if(i<chars)
                {
                    myString[i+1] = mySerial.read() //get remaining '\r' or '\n'
                    myString[i+2]=0;//add null term if necessary
                }

                break;
            }
        }
        if(strstr(myString, "GPGGA") == NULL)
          {
               Serial.println("Not a GPGGA string");   
               //EDIT
               strcpy(globalString, "");//if failed, do not want globalString populated
          }
          else
          {    //EDIT
               strcpy(globalString, myString);
          }

    }
    //free(myString) //somewhere when you are done with it
}

Now, the return value from mySerial.available() tells you exactly how many bytes to read, you can read the entire buffer, and test for validity all in one.

ryyker
  • 22,849
  • 3
  • 43
  • 87
  • this didn't worked out for me, as it output is again undefined when i tried it.I willl use the malloc functionality definitely in my code. – shailendra Nov 08 '13 at 21:54
  • I just updated this post, did you get the lines that test for '\n\r'?, and do you know if you are supposed to keep them, or are you supposed to strip them off and append a '\0' char to terminate. A C string always needs a '\0' termination for it to be used in any C string function. – ryyker Nov 08 '13 at 21:56
  • One more thing to be added, in your code you are only printing if myString don't have GPGGA, my aim is to store the string which is coming after GPGGA only. – shailendra Nov 08 '13 at 22:24
  • @shailendra - Actually, the string is already completed in my code, just as it is. Both ways, including the '\n\r', or simply appending a '\0'. (you never told me which one is preferable, with or without the '\n\r'). If you need a way to get the string _out_ of the function, I will give you an example, see my edit. – ryyker Nov 08 '13 at 23:13
  • :well including \n\r is required.I am too using the same thing in my code but i am not using dynamic memory allocation but by both way, output is changing dramatically,i think the problem is with the output string.I used your code too, but same response.I will post the output after using your code in my code – shailendra Nov 08 '13 at 23:24
0

I have a project that will need to pull the same information out of the same sentence. I got this out of a log file

import serial
import time
ser = serial.Serial(1)

ser.read(1)
read_val = ("nothing")

gpsfile="gpscord.dat"
l=0

megabuffer=''
def buffThis(s):
        global megabuffer
        megabuffer +=s
def buffLines():
        global megabuffer
        megalist=megabuffer.splitlines()
        megabuffer=megalist.pop()
        return megalist

def readcom():
        ser.write("ati")
        time.sleep(3)
        read_val = ser.read(size=500)
        lines=read_val.split('\n')
        for l in lines:
                if l.startswith("$GPGGA"):
                        if l[:len(l)-3].endswith("*"):
                                outfile=open('gps.dat','w')
                                outfile.write(l.rstrip())
                                outfile.close()

readcom()

while 1==1:
    readcom()

answer=raw_input('not looping , CTRL+C to abort')

The result is this: gps.dat

$GPGGA,225714.656,5021.0474,N,00412.4420,W,0,00,50.0,0.0,M,18.0,M,0.0,0000*5B
0

Using "malloc" every single time you read a string is an enormous amount of computational overhead. (And didn't see the corresponding free() function call. Without that, you never get that memory back until program termination or system runs out of memory.) Just pick the size of the longest string you will ever need, add 10 to it, and declare that your string array size. Set once and done.

There are several C functions for getting substrings out of a string, strtok() using the coma is probably the least overhead.

You are on an embedded microcontroller. Keep it small, keep overhead down. :)

0
#include <stdio.h>
#include <string.h>

#define GNSS_HEADER_LENGTH 5
#define GNSS_PACKET_START '$'
#define GNSS_TOKEN_SEPARATOR ','

#define bool int
#define FALSE 0
#define TRUE 1

//To trim a string contains \r\n
void str_trim(char *str){
    while(*str){
        if(*str == '\r' || *str == '\n'){
            *str = '\0';
        }
        str++;
    }
}

/**
 * To parse GNSS data by header and the index separated by comma
 *
 * $GPGSV,1,1,03,23,39,328,30,18,39,008,27,15,33,035,33,1*5A
 * $GNRMC,170412.000,V,,,,,,,240322,,,N,V*2D
 * $GNGGA,170412.000,,,,,0,0,,,M,,M,,*57
 *
 * @data_ptr the pointer points to gps data
 * @header the header for parsing GPGSV
 * @repeat_index the header may repeat for many lines
 *      so the header index is for identifying repeated header
 * @token_index is the index of the parsing data separated by ","
 *      the start is 1
 * @result to store the result of the parser input
 *
 * @result bool - parsed successfully
 **/
bool parse_gnss_token(char *data_ptr, char *header, int repeat_index, int token_index, char *result) {
    bool gnss_parsed_result = FALSE; // To check GNSS data parsing is success
    bool on_header = FALSE;
    // For header
    int header_repeat_counter = 0;
    int header_char_index = 0; // each char in header index
    // For counting comma
    int counted_token_index = 0;
    //  To hold the result character index
    bool data_found = FALSE;
    char *result_start = result;
    char header_found[10];
    while (*data_ptr) {
        // 1. Packet start
        if (*data_ptr == GNSS_PACKET_START) {
            on_header = TRUE;
            header_char_index = 0; // to index each character in header
            data_found = FALSE; // is data part found
            data_ptr++;
        }
        // 2. For header parsing
        if (on_header) {
            if (*data_ptr == GNSS_TOKEN_SEPARATOR || header_char_index >= GNSS_HEADER_LENGTH) {
                on_header = FALSE;
            } else {
                header_found[header_char_index] = *data_ptr;
                if (header_char_index == GNSS_HEADER_LENGTH - 1) { // Now Header found
                    header_found[header_char_index + 1] = '\0';
                    on_header = FALSE;
                    if (!strcmp(header, header_found)) {
                        // Some headers may repeat - to identify it set the repeat index
                        if (header_repeat_counter == repeat_index) {
                            //printf("Header: %s\r\n", header_found );
                            data_found = TRUE;
                        }
                        header_repeat_counter++;
                    }
                }
                header_char_index++;
            }
            
        }
        // 3. data found
        if (data_found) {
            // To get the index data separated by comma
            if (counted_token_index == token_index && *data_ptr != GNSS_TOKEN_SEPARATOR) {
                // the data to parse
                *result++ = *data_ptr;
                gnss_parsed_result = TRUE;
            }
            if (*data_ptr == GNSS_TOKEN_SEPARATOR) { // if ,
                counted_token_index++; // The comma counter for index
            }
            // Break if the counted_token_index(token_counter) greater than token_index(search_token)
            if (counted_token_index > token_index) {
                break;
            }

        }
        // Appending \0 to the end
        *result = '\0';
        // To trim the data if ends with \r or \n
        str_trim(result_start);
        // Input data
        data_ptr++;
    }
    return gnss_parsed_result;
}

int main()
{
    char res[100];
    char *nem = "\
    $GNRMC,080817.000,A,0852.089246,N,07636.289920,E,0.00,139.61,270322,,,A,V*04\r\n\\r\n\
    $GNGGA,080817.000,0852.089246,N,07636.289920,E,1,5,1.41,11.246,M,-93.835,M,,*5E\r\n\
    $GNVTG,139.61,T,,M,0.00,N,0.00,K,A*2F\r\n\
    $GNGSA,A,3,30,19,17,14,13,,,,,,,,1.72,1.41,0.98,1*0A\r\n\
    $GNGSA,A,3,,,,,,,,,,,,,1.72,1.41,0.98,3*02\r\n\
    $GNGSA,A,3,,,,,,,,,,,,,1.72,1.41,0.98,6*07\r\n\
    $GPGSV,3,1,12,06,64,177,,30,60,138,15,19,51,322,18,17,42,356,27,1*68\r\n\
    $GPGSV,3,2,12,14,36,033,17,07,34,142,17,13,32,267,17,02,21,208,,1*6C\r\n\
    $GPGSV,3,3,12,15,05,286,,01,05,037,,03,03,083,,20,02,208,,1*6B\r\n\
    $GAGSV,1,1,00,7*73\r\n\
    $GIGSV,1,1,00,1*7D\r\n\
    $GNGLL,0852.089246,N,07636.289920,E,080817.000,A,A*43\r\n\
    $PQTMANTENNASTATUS,1,0,1*4F\r\n";
    
    printf("Parsing GNRMC\r\n");
    printf("===============\r\n");
    for(int i=1;i<=16;i++){
        parse_gnss_token(nem, "GNRMC", 0, i, res);
        printf("Index: %d, Result: %s\r\n", i, res);
    }
    
    printf("Parsing GNVTG (First Parameter)\r\n");
    printf("================================");
    // GNVTG - Header, 0 - Repeat Index(if header is repeating), 1 - Value Index, 
    parse_gnss_token(nem, "GNVTG", 0, 1, res);
    printf("\r\nGNVTG: %s\r\n", res);
    return 0;
}
manu
  • 21
  • 5