GPS and NMEA

< BACK  NEXT >
[oR]

GPS (Global Positioning System) devices, used to obtain location fixes to an accuracy of around 100 meters, can be connected to Windows CE devices using a serial communications cable. GPS uses satellites that broadcast position and timing information, and a GPS device calculates its position using satellites that are visible at the time. GPS PCMCIA cards are also available, and these again use a serial connection to the Windows CE device. As an added advantage, GPS devices also provide a precise timing signal some companies use GPS devices with computers to coordinate activities in different locations throughout the world.

GPS is capable of providing positional information to an accuracy of 1 to 5 meters, but the U.S. government reduces accuracy for nonmilitary users with Selective Availability (SA). However, the general public can use DGPS (Differential GPS) that uses a land station with a precisely known location to improve accuracy. GPS devices send positional information using the NMEA (National Marine Electronics Association) format. Other navigational devices, such as autopilots, also use this same format.

The NMEA 0183 Standard

GPS devices usually produce positional information to the NMEA 0183 standard. This is a text-based standard that outputs lines of data each line is known as a sentence. The device sends out these sentences continuously. The standard specifies that data should be transmitted at 4800 Baud, but many devices can select transmission speeds. You can find a sample output file from a Garmin GPS 48 on the CDROM in the file \GPS\output.txt. Here is sample output from a Garmin GPS 48 device:

 $GPRMC,195531,A,5326.986,N,00610.147,W,000.0,360.0,170500,007.2,W*7F $GPRMB,A,,,,,,,,,,,,V*71 $GPGGA,195532,5326.986,N,00610.147,W,1,07,1.5,24.7,M,54.0,M,,*6A $GPGSA,A,3,,04,06,,10,13,,18,19,,24,,3.0,1.5,2.6*37 $GPGSV,3,1,12,01,24,065,30,04,44,194,36,06,11,328,37,08,27,170,32*73 $GPGSV,3,2,12,10,35,291,44,13,56,238,46,16,24,110,00,18,63,073,41*70 $GPGSV,3,3,12,19,52,096,35,22,10,037,31,24,58,261,47,27,41,158,30*79 $PGRME,5.5,M,9.1,M,10.7,M*10 $GPGLL,5326.986,N,00610.147,W,195532,A*31 $PGRMZ,81,f,3*22 $PGRMM,WGS 84*06 $GPBOD,,T,,M,,*47 $GPRTE,1,1,c,0*07 $GPRMC,195533,A,5326.986,N,00610.147,W,000.0,360.0,170500,007.2,W*7D 

Each sentence starts with a "$" sign followed by a sentence identifier. The first two letters in the identifier are the "talker id," which identifies the device producing the information. In the above example "GP" indicates that the data is from a GPS device, and "PG" indicates a proprietary sentence from Garmin. Each data item in a sentence is separated with a comma, and data items can be empty (indicated by two adjacent commas). Some sentences finish with a "*" followed by a number. This is a checksum which is an exclusive OR checksum on all characters in the message between, but not including, the "$" and "*". The checksum is displayed in hexadecimal it is optional for some sentences and mandatory for others.

The code presented in this chapter describes how to interpret the "RMC" sentence the recommended minimum specific GPS/Transit data. You can find out more about the NMEA standard and the structure of other sentences at http://vancouver-webpages.com/peter, a website maintained by Peter Bennett. The "RMC" sentence provides basic navigational and time information including the following:

  • Latitude and longitude

  • Time accurate to a second and a date

  • Speed in knots

  • Course made good (in degrees)

  • Magnetic variation (in degrees east or west)

The following is an RMC sentence recording in Dublin, Ireland:

 $GPRMC,195531,A,5326.986,N,00610.147,W,000.0,360.0,170500,007.2,W*7F 

The fields in this sentence are described in Table 9.3.

