NMEA Software Standard: Parameter Description $ ID MSG, DN CS (CR) (LF)
NMEA Software Standard: Parameter Description $ ID MSG, DN CS (CR) (LF)
NMEA Software Standard: Parameter Description $ ID MSG, DN CS (CR) (LF)
Introduction
A software standard for commercial GPS receivers is NMEA 0183 (www.nmea.org). This
is a serial protocol using ASCII sentences to convey information from the device, in
this case, a GPS receiver. The standard baud rate is 4800 with a word length of 8 (bit 7
cleared), 1 stop bit, and no parity. PCM-GPS boards as shipped, default to this NMEA 0183
standard and begin transmitting NMEA sentences immediately after power-up.
The Trimble Lassen IQ GPS module is also capable of transmitting and receiving serial
data in a Trimble proprietary format known as TSIP, the factory standard. This is a binary
protocol which ordinarily runs at 9600 baud, an 8-bit word, and odd parity. Users requiring
a TSIP interface to the GPS should contact WinSystems Technical Support for details on
converting the PCM-GPS to TSIP.
The sections that follow will document the NMEA sentences sent by the PCM-GPS
and provide C source code examples for decoding and utilizing the NMEA data in an
application.
$IDMSG,D1,D2,D3,D4,....,Dn*CS[CR][LF]
Parameter Description
The $ signifies the start of a sentence.
$
The talker identification is a two letter mnemonic which
describes the source of the navigation information. The
ID
identification for GPS is GP.
The carriage return [CR] and the line feed [LF] combination
[CR][LF]
terminate the sentence.
NMEA 0183 sentences vary in length, but each sentence is limited to 79 characters or
less. This length limit excludes the $ and the [CR][LF] characters. The data field block,
including delimiters is limited to 74 characters or less.
$GPGGA,hhmmss.ss,llll.lll,a,nnnnn.nnn,b,t,uu,v.v,w.w,M,x.x,M,y.y,zzzz*hh<CR><LF>
Parameter Description
hh Checksum
$GPGLL, llll.lll,a,yyyyy.yyy,b,hhmmss.ss,A,i,*hh<CR><LF>
Parameter Description
Degreesminutes.decimal : Latitude value is 2 fixed digits of
degrees, 2 fixed digits of minutes and a variable number of
digits for decimal fractions of minutes. Leading zeros are
IIII.III
always included for degrees and minutes to maintain fixed
length. The decimal point and decimal fraction value are
optional.
Hemispherical orientation N or S. Single character indicates
a
latitude hemisphere.
hh Checksum
$GPGSA,a,b,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,c.c,d.d,e.e*hh<CR><LF>
Parameter Description
hh Checksum
$GPGSV,a,b,c,d1,e1,f1,g1,d2,e2,f2,g2,d3,e3,f3,g3,d4,e4,f4,g4*hh<CR><LF>
Parameter Description
hh Checksum
$GPRMC,hhmmss.ss,A,llll.ll,a,yyyyy.yy,b,c.c,d.d,eeeeee,f.f,g,i*hh<CR><LF>
Parameter Description
A = Autonomous Mode
D = Differential Mode
i E = Estimated (Dead reckoning) Mode
M = Manual Input Mode
S = Simulation Mode
N = Data not valid.
hh Checksum
$GPVTG,a.a,T,b.b,M,c.c,N,d.d,K,i*hh<CR><LF>
Parameter Description
A = Autonomous Mode
D = Differential Mode
i E = Estimated (Dead reckoning) Mode
M = Manual Input Mode
S = Simulation Mode
N = Data not valid.
hh Checksum
$GPZDA,hhmmss.ss,dd,mm,yyyy,x1,x2*hh<CR><LF>
Parameter Description
hoursminutesseconds.decimal : 2 Fixed digits for hours,
2 fixed digits for minutes, 2 fixed digits for seconds and
a variable number of digits for decimal seconds. Leading
zeros are always included for hours, minutes, and seconds
hhmmss.ss
to maintain fixed length. The decimal point and the
associated decimal value are optional if full resolution is not
needed.
yyyy Year
Unused (NULL). A GPS receiver cannot independently
x1.xy identify the local time zone offsets.
hh Checksum
WARNING: If the UTC time is not available, time output will be in GPS
time until the UTC offset value is collected from the GPS satellites.
When the offset becomes available, the time will jump to UTC time.
NOTE: GPS time can be used as a time tag for the PPS output. The ZDA
sentence comes out 100-500 ms after the PPS.
1. To resolve time. The atomic clocks and the ground updates mean that GPS derived time is
probably the most accurate time available to a standard embedded application program.
2. To resolve a position laterally and/or vertically. To know where an object is within a few
meters on or above the surface of the earth is useful in a wide variety of tracking applications.
3. To navigate to a location. The position information, when combined with current course
and speed, allows an application to control and recognize its relative position and distance to
any other location within the sphere of the GPS constellation.
To this end, WinSystems provides a sample NMEA 0183 decoding function in C called parse_
nmea() in the file gps_func.c This function is called with a string argument holding the
string to decode, and then decodes the NMEA sentence type and loads the appropriate global
variables with the results from that data sentence. Some variables are affected by more than
one sentence type, in which case the variable(s) holds the result from the most recently parsed
NMEA sentence. The following list gives all of the global variables, their data type, and the
NMEA sentence type that will affect their value(s).
An application needs to simply extract the serial data into [CR][LF] terminated strings, call the
parse_nmea() function, and then utilize the resultant variables as needed.
float gps_differential_age v.v GGA
int gps_differential_station_id (000-1023) GGA
int gps_prn_number[3][4] xx GSV
int gps_elevation[3][4] (0-90) GSV
int gps_azimuth[3][4] (000-359) GSV
int gps_snr[3][4] xx GSV
char gps_rmc_status A or V RMC
float gps_sog x.x RMC, VTG
float gps_track x.x RMC, VTG
long gps_rmc_date ddmmyy RMC
float gps_mag_variation x.x RMC
char gps_variation_direction E or W RMC
char gps_sys_mode_indicator ADEMSN RMC
float gps_track_magnetic x.x VTG
float gps_sog_kilometers x.x VTG
In addition to the NMEA global variables there are several globals defined to support the
serial port functions. These globals are listed here. Refer to the source code for the sample
application NMEA3.C for example usage of the serial port support functions.
Parameter Description
Unsigned port_address The Base address of the UART
Function Prototype
int parse_nmea(char * string)
int com_read(void)
void com_flush(void)
void com_close(void)
void com_init(void)
void com_putch(char c)
All of these items are declared in the header file gps_func.h, which may be included by
an application desiring to utilize these routines as supplied. Refer to the source code for
gps_func.c or nmea3.c for clarification of any usage details.
An actual MS-DOS sample application program is provided both in C source form and in
a precompiled .EXE file. The program is invoked at the DOS command line with optional
arguments such as:
Where 300 is the hex address of the COM port configuration for the GPS, 5 is the IRQ level
configuration for the GPS and the 4800 specifies the desired baud rate. If no arguments
are given, the program defaults to COM1 values (i.e., 3F8 4 4800).
The application was built and tested using the Borland C/C++ compiler Version 3.1 with a
build command line of :
NMEA3.EXE illustrates the usage of the global variables for display purposes and also
serves as a test/diagnostic utility to show the current state of the GPS receiver and its
satellite reception.
The middle of the screen shows the raw NMEA 0183 sentences while the rest of the screen
displays a number of the GPS global variables.
#include <stdio.h>
#include <dos.h>
#include <conio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
float gps_utc_time;
int gps_day;
int gps_month;
int gps_year;
float gps_latitude;
char gps_lat_reference;
float gps_longitude;
char gps_long_reference;
int gps_quality;
int gps_satellite_count;
float gps_hdop;
float gps_altitude;
char gps_altitude_unit;
float gps_separation;
char gps_separation_unit;
float gps_differential_age;
int gps_differential_station_id;
char gps_op_mode;
char gps_fix_mode;
int gps_satellites_in_use[12];
float gps_pdop;
float gps_vdop;
int gps_gsv_message_count;
int gps_gsv_message_number;
int gps_total_sats_in_view;
int gps_prn_number[3][4];
int gps_elevation[3][4];
int gps_azimuth[3][4];
int gps_snr[3][4];
char gps_rmc_status;
float gps_sog;
float gps_track;
long gps_rmc_date;
float gps_mag_variation;
char gps_variation_direction;
char gps_sys_mode_indicator;
float gps_track_magnetic;
float gps_sog_kilometers;
unsigned port_address;
unsigned int_number;
unsigned baud_rate;
unsigned vector;
unsigned mask_val;
long old_vector;
unsigned pic_base = 0x20;
/* This function accepts a string believed to contain standard NMEA 0183 sentence
data and parses those fields and loads the appropriate global variables with the results.
*/
field_count = 0;
#ifdef DEBUG
printf(Parsing NMEA string : <%s>\n,string);
#endif
/* NMEA 0183 fields are delimited by commas. The my_token function returns
pointers to the fields.
*/
field[0] = my_token(string,,);
#ifdef DEBUG
if(field[0])
printf(Token = <%s>\n,field[0]);
#endif
field_count++;
while(1)
{
/* Contiue retrieving fields until there are no more (NULL) */
field[field_count] = my_token(NULL,,);
if(field[field_count] == NULL)
break;
#ifdef DEBUG
printf(Token = <%s>\n,field[field_count]);
#endif
field_count++;
}
#ifdef DEBUG
printf(%d fields parsed\n,field_count);
#endif
if(field_count)
{
/* Check the first field for the valid NMEA 0183 headers */
if(strcmp(field[0],$GPGGA) == 0)
{
/* Retrieve the values from the remaining fields */
gps_utc_time = atof(field[1]);
gps_latitude = atof(field[2]);
gps_lat_reference = *(field[3]);
gps_longitude = atof(field[4]);
#endif
if(strcmp(field[0],$GPGLL) == 0)
{
/* Retrieve the values from the remaining fields */
gps_latitude = atof(field[1]);
gps_lat_reference = *(field[2]);
gps_longitude = atof(field[3]);
gps_long_reference = *(field[4]);
gps_utc_time = atof(field[5]);
gps_status = *(field[6]);
gps_mode_indicator = *(field[7]);
#ifdef DEBUG
printf(NMEA string GPGLL recognized\n);
printf(Position : %8.3f %c %8.3f %c\n,gps_latitude,gps_lat_reference,
gps_longitude,gps_long_reference);
printf(Time = %9.2f\n,gps_utc_time);
if(strcmp(field[0],$GPGSA) == 0)
{
/* Retrieve the values from the remaining fields */
gps_op_mode = *(field[1]);
gps_fix_mode = *(field[2]);
gps_pdop = atof(field[15]);
gps_hdop = atof(field[16]);
gps_vdop = atof(field[17]);
printf(Satelites in use : );
#endif
for(x=0; x<12; x++)
{
gps_satellites_in_use[x] = atoi(field[x+3]);
#ifdef DEBUG
if(gps_satellites_in_use[x])
printf(%d ,gps_satellites_in_use[x]);
#endif
}
#ifdef DEBUG
printf(\n);
if(strcmp(field[0],$GPGSV) == 0)
{
gps_gsv_message_count = atoi(field[1]);
gps_gsv_message_number = atoi(field[2]);
gps_total_sats_in_view = atoi(field[3]);
#ifdef DEBUG
printf(NMEA string GPGSV recognized\n);
printf(Total satelites in view = %d\n,gps_total_sats_in_view);
#endif
if((gps_gsv_message_number > 0) && (gps_gsv_message_number < 4))
{
y = gps_gsv_message_number - 1;
for(x=0; x< 4; x++)
{
gps_prn_number[y][x] = atoi(field[(x*4)+4]);
gps_elevation[y][x] = atoi(field[(x*4)+5]);
gps_azimuth[y][x] = atoi(field[(x*4)+6]);
gps_snr[y][x] = atoi(field[(x*4)+7]);
#ifdef DEBUG
printf(Satelite %d - Elev = %d Azim = %d SNR = %d\n,
gps_prn_number[y][x],gps_elevation[y][x],gps_azimuth[y][x],
gps_snr[y][x]);
#endif
}
}
if(strcmp(field[0],$GPRMC) == 0)
{
#ifdef DEBUG
printf(NMEA string GPRMC recognized\n);
printf(GPS Date : %8ld Mag Variation %6.2f Deg %c GPS Mode %c\n,
gps_rmc_date,gps_mag_variation,gps_variation_direction,gps_mode_indicator);
#endif
}
if(strcmp(field[0],$GPVTG) == 0)
{
/* Retrieve the data from the remaining fields */
gps_track = atof(field[1]);
gps_track_magnetic = atof(field[3]);
gps_sog = atof(field[5]);
gps_sog_kilometers = atof(field[7]);
gps_sys_mode_indicator = *(field[9]);
#ifdef DEBUG
printf(NMEA string GPVTG recognized\n);
printf(GPS Track : %7.2f True %7.2f Magnetic. Speed : %9.2f Knots %9.2f Kilometer/Hour\n,
gps_track,gps_track_magnetic,gps_sog,gps_sog_kilometers);
#endif
}
if(strcmp(field[0],$GPZDA) == 0)
{
gps_utc_time = atof(field[1]);
gps_day = atoi(field[2]);
gps_month = atoi(field[3]);
gps_year = atoi(field[4]);
#ifdef DEBUG
printf(NMEA string GPZDA recognized\n);
printf(%9.2f %2d/%2d/%4d\n,gps_utc_time,gps_month,gps_day,gps_year);
#endif
}
return field_count;
}
/* These variables and the function my_token are used to retrieve the comma
delimited field pointers from the input string. Repeated calls to
my_token return the next field until there are no more (NULL).
*/
/* The source string is real only for the first call. Subsequent calls
are made with the source string pointer as NULL
*/
if(source != NULL)
{
/* If the string is empty return NULL */
if(strlen(source) == 0)
return NULL;
strcpy(stat_string,source);
/* Current is our current position within the string */
current = stat_string;
}
start = current;
while(1)
{
/* If were at the end of the string, return NULL */
if(*current == \0)
return start;
if(*current == token)
{
*current = \0;
current++;
return start;
}
else
{
current++;
break_handler()
{
return 1;
}
char com_buffer[BUFFER_SIZE];
unsigned com_head,com_tail;
/* calculate the necessary baud rate divisor given the desired baud
rate and the input frequency to the counter timer
*/
outportb(port_address+3,inportb(port_address + 3) | 0x80);
outportb(port_address+1, baud >> 8);
outportb(port_address, baud & 0xff);
outportb(port_address+3,inportb(port_address + 3) & 0x7f);
if(int_number < 8)
vector = int_number + 8;
else
vector = (int_number - 8) + 0x70;
old_vector = *addr;
com_head = com_tail = 0;
/* Initialize the UART for 8 bit word, no parity and one stop bit */
inportb(port_address);
inportb(port_address);
inportb(port_address);
if(int_number < 8)
{
mask_val = 1 << int_number;
outportb(pic_base + 1,inportb(pic_base + 1) & ~mask_val);
}
else
{
mask_val = 1 << (int_number - 8);
outportb(0xa1,inportb(0xa1) & ~mask_val);
outportb(pic_base +1,inportb(pic_base + 1) & 0xfb);
}
enable();
}
/* This is the comm_close routine to shut down the com port before
exiting thus not leaving the interrupts hanging
*/
void com_close(void)
{
long far *addr;
com_buffer[com_tail++] = inportb(port_address);
if(com_tail == BUFFER_SIZE)
com_tail = 0;
if(int_number > 8)
outportb(0xa0,0x20);
/* This function writes the specified character to the COM UART. This is
a polled mode function and will wait until the UART is ready for the
next character.
*/
void com_putch(char c)
{
unsigned retry;
int com_check(void)
{
if(com_head == com_tail)
return 0;
else
return 1;
}
while(!com_check())
;
c = com_buffer[com_head++];
if(com_head == BUFFER_SIZE)
com_head = 0;
return(c & 0xff);
}
int com_read()
void com_flush()
{
disable();
com_head = com_tail = 0;
enable();
}
#include <stdio.h>
#include <dos.h>
#include <conio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include gps_func.h
void update_screen(void);
void screen_init(void);
void close_screen(void);
void draw_box(void);
void center_text(int line, char *text);
void display_raw(char *string);
void put_blanks(int count);
char line_buffer[128];
char temp_string[80];
/* Command line arguments specify the com port I/O address, the com port IRQ
number and the com port baud rate respectively as in :
which specifies a com port at 3f8 (hex) IRQ 4 (decimal) and 4800 baud (decimal).
When no arguments are given the default is 3f8 4 4800.
*/
main(int argc, char *argv[])
{
port_address = 0x3f8;
int_number = 4;
baud_rate = 4800;
if(argc > 1)
{
sscanf(argv[1],%x,&port_address);
if(port_address < 0x100 || port_address > 0x3f8)
{
printf(\nInvalid port address %04x specified\n,port_address);
exit(2);
}
if(port_address == 0x2f8)
int_number = 3;
if(port_address == 0x3f8)
int_number = 4;
}
if(argc > 2)
{
sscanf(argv[2],%d,&int_number);
if(int_number < 2 || int_number > 15)
{
printf(\nInvalid interrupt number %d specified\n);
exit(2);
}
}
if(argc > 3)
{
sscanf(argv[3],%d,&baud_rate);
if(baud_rate < 110 || baud_rate > 38400)
{
printf(\nInvalid baud rate %d specified\n,baud_rate);
exit(2);
}
}
/* Now that all of the com port values are known we can call com_init();
which will initialize the port.
*/
com_init();
screen_init();
/* Get ready to start handling the incoming characters from the NMEA
stream. line_index is the current string position for the incoming
characters. Since this is the beginning, thats where it starts.
*/
/* This application does nothing but parse NMEA strings and display
the provided data. It loops eternal until a Ctrl-A keypress occurs.
*/
while(1)
{
/* Check for any characters in the serial receive buffer */
if(com_check())
{
/* Yes, retrieve the character */
c = com_getch();
display_raw(line_buffer);
parse_nmea(line_buffer);
update_screen();
line_index = 0;
continue;
}
}
/* Check for a keyboard character */
if(kbhit())
{
c = getch();
/* This function displays the RAW NMEA 0183 strings, or whatever comes in over the
comm port, in a small window in the center of the screen.
*/
/* This function sets up the screen and displays the data block titles */
void screen_init()
{
_setcursortype(_NOCURSOR);
window(1,1,80,25);
textbackground(GREEN);
textcolor(WHITE);
clrscr();
draw_box();
sprintf(temp_string,WinSystems PCM-GPS NMEA 0183 Display Program V%s,VERSION);
center_text(2,temp_string);
textcolor(BLACK);
gotoxy(4,4);
cputs(GPS UTC Time);
gotoxy(20,4);
cputs(GPS UTC Date);
gotoxy(35,4);
cputs(COM Port Addr.);
gotoxy(55,4);
cputs(Baud Rate);
gotoxy(66,4);
cputs(Serial IRQ);
gotoxy(4,7);
cputs(Fix Status);
gotoxy(23,7);
cputs(Lattitude);
gotoxy(41,7);
cputs(Longitude);
gotoxy(62,7);
cputs(Altitude);
gotoxy(35,10);
cputs(True);
gotoxy(35,11);
cputs(Direction);
gotoxy(50,10);
cputs(Magnetic);
gotoxy(50,11);
cputs(Direction);
gotoxy(65,10);
cputs(Local Magnetic);
gotoxy(68,11);
cputs(Variation);
void close_screen()
{
_setcursortype(_NORMALCURSOR);
window(1,1,80,25);
textbackground(BLACK);
textcolor(WHITE);
clrscr();
}
/* This function uses the line characters to draw the screen box and
the data block areas.
*/
void draw_box(void)
{
int x;
gotoxy(1,1);
putch();
for(x=1; x<79; x++)
putch();
putch();
gotoxy(1,24);
putch();
for(x=1; x<79; x++)
putch();
putch();
gotoxy(1,3);
putch();
putch();
gotoxy(1,6);
putch();
putch();
gotoxy(1,9);
putch();
putch();
gotoxy(1,13);
putch();
putch();
gotoxy(1,18);
putch();
putch();
gotoxy(1,21);
putch();
temp_string[x] = \0;
cputs(temp_string);
}
void update_screen()
{
int x,y;
int hour,minute,second;
unsigned long temp;
float ftemp;
double bearing;
double distance;
float lat_mul,lon_mul;
if(gps_utc_time)
{
temp = (unsigned long)gps_utc_time;
hour = (int)(temp /10000l);
temp = temp - (hour * 10000l);
minute = (int)(temp / 100);
temp = temp - (minute * 100);
gotoxy(20,5);
cprintf(%2d/%02d/%04d,gps_month,gps_day,gps_year);
gotoxy(7,8);
put_blanks(69);
gotoxy(7,8);
if(gps_fix_mode == 1)
{
/* We dont have a location yet */
cprintf(N/A);
}
else
{
/* We have at least a 2-dimensional location fix, we can display
basic position information
*/
if(gps_fix_mode == 2)
cprintf(2D);
if(gps_fix_mode == 3)
cprintf(3D);
gotoxy(21,8);
gotoxy(39,8);
gotoxy(55,8);
if(gps_fix_mode == 3)
{
/* If we have a 3-dimensional fix, we can also display GPS
altitude in both meters and feet.
*/
cprintf(%8.2f %c,gps_altitude,gps_altitude_unit);
}
gotoxy(10,23);
/* This code sequence displays the currently used satelite numbers (PRN)
and their corresponding signal to noise ratio (SNR).
*/
gotoxy(3,12);
/* Display the current speed over the ground (SOG) in both knots and
Mile per hour.
*/
gotoxy(35,12);
cprintf(%5.2f,gps_track);
gotoxy(50,12);
cprintf(%5.2f,gps_track_magnetic);
gotoxy(68,12);
cprintf(%5.2f %c,gps_mag_variation,gps_variation_direction);