GPS help please

Hi all,

I am very new to programming and Arduino.

Could someone please help with some example code for parsing NMEA from a GPS (Lycosys LS20031).

I have used Mikal Hart's TinyGPS library with good results. However, I am only interested in the string for "course over ground" headed $GPVTG and TinyGPS uses GPRMC and GPGGA.

I am using a Duemilanove with the GPS on the hardware serial port and my LCD on NewSoftSerial.

Thanks, Matt

Why don't you post the code you are now using to see the GPS data, and some sample output. We can then tell you how to extract just the bit that is of interest.

The reason that this is necessary is that you may or may not currently be storing the GPS output in an array. If you are, then making a string from that array, and parsing it are easy. If you are not, then it will be necessary to do that first.

Okay, I hope it’s not too big;
As you can see I’m only interested in speed and course but the $GPVTG string outputs those values directly whereas the TinyGPS code uses multipliers to change the speed in Knots to other units.

/*
TinyGPS - a small GPS library for Arduino providing basic NMEA parsing
Copyright (C) 2008-9 Mikal Hart
All rights reserved

NewSoftSerial.h - Multi-instance software serial library
Copyright (c) 2006 David A. Mellis. All rights reserved.
– Interrupt-driven receive and other improvements by ladyada
– Tuning, circular buffer, derivation from class Print,
multi-instance support, porting to 8MHz processors,
various optimizations, PROGMEM delay tables, inverse logic and
direct port writing by Mikal Hart

Most of this code has been taken from Mikal Hart’s normal use of
TinyGPS example code

This modification by Matthew Gray
*/

#include <NewSoftSerial.h>
#include <TinyGPS.h>

TinyGPS gps;
NewSoftSerial lcd(2, 3);

void gpsdump(TinyGPS &gps);
bool feedgps();
void printFloat(double f, int digits = 1);

void setup()
{
Serial.begin(57600);
lcd.begin(115200);

lcd.print(0x7C,BYTE);
lcd.print(0x00,BYTE); // clear LCD

lcd.print(0x7C,BYTE);
lcd.print(0x02,BYTE);
lcd.print(0x64,BYTE); // set backlight to 50%

lcd.print(" SPEEDO TEST "); // print title

lcd.print(0x7C,BYTE);
lcd.print(0x18,BYTE);
lcd.print(0x00,BYTE);
lcd.print(0x7C,BYTE);
lcd.print(0x19,BYTE);
lcd.print(0x35,BYTE);
lcd.print("Course: "); // write heading

lcd.print(0x7C,BYTE);
lcd.print(0x18,BYTE);
lcd.print(0x00,BYTE);
lcd.print(0x7C,BYTE);
lcd.print(0x19,BYTE);
lcd.print(0x2B,BYTE);
lcd.print("Knots : "); // write heading

lcd.print(0x7C,BYTE);
lcd.print(0x18,BYTE);
lcd.print(0x00,BYTE);
lcd.print(0x7C,BYTE);
lcd.print(0x19,BYTE);
lcd.print(0x21,BYTE);
lcd.print("Mph : "); // write heading

delay(10000); // delay needed so the GPS doesn’t write initial values to LCD

}

void loop()
{
bool newdata = false;

{
if (feedgps())
newdata = true;
}

if (newdata)
{

gpsdump(gps);

}
}