Table 9.3. Structure of the RMC EMEA sentence
Data Purpose
$GPRMC Talker ID (GP) and sentence identifier (RMC).
195531 Time when sentence was created in hhmmss format using 24-hour notation. This time is 19:55:31.
A Navigation receiver warning. A = OK, V = warning. A warning is usually given if satellite reception is poor or insufficient satellites are visible.
5326.986 Current latitude showing degrees between 0 and 90, minutes, and a fraction of a minute. In this case the latitude is 53 degrees, 26 minutes, and 1/986 of a minute.
N Indicates whether latitude is north or south of the equator.
00610.147 Longitude showing degrees between 0 and 180, minutes, and a fraction of a minute. In this case the longitude is 006 degrees, 10 minutes, and 1/147 of a minute.
W Indicates whether the longitude is west or east of the Greenwich meridian.
000.0 Speed over the ground in knots. A knot is one nautical mile per hour, and a nautical mile is one minute of latitude. In this case the speed was zero knots.
360.0 Course made good. Since the GPS unit was not moving, the course should be ignored but is reported as 360 degrees.
170500 Date in the form ddmmyy. This date is 17May2000.
007.2 Magnetic variation. This is the difference, expressed in degrees, between the magnetic north pole and the true north pole. This varies around the world and over time. In Dublin on this date the magnetic variation is 7.2 degrees.
W Indicates whether the magnetic pole is to the west or east of the true pole.
*7F Checksum expressed as a hexadecimal value.

Figure 9.1 shows sample output from the code described in this chapter for an RMC sentence, including using the checksum. The checksum is important in navigational applications.

Figure 9.1. Sample output from reading an RMS sentence
graphics/09fig01.gif

Connecting Windows CE and GPS Devices

You will need either to buy a custom cable suitable for both the GPS device and the Windows CE device, or to make a cable yourself. Most GPS devices have standard cables that terminate in a standard 9-pin female "D" connector, and this is the same for Windows CE devices. Therefore, you will need a female-to-female "D" connector to connect the GPS device cable to a Windows CE device cable. This connector will probably need to switch the Transmit Data (TD pin3 on a 9-pin connector) with the Receive Data (RD pin 2 on a 9-pin connector). This is because both the Windows CE and GPS devices are acting as data terminal computers (DCEs).

Reading Data from a GPS Device

The data from a GPS device will be read using standard serial communications functions, regardless of whether you are communicating with a GPS device or have a PCMCIA card. You will first need to determine the port name to connect to this will generally be COM1 for an external GPS device, or a device name for a PCMCIA card. The CreateFile function can then be called to open the port, and the timeout and DCB port configuration values set. In the example code the communications port is opened with a call identical to Listing 9.1a. The following timeout and DCB configurations values are used for connecting to a Garmin GPS 48.

 ct.ReadIntervalTimeout = 1000; ct.ReadTotalTimeoutMultiplier = 0; ct.ReadTotalTimeoutConstant = 0; ct.WriteTotalTimeoutMultiplier = 10; ct.WriteTotalTimeoutConstant = 1000; dcb.BaudRate = CBR_9600;          // set baud rate to 9600 dcb.fOutxCtsFlow      = FALSE; dcb.fRtsControl     = RTS_CONTROL_DISABLE; dcb.fDtrControl     = DTR_CONTROL_DISABLE; dcb.fOutxDsrFlow    = FALSE; dcb.fOutX           = TRUE; // XON/XOFF control dcb.fInX            = TRUE; dcb.ByteSize        = 8; dcb.Parity          = NOPARITY; dcb.StopBits        = ONESTOPBIT; 

Using these values calls to ReadFile will timeout if there is more than a second between characters received at the port. Timeout values are generally not too important when reading from a GPS device since a constant stream of data is being received. The port is then configured to receive data at 9600 Baud without hardware flow control. The serial connector on a Garmin GPS 48 only has transmit and receive pins (equating to pins 2 and 3 on the 9-pin D connector), and so cannot use hardware flow control. Instead, XON/XOFF flow control is enabled. Even with flow control, you will need to write code that expects errors in transmission.

