Keywords: GPS, Garmin Emap, serial communication, RS-232, C program, string messages, reading GPS string, parsing string message, extracting longitude and latitude from GPS string, NMEA protocal
The photo shows a Garmin eMap, a common handheld GPS unit,
that comes with a serial cable allowing you to interface
it with a PC. Using a terminal program like Windows'
Hyperterminal or DOS' Lynx, one can directly access the
GPS to view the incoming geospatial message received from
satellites orbiting Earth. Among other parameters,
the message describes the unit's position (longitude and latitude)
and speed (if carried or transported in a vehicle). Using
a terminal program to read the geospatial message isn't
very practical. The messages scroll too quickly to
read well and they contain a lot of extraneous information.
This tutorial presents DOS Turbo C code that shows you
how to serially read the GPS unit and extract desired
information from the ASCII message.
An audience interested in this tutorial could be asking the following questions:
The tutorial breakdown is as follows:
References to details of each message string are listed at the end of the tutorial. A brief description of the seven standard message strings are:
$GPGLL Geographical postiion, latitude and longitude $GPGSA GPS dillution of pecision and active satellites $GPGSV GPS satellite in view $GPGGA GPS fixed data $GPRMC Recommended minimum specific GPS/TRANSIT data $GPVTG Track made good and ground speed $GPZDA Time and date
Highlighted above, the $GPGGA string is popular examined because it contains navigational data most commonly sought after. For example, looking at the $GPDDA string in the previous DOS Lync screen shot more closely reveals the following
The format for the $GPGGA message string is:
$GPGGA,hhmmss.ss,ddmm.mmmm,n,dddmm.mmmm,e,q,ss,y.y,a.a,z,g.g,z,t.t,iii*CCending with a CR and LF (carriage return and line feed). Where we have
hhmmss.ss in UTC (coordinated universal time zone). UTC used be known as GMT. ddmm.mmmm,N latitude of the GPS position fix dddmm.mmmm,W longitude of the GPS position fix q quality of the GPS fix (1 = fix, but no differential correction) ss number of satellites being used y.y horizontal dillution of precision a.a,M GPS antenna altitude in meters g.g,M geoidal separation in meters t.t age of the deferrential correction data iiii deferential station's ID *CC checksum for the sentence
Written in accordance to ANSI standards, a C program will run on any platform. C was thus used anticipating a future design where the PC interface is replaced with a PIC microcontroller or single board computer. A DOS program was written because the software overhead incurred using Windows wasn't warranted in this design. DOS is also relatively easier to work with and the resulting code can even run on older PCs.
Software used:
Borland's Turbo C DOS compiler, which continues to be a freeware download, is used in this tutorial. To simplify serial port programming, shareware/freeware libraries exist. In my experience, many libraries don't work well or at all, and hence some caution should be exercised.
/*
FILE: gps1_5.c
AUTH: P.OH
DESC: Garmin EMap connected to COM1
REFS: Uses ibmcom serial libraries
NOTE: To compile: tcc -ml gps1_5.c ibmcom3.obj
*/
/* Defines required for serial i/o */
#define COM_PORT 1 /* Serial device connected to COM 1 */
#define SPEED 4800 /* baud rate = 4800 */
#define CR 0x0d
#define LF 0x0a
#define ESC 0x1b
#define BEEP 0x07
/* Some helpful defines */
#define SPACE 0x20
#define COMMA 0x2C
#define MAXSIZE 100 /* GPS at most, sends 80 or so chars per message string. So set maximum to 100 */
#include < stdio.h >
#include < ctype.h > /* required for the isalnum function */
#include < stdlib.h >
#include < string.h >
#include < conio.h >
#include < math.h >
#include < dos.h >
#include "ibmcom3.h" /* for serial */
/* Prototypes */
void comm_setting(void); /* Set com port */
void close_com(void); /* Close com port */
int main(void) {
unsigned char charRead; /* char read from COM port */
unsigned char stringRead[MAXSIZE]; /* Buffer collects chars read from GPS */
unsigned char tempString[MAXSIZE];
unsigned char timeString[12];
unsigned char latitudeString[11];
unsigned char latitudeCardinalString[3];
unsigned char longitudeString[12];
unsigned char longitudeCardinalString[3];
unsigned char *pChar;
unsigned char dummyChar;
unsigned long utcTime, estTime; /* Coordinated Universal Time and Eastern Standard Time */
unsigned long utcHour, estHour;
unsigned long utcMinutes, estMinutes;
unsigned long utcSeconds, estSeconds;
unsigned char lastCommaPosition;
float latitude;
int latDegrees;
float latMinutes;
float longitude;
int longDegrees;
float longMinutes;
FILE *gpsFile; /* Text file of GPS strings read */
unsigned int j, k; /* dummy variable */
unsigned int i; /* Number of chars read per GPS message string */
unsigned int numLinesRead; /* Number of GPS strings read */
dummyChar = 'A'; pChar = &dummyChar;
gpsFile = fopen("gpsData.txt", "w");
printf("Initializing port...");
comm_setting();
printf("done/n");
numLinesRead = 0;
printf("Entering while loop.../n");
do {
charRead = com_rx(); /* read char from serial port */
if(charRead == '$') { /* GPS messages start with $ char */
i = 0;
numLinesRead++;
stringRead[i] = charRead;
do {
charRead = com_rx();
if( (charRead != '/0') && (isalnum(charRead) || isspace(charRead) || ispunct(charRead)) ) {
i++;
stringRead[i] = charRead;
}
} while(charRead != CR);
/* By this point, a complete GPS string has been read so save it to file */
/* Append the null terminator to the string read */
stringRead[i+1] = '\0';
/* Analyze string that we collected */
j = 0;
pChar = stringRead;
while(*(pChar+j) != COMMA) {
tempString[j] = *(pChar+j);
j++;
}
tempString[j] = '\0';
/* Check if string we collected is the $GPGGA message */
if(tempString[3] == 'G' && tempString[4] == 'G' && tempString[5] == 'A') {
/*
Found GPGGA string. It has 14 commas total. Its NMEA sentence structure is:
$GPGAA,hhmmss.ss,ddmm.mmmm,n,dddmm.mmmm,e,q,ss,y.y,a.a,z,g.g,z,t.t,iii*CC
| | | | | | | | | | | | | | |
0 1 2 3 4 5 6 7
0123456789012345678901234567890123456789012345678901234567890123456789012
where:
GPGAA : GPS fixed data identifier
hhmmss.ss : Coordinated Universal Time (UTC), also known as GMT
ddmm.mmmm,n : Latitude in degrees, minutes and cardinal sign
dddmm.mmmm,e : Longitude in degrees, minutes and cardinal sign
q : Quality of fix. 1 = there is a fix
ss : Number of satellites being used
y.y : Horizontal dilution of precision
a.a,M : GPS antenna altitude in meters
g.g,M : geoidal separation in meters
t.t : Age of the defferential correction data
iiii : Deferential station's ID
*CC : checksum for the sentence
*/
pChar = stringRead;
/* Get UTC time */
j = 7; /* start of time field */
k = 0;
while(*(pChar+j) != COMMA) {
timeString[k] = *(pChar+j);
j++;
k++;
}
lastCommaPosition = j;
timeString[k] = '\0';
sscanf(timeString, "%ld", &utcTime);
utcHour = (utcTime/10000); /* extract Hours from long */
utcMinutes = (utcTime - (utcHour*10000))/100; /* extract minutes from long */
utcSeconds = utcTime - (utcHour*10000) - (utcMinutes*100); /* extract seconds from long */
if(utcHour >= 4 && utcHour <= 23) estHour = utcHour - 4;
else estHour = utcHour + 20;
estMinutes = utcMinutes;
estSeconds = utcSeconds;
/* NB: %02ld formats long to print 2 chars wide, padding with 0 if necessary */
printf("%02ld:%02ld:%02ld UTC = %02ld:%02ld:%02ld EST", utcHour, utcMinutes, utcSeconds, estHour, estMinutes, estSeconds);
/* Get lattitude: ddmm.mmmm */
pChar = stringRead;
j = lastCommaPosition + 1;
k = 0;
while(*(pChar+j) != COMMA) {
latitudeString[k] = *(pChar+j);
j++;
k++;
}
lastCommaPosition = j;
latitudeString[k] = '\0';
sscanf(latitudeString, "%f", &latitude);
latDegrees = (int)(latitude/100);
latMinutes = (float)(latitude - latDegrees*100);
printf("/t%02d DEG/t%2.4f MIN", latDegrees, latMinutes);
/* Get lattitude Cardinal direction */
pChar = stringRead;
j = lastCommaPosition + 1;
k = 0;
while(*(pChar+j) != COMMA) {
latitudeCardinalString[k] = *(pChar+j);
j++;
k++;
}
lastCommaPosition = j;
latitudeCardinalString[k] = '\0';
printf(" %s", latitudeCardinalString);
/* Get longitude: dddmm.mmmm */
pChar = stringRead;
j = lastCommaPosition + 1;
k = 0;
while(*(pChar+j) != COMMA) {
longitudeString[k] = *(pChar+j);
j++;
k++;
}
lastCommaPosition = j;
longitudeString[k] = '\0';
sscanf(longitudeString, "%f", &longitude);
longDegrees = (int)(longitude/100);
longMinutes = (float)(longitude - longDegrees*100);
printf("/t%03d DEG/t%2.4f MIN", longDegrees, longMinutes);
printf("/n");
} /* else not a GPGGA sentence */
fprintf(gpsFile, "%d: (%d) %s/n", numLinesRead, i, stringRead);
} /* otherwise not a $ character... so loop back until one arrives */
} while(!kbhit());
printf("Exiting...");
close_com(); /* Finished with serial port so close it */
fclose(gpsFile);
printf("done/n");
return (0);
} /* end of main */
void comm_setting(void) {
int dummy;
dummy = com_install(COM_PORT);
if(dummy != 0) {
switch (dummy) {
case 1 : printf("Invaid port number/n");
break;
case 2 : printf("No UART fot specified port/n");
break;
case 3 : printf("Drivers already installed/n");
break;
default : printf("Err #%d/n", dummy);
break;
}
exit(1);
} com_raise_dtr();
com_set_speed(SPEED);
com_set_parity(COM_NONE, STOP_BIT_1);
}
void close_com(void) {
com_lower_dtr();
com_deinstall();
}
To compile, at the DOS prompt type tcc -ml gps1_5.c ibmcom3.obj. This of course assumes that ibmcom3.obj is in the same directory as gps1_5.c. the -ml option invoke Turbo C's large memory model. Running the executable (gps1_5.exe) will display the UTC time, the Eastern Standard time (EST) equivalent and both latitude and longitude coordinates. Additionally, all message strings output by your GPS receiver are saved into an ASCII file named gpsData.txt.
A while loop is entered, where the statement charRead = com_rx(); serially reads a character and checks if it begins with a dollar sign. If so, this indicates a new GPS message string has been received and more characters are read until a carriage return (CR) is found.
tempString holds the GPS message string that was serially
read. If it is a $GPGGA message, additional reading is done, where
we know that commas separate geospatial data. sscanf is
used to extract numerical data from the ASCII characters.
The Turbo C program featured is an example where only
UTC time, longitudinal and latitude coordinates were extracted
from the $GPGGA message string. One could customize the
code to seek other GPS message strings to extract altitude,
or ground speed for example. DOS and C were used keeping
expansion in mind; compact GPS-based equipment can be
made by using a PIC microcontroller to replace a PC. This
tutorial was not meant to describe GPS in detail, but rather
share how to serially interface a GPS for one's own hardware
interests. Some references for GPS follow to best conclude
this tutorial and leave you with imaginative possibilities.
Where To Go From Here
A handheld GPS receiver that has a serial interface allows
message strings to be viewed with a PC terminal program like
Hyperterminal. This tutorial showed that a DOS program can
be written to display, store, extract or parse the message
string. Doing so allow you to leverage the GPS' ability
to report its location to design equipment for applications
demanding navigational, mapping and localization data.
References