void printFloat(double number, int digits)
{
// Handle negative numbers
if (number < 0.0)
{
lcd.print(’-’);
number = -number;
}

// Feed single digit space to keep decimal point aligned
if (number < 100.0)
{
lcd.print(’ ');
}

// Feed single digit space to keep decimal point aligned
if (number < 10.0)
{
lcd.print(’ ');
}

// Round correctly so that print(1.999, 2) prints as “2.00”
double rounding = 0.5;
for (uint8_t i=0; i<digits; ++i)
rounding /= 10.0;

number += rounding;

// Extract the integer part of the number and print it
unsigned long int_part = (unsigned long)number;
double remainder = number - (double)int_part;
lcd.print(int_part);

// Print the decimal point, but only if there are digits beyond
if (digits > 0)
lcd.print(".");

// Extract digits from the remainder one at a time
while (digits-- > 0)
{
remainder *= 10.0;
int toPrint = int(remainder);
lcd.print(toPrint);
remainder -= toPrint;
}
}

void gpsdump(TinyGPS &gps)
{
feedgps();

// Display Course
lcd.print(0x7C,BYTE);
lcd.print(0x18,BYTE);
lcd.print(0x3C,BYTE);
lcd.print(0x7C,BYTE);
lcd.print(0x19,BYTE);
lcd.print(0x35,BYTE);
printFloat(gps.f_course());

// Display Speed in Knots
lcd.print(0x7C,BYTE);
lcd.print(0x18,BYTE);
lcd.print(0x3C,BYTE);
lcd.print(0x7C,BYTE);
lcd.print(0x19,BYTE);
lcd.print(0x2B,BYTE);
printFloat(gps.f_speed_knots());

// Display speed in Miles Per Hour
lcd.print(0x7C,BYTE);
lcd.print(0x18,BYTE);
lcd.print(0x3C,BYTE);
lcd.print(0x7C,BYTE);
lcd.print(0x19,BYTE);
lcd.print(0x21,BYTE);
printFloat(gps.f_speed_mph());

delay(500); // So the values don’t change too often to be read by the user

}

bool feedgps()
{
while (Serial.available())
{
if (gps.encode(Serial.read()))
return true;
}
return false;
}

Most of the BYTE Hex values I am sending to the LCD are commands for positioning of the text as it is a Graphic LCD with a serial backpack from Sparkfun. A user by the name of SummoningDark on the Sparkfun forum has written new firmware for the backpack that expands the user tool set. In particular I need a large font for this boat speedo. None of this is included yet.

I don't really process any of the NMEA data myself because it is all done within TinyGPS. I had a look through the library and there is just no way I am capable of modifying it myself to get the $GPVTG string. I was hoping to find out how to extract the string headed by $GPVTG with the comma separators and arrange the values into simple variables and ignore/discard the rest. I can post up the string format if it helps?

It doesn’t look like it would be all that difficult to change the library to parse other NMEA data types, like GPVTG.

I don’t have a GPS so I can’t tell how the values in the GPVTG format differ from the values in the GPRMC and GPGGA format. The comments in the TinyGPS header file indicate that the course value is in hundredths of a degree and that the speed value is in hundredths of a knot, in the GPRMC and GPGGA formats.

Having the course and speed in known units, it should be easy to express them in other units.

Can you post a sample GPRMC and GPVTG string, and explain what the GPVTG string tells you that the GPRMC does not?

Here are some snippits;

GGA - essential fix data which provide 3D location and accuracy data.

$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,*47

Where:
GGA Global Positioning System Fix Data
123519 Fix taken at 12:35:19 UTC
4807.038,N Latitude 48 deg 07.038’ N
01131.000,E Longitude 11 deg 31.000’ E
1 Fix quality: 0 = invalid
1 = GPS fix (SPS)
08 Number of satellites being tracked
0.9 Horizontal dilution of position
545.4,M Altitude, Meters, above mean sea level
46.9,M Height of geoid (mean sea level) above WGS84
ellipsoid
(empty field) time in seconds since last DGPS update
(empty field) DGPS station ID number
*47 the checksum data, always begins with *

Then there’s;

RMC - NMEA Recommended Minimum, which will look similar to:

$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A

Where:
RMC Recommended Minimum sentence C
123519 Fix taken at 12:35:19 UTC
A Status A=active or V=Void.
4807.038,N Latitude 48 deg 07.038’ N
01131.000,E Longitude 11 deg 31.000’ E
022.4 Speed over the ground in knots
084.4 Track angle in degrees True
230394 Date - 23rd of March 1994
003.1,W Magnetic Variation
*6A The checksum data, always begins with *

And as an alternative;

VTG - Velocity made good. The gps receiver may use the LC prefix instead of GP if it is emulating Loran output.

$GPVTG,054.7,T,034.4,M,005.5,N,010.2,K*48

where:
VTG Track and ground speed
054.7,T True track made good (degrees)
034.4,M Magnetic track made good
005.5,N Ground speed, knots
010.2,K Ground speed, Kilometers per hour
*48 Checksum

So the VTG sentence is a lot smaller and only has the fields I need with the accuracy I need, neat and tidy. I also have the ability to turn off all of the other sentences from the GPS using a terminal program which will increase the sample rate to 5Hz which in turn will give me better accuracy when I start using the system as a cruise control in the future. The string also terminates with .

If I have only one string being read on the serial port it must be easier to parse? Is the Arduino string library of any use to me? How do I extract the values into useable variables?

If you remove the TinyGPS library, and simply collect the GPS output into a string (using the String library), you can use the indexOf and substring methods to extract, as strings, the portions of interest.

Thanks mate I will have a play with the library and see what I can manage.

Matt

Ok I have tried and failed :-[

I have set my GPS to output only the GPVTG sentence and using some code I found on here I am getting a lovely stable string like this;

GPVTG,331.15,T,,M,1.44,N,2.67,K,A*3A

Without any proper C++ experience I have been unable to separate the values and need some help.

I borrowed some more code;

parseptr = gps_buffer;                     // offload the buffer into temp variable
       
header = parsedecimal(parseptr);     // parse the NMEA heading
parseptr = strchr(parseptr, ',')+1;     // move past the ","

courseT = parsedecimal(parseptr);    // parse the true course
parseptr = strchr(parseptr, ',')+1;     // move past the ","

units = parsedecimal(parseptr);        // parse the units
parseptr = strchr(parseptr, ',')+1;     // move past the ","

courseM = parsedecimal(parseptr);   // parse the magnetic course
parseptr = strchr(parseptr, ',')+1;     // move past the ","

units = parsedecimal(parseptr);        // parse the units
parseptr = strchr(parseptr, ',')+1;     // move past the ","

speedKn = parsedecimal(parseptr);  // parse the speed in knots
parseptr = strchr(parseptr, ',')+1;    // move past the ","

units = parsedecimal(parseptr);       // parse the units
parseptr = strchr(parseptr, ',')+1;    // move past the ","
        
speedKm = parsedecimal(parseptr); // parse the speed in kilometres
parseptr = strchr(parseptr, ',')+1;    // move past the ","

units = parsedecimal(parseptr);       // parse the units
parseptr = strchr(parseptr, ',')+1;    // move past the ","

fix = parsedecimal(parseptr);          // parse the fix status N=no fix / A=autonomous / D=DGPS / E=DR

The string I posted above is stored in "gps_buffer" and I have declared - uint16_t header, courseT, courseM, speedKn, speedKm, units, fix;

When I try to compile I get - error: 'parsedecimal' was not declared in this scope. Which is obvious but it wasn't declared in the example code and I can't find it in any library.

Can someone please help me with the function I need to move the values between the comma's into the variables I have created? Or possibly help with the code I need to make "parsedecimal" work? I don't need the header or the units but I figured I had to put them somewhere anyway.

Matt

Where did you get this code? There is a parsedecimal function in TinyGPS, as I recall.

I can't find it again now but someone was using it to separate servo positions from a string of multiple vaues separated by commas.

Never mind about that code I'm giving up on it. Including the TinyGPS library doesn't help.

I'm going to spend some more time trying to use indexOf as you suggested earlier. if I just wanted to Serial.print them it would be much easier but I need to work with the values before I output them. I don't think substring will work because as the values increase by tens, hudreds, thousands the number of characters in the string increases. i.e. they are not static like date and time values.

If anyone has some ideas please let me know.

If you find the index of the comma that precedes a value like latitude, and the value that follows it, you can extract a string that contains just the latitude value.

Then, you can look at the atoi, atol, and atof functions that convert a string (a) to (to) an integer (i), long (l), or float (f).

Never mind about that code I'm giving up on it. Including the TinyGPS library doesn't help.

The reason for pointing you to the TinyGPS library is that it contains all the code needed to parse a string in one format, and return the numerical values in the string as integers, longs, or floats.

The format of your strings is different, and contain values in a different order than what TinyGPS is written to parse. But the principles of parsing the string are exactly the same.

True re:TinyGPS but I find it very confusing to read because it is in a library format and I have no experience. I can find the sections of code that I think are doing the job but I don't understand how they increment then allocate the values to each variable. If I try and copy the code from the .cpp file into a program I get all sorts of definition errors that I haven't been able to work through.

If you find the index of the comma that precedes a value like latitude, and the value that follows it, you can extract a string that contains just the latitude value.

You make it sound so simple but this is where I am having the trouble.

As far as finding the first comma goes I can do that OK but what function do I use to copy the following value to a variable? Then how can I increment to the next variable and so on. Can I define a map of the string? If I read directly from the serial port I can probably get one character at a time beyond the comma but then can I do that from the "gps_buffer" area defined is my code as a char?

You make it sound so simple but this is where I am having the trouble.

Well, it really is simple.

String inString = "$GPSDATA,45,67,99,34";

int commaPos = inString.indexOf(','); // Find the first comma
int secondCommaPos = inString.indexOf(',', commaPos+1); // Find the next comma
String valStg = inString.substring(commaPos+1, secondCommaPos-1); // Extract string between commas
int val = atoi(valStg.getChars());

Obviously you won't be hardcoding strings like this, but it's just an example.

The indexOf call with one argument starts at the beginning of the string looking for the specified character (,). In this example, commaPos is set to 8.

The indexOf call with two arguments starts looking for the specified character somewhere other than at the beginning of the array. In this case, we want it to start looking for the comma starting right after the last one. In this example, secondCommaPos will be set to 11.

The substring function returns the string starting at the position specified by the first argument (8+1=9 in this example, or the 4), and ending at the position specified by the second argument (11-1 = 10 in this example, or the 5). Since we want the string starting just after the first comma, and ending just before the next comma, we add and subtract 1 to/from the comma positions. In this example, valStg will be set to "45".

Then, the atoi function is called to convert the string to a number. Since atoi does not know how to deal with objects of the string class, we need to extract the actual C string from the string instance, using the getChars() method. In this example, val would be 45.

The atof or atol functions could have been used, too.

The process could be performed in a loop to extract all numeric values, if they were all the same type.

You'll need to know something about the format of the GPS data to extract useful data, but I think you already know that.

The other way you could use the string class is to locate the first comma.

int commaPos = inString.indexOf(',');

Then, extract the rest of the string:

String rest = inString.substring(commaPos+1);

In the example, rest would be "45,67,99,34".

Now, in a loop, you can find a comma, and extract substrings starting at 0 and ending at commaPos-1, to get each value.

Thanks so much for that. I assure you I don't find it that simple. I am starting to manipulate code to get the result I need but the basic format is foreign to me.

 //----------let's process buffer-------   

        Serial.println(gps_buffer);

        String inString = gps_buffer;
        

        int commaPos = inString.indexOf(','); // Find the first comma
        int secondCommaPos = inString.indexOf(',', commaPos+1); // Find the next comma
        String valStg = inString.substring(commaPos+1, secondCommaPos-1); // Extract string between commas
        float True_Course= atof(valStg.getChars());

        Serial.println(True_Course);

Ok so that code displays this on the serial monitor:-

GPVTG,0.00,T,,M,0.00,N,0.00,K,N*32

0.00

Can you help me with the loop code? I guess I need to use a for loop but what are the boundaries? e.g. for (i = 0; i !=*; i++) with i being a char and * appearing before the checksum. How would I need to read the values out? Keeping in mind that the string is in the format (sentence type, float true course, char units, float magnetic course, char units, float speed in knots, char units, float speed in Kmh, char units, char fix type *checksum)

And all I want from all that is (float true course, float speed in knots and char fix type)

Since your format is fixed, it might be easier to create some functions.

String ExtractType(String gpsIn, String &type)
{
   int commaPos = gpsIn.indexOf(',');
   type = gpsIn.substring(0, commaPos-1);
   return gpsIn.substring(commaPos+1);
}

String ExtractTrueCourse(String gpsIn, float &trueCourse)
{
   int commaPos = gpsIn.indexOf(','); // Find the first comma
   String valStg = gpsIn.substring(0, commaPos+1); // Extract string up to comma
   trueCourse= atof(valStg.getChars());

   return gpsIn.SubString(commaPos+1); // return the rest of the string
}

Then, call these like this:

String type;
float True_Course;

String rest = ExtractType(inString, type);
rest = ExtractTrueCourse(rest, True_Course);

Create additional functions, each of which removes one token from the string, converts it to the appropriate type, and returns the rest of the string. Then, call them in order.

Ok so I added your code and adjusted it so the first return of the string is commaPos+6 because the gps is not outputting a magnetic course value so I can skip over to the comma before speed knots.(The number of characters won't change.)

It compiled fine

Then I added the call directly beneath it and I got an error: redefinition of 'float True_Course' so I deleted the definition above void setup(). The next error: 'inString' was not declared in this scope. What have I done wrong and how do I call subsequent values? Is what I've done to the function OK?

String ExtractTrueCourse(String gpsIn, float &trueCourse)
{
   int commaPos = gpsIn.indexOf(','); // Find the first comma
   String valStg = gpsIn.substring(0, commaPos+1); // Extract string up to comma
   trueCourse= atoi(valStg.getChars());

   return gpsIn.substring(commaPos+6); // return the rest of the string
}
String ExtractSpeedKn(String gpsIn, float &speedKn)
{
   int commaPos = gpsIn.indexOf(','); // Find the first comma
   String valStg = gpsIn.substring(0, commaPos+1); // Extract string up to comma
   speedKn= atof(valStg.getChars());

   return gpsIn.substring(commaPos+1); // return the rest of the string
}
String type;
float True_Course;

String rest = ExtractType(inString, type);
rest = ExtractTrueCourse(rest, True_Course);

The code below the functions was meant to go in loop(), in place of the code that you had in there to extract just the first value.

I don't understand?

In reply #14, you have some code that copies the GPS data into a string. Then, you have some code to parse the string.

In reply #15, I suggested that you define some functions to do the parsing, and I provided some code to call those functions.

The functions go at after the loop function, and the code to call those functions goes in place of the code that parses the string that you showed in reply #14.