A thread will be used for reading from the GPS device, and this is created using the following code:

 HANDLE hCommReadThread = CreateThread(NULL, 0,     GPSReadThreadFunc, NULL, 0, NULL); 

The thread function, GPSReadThreadFunc (Listing 9.4a), is a little different from CommReadThreadFunc in Listing 9.1b in that it does not block using communications events. Instead, it blocks using ReadFile. This is acceptable in this case since a constant stream of data is being read and interpreted. Notice that in Listing 9.4a the thread first makes a call to SetThreadPriority to lower the thread's own priority its thread handle is obtained through calling GetCurrentThread (see Chapter 5). This is to ensure that this thread does not degrade performance for the user.

Listing 9.4a Thread for reading from GPS device
 // Thread function reads NMEA output from GPS device DWORD WINAPI GPSReadThreadFunc(LPVOID) {   DWORD dwBytesRead;   char szSentence[1000], c;   TCHAR szwcsSentence[1000];   int nc = 0;   SetThreadPriority(GetCurrentThread(),       THREAD_PRIORITY_BELOW_NORMAL);   while(hGPSPort != INVALID_HANDLE_VALUE)   {     if(!ReadFile(hGPSPort, &c,         1, &dwBytesRead, NULL))     {       ReportCommError(_T("Reading comms port."));       return 0;     }     if(dwBytesRead == 1)     {       if(c == '\n') // LF marks end of sentence       {           // remove trailing CR         szSentence[nc-1] = '\0';         nc = 0;         if(strlen(szSentence)  6)           cout   _T("Corrupt sentence")                  endl;         else if(szSentence[0] != '$')           cout   _T("No leading $")                  endl;         else         {           // Read a sentence.           // Convert to Unicode           mbstowcs(szwcsSentence,             szSentence, 1000);           // find sentence ID           if(wcsncmp(&szwcsSentence[3],               _T("RMC"), 3) == 0)             ParseRMC(szwcsSentence);         }       }       else         szSentence[nc++] = c;     }   }   return 0; } 

Characters are read one at a time using ReadFile, and the characters are added to an ANSI buffer, szSentence, to build up the sentence. A line feed character marks the end of a sentence. When this is detected, the sentence in szSentence is checked for starting with a "$" and being at least six characters long. It is then converted to Unicode and the three-letter sentence identifier is inspected. If the sentence identifier is "RMC" the function ParseRMC is called, otherwise the sentence is ignored.

Listing 9.4b shows code for parsing the RMC sentence and displaying the data using the format illustrated in Figure 9.1. An RMC sentence is typically sent about every second, so the display is regularly updated. The function GetNextToken is used to extract the next data item from the sentence. Given that the sentence may be corrupt, the function needs to provide for all eventualities of an empty sentence, a sentence that is prematurely terminated, or an empty data item. Data items may be empty if the quality of GPS reception is poor. The function copies the next data item in lpToken and then returns a pointer to the start of the next token. The function ParseRMC calls GetNextToken repeatedly, displaying the data from the sentence.

