GPS Serial Communications

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.

Motivation and Audience

A GPS (Global Positioning System) receiver reports its location on Earth. The longitudinal and lateral coordinates can be used for applications like navigating vehicles, coordinating search and rescue efforts and mapping trails and exploring new terrains. Custom building devices that use GPS are possible - one designs around systems like the Trimble Lassen LP starter kit ($595 USD) or a Motorola OnCore development kit. A more affordable option is to purchase and design around a handheld GPS receiver, leveraging its serial interface port.

An audience interested in this tutorial could be asking the following questions:

This tutorial does not explain GPS theory, discuss GPS hardware or describe NMEA message strings in great detail. Some references for these are given at the end of the tutorial.

The tutorial breakdown is as follows:

GPS Message Strings and Terminal Programs

The National Marine Electronics Association (NMEA) defined a RS-232 communcation standard for devices that include GPS receivers. The GPS receivers can output geospatial location, time, headings and navigation-relevant information in the form of ASCII comma-delimited message strings. Hyperterminal, bundled with Windows, can be used to view these message strings. For DOS there's Lync, a popular shareware program for dialing modems via the serial port.

GPS and Hyperterminal

Handheld GPS receivers, like the Garmin eMap, come equipped with a cable that plugs into your PC's serial port. Hyperterminal is a communications program that comes with Windows 95/98/ME/NT and 2K. Your GPS receiver's handbook probably describes the necessary terminal settings; a typical port setting is 4800 baud, 8N1 (eight data bits, no parity, 1 stop bit) with no flow control. With eMap hooked up with serial port COM1, your Hyperterminal display should look like the screen shot below, with message strings updated every 2 seconds or so.

GPS and DOS Lync

Lync is DOS shareware popular for PC modem dialups. Downloading and installing lync20.zip will allow you to display GPS receiver message strings in DOS. My eMap was plugged into my PC's COM1 serial port and Lync was was configured at 4800 baud, 8N1 and no flow control. The screen shot below is the result; GPS message strings could be seen with a new message displayed every 2 seconds or so.

Dissecting the GPS Message String

The screen shots above showed message string output from a GPS receiver. The NMEA standard dictates how each string is formed with a dollar sign ($) leading each new GPS message.

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*CC

ending 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

Extracting or Parsing the GGA string

A program can be written to serially read the incoming message strings and determine if it's a GPGGA message. If so, the program can extract and/or display only geospatial information we might want, like the time and longitude/latitude coordinates.

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.

Source Code


Turbo C code
Note: download gps1_5.c rather than cutting and pasting from below.

/*
   FILE: gps1_5.c
   AUTH: P.OH
   DESC: Garmin EMap connected to COM1
   REFS: Uses ibmcom serial libraries
   NOTE: To compile: tcc -ml scon1_1.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.

Code Description

gps1_5.c begins by opening a file gpsData.txt which will save all GPS message strings in ASCII. Next, the serial port is opened using a function prototype comm_setting() which invokes functions found in the IBMCOM library.

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.

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.

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.

References