Listing 9.4b Code for parsing and displaying NMEA sentence
 // returns the next token from the sentence. LPTSTR GetNextToken(LPTSTR lpSentence, LPTSTR lpToken) {   lpToken[0] = '\0';   if(lpSentence == NULL) // empty sentence     return NULL;   if(lpSentence[0] == '\0') // end of sentence     return NULL;   if(lpSentence[0] == ',') // empty token     return lpSentence + 1;   while(*lpSentence != ',' &&       *lpSentence != '\0' &&       *lpSentence != '*')   {     *lpToken = *lpSentence;     lpToken++;     lpSentence++;   }   // skip over comma that terminated the token.   lpSentence++;   *lpToken = '\0';   return lpSentence; } // Parses a RMC sentence which has the format: // $GPRMC,195531,A,5326.986,N,00610.147,W,000.0,360.0, // 170500,007.2,W*7F void ParseRMC(LPTSTR szSentence) {   TCHAR szToken[20];   DWORD dwCheckSum = 0, dwSentenceCheckSum;   cout.CLS();   // Calculate the checksum. Exclude $ and work up to *   for(UINT i = 1; i < wcslen(szSentence) &&         szSentence[i] != '*'; i++)     dwCheckSum ^= szSentence[i];   // lpNextTok points at ID $GPRMS, ignore this   szSentence = GetNextToken(szSentence, szToken);   // Time of Fix, convert to Unicode   szSentence = GetNextToken(szSentence, szToken);   cout   _T("Time (UTC hhmmss):")   szToken   endl;   // Navigation receiver (GPS) warning   szSentence = GetNextToken(szSentence, szToken);   if(szToken[0] == 'A')     cout   _T("Receiving OK")   endl;   else     cout   _T("Suspect signal")   endl;   // Latitude   szSentence = GetNextToken(szSentence, szToken);   cout   _T("Latitude (ddmm.ss): ")   szToken;   // Latitude N or S   szSentence = GetNextToken(szSentence, szToken);   cout   szToken   endl;   // Longitude   szSentence = GetNextToken(szSentence, szToken);   cout   _T("Longitude dddmm.ss: ")   szToken;   // Longitude W or E   szSentence = GetNextToken(szSentence, szToken);   cout   szToken   endl;   // Speed in Knots   szSentence = GetNextToken(szSentence, szToken);   cout   _T("Speed (Knots): ")   szToken   endl;   // Course made good   szSentence = GetNextToken(szSentence, szToken);   cout   _T("Course made good: ")   szToken      _T("deg")   endl;   // Date   szSentence = GetNextToken(szSentence, szToken);   cout   _T("Date (ddmmyyyy):")   szToken   endl;   // Magnetic Variation   szSentence = GetNextToken(szSentence, szToken);   cout   _T("Mag. Var (Deg): ")   szToken;   // Magnetic Variation W or E   szSentence = GetNextToken(szSentence, szToken);   cout   szToken   endl;   // do the check sum   szSentence = GetNextToken(szSentence, szToken);   LPTSTR lpEnd;   dwSentenceCheckSum = wcstoul(szToken, &lpEnd, 16);   if(dwCheckSum != dwSentenceCheckSum)     cout   _T("Error in checksum");   else     cout   _T("Checksum OK"); } 

The function ParseRMC in Listing 9.4b calculates a checksum for the sentence. The checksum is obtained by starting with a zero integer value in dwCheckSum and then performing an exclusive or (XOR) operation on dwCheckSum and each character in the sentence. All characters between the "$" marking the start of the sentence and "*" marking the start of the checksum value are used. Note that the "$" and "*" characters themselves are not included.

 for(UINT i = 1; i < wcslen(szSentence) &&       szSentence[i] != '*'; i++)   dwCheckSum ^= szSentence[i]; 

Once the data items have been displayed, the reported checksum value is extracted from the sentence, and this is compared to the calculated checksum value. The checksum value in the sentence is sent as a two-digit hexadecimal value. The function wcstoul is used to convert the string representation of the checksum value into a binary value that is stored in the variable dwSentenceCheckSum:

 dwSentenceCheckSum = wcstoul(szToken, &lpEnd, 16); 

The function wcstoul is passed the string value to convert (szToken), a pointer to a string pointer that returns a pointer to the character in szToken that terminated the conversion (for example, a NULL character or non-numeric value), and the base used to perform the conversion (16, which is hexadecimal).


< BACK  NEXT >


Windows CE 3. 0 Application Programming
Windows CE 3.0: Application Programming (Prentice Hall Series on Microsoft Technologies)
ISBN: 0130255920
EAN: 2147483647
Year: 2002
Pages: 181

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net