Click here to Skip to main content
15,867,488 members
Articles / Mobile Apps / Windows Mobile

Speaking Garmin

Rate me:
Please Sign up or sign in to vote.
4.76/5 (24 votes)
14 Dec 2005CPOL11 min read 133.7K   1.4K   79   23
Covers the basics needed to get a Pocket PC and a Garmin GPS talk in their own language and to graphically display the calculated data.

Image 1

Contents

Introduction

Recently, I've been exposed to the whole world there is to the ever smaller and lighter electronic-equipments. I've always been interested in cutting-edge technology and somehow I got involved in an aeromodelism group where it was decided that the best (and simplest) solution for measuring flight data was to use a GPS device. No doubt we chose the world leading brand Garmin, of all the features available in their devices, the one that was important to us was track recording. As the sole Windows application programmer in the group, I was requested to create software that would transfer data between the GPS unit and a Pocket PC so that adjustments could be made instantly based on the data collected which would be analyzed in graphs.

Background

The protocol

Garmin uses a proprietary format that is not complicated, but let's cover its basics here as there aren't many thorough resources on the internet.

Information transferred to and from the GPS is divided into packets, which we'll refer to as messages. Each message starts with a hexadecimal 0x10 code and ends with two bytes 0x10 and 0x03. The information itself is found between these starting and finishing bytes. The second byte refers to the kind of information that is being transmitted, i.e. the message ID. Next, we have a byte which represents the number of bytes that are to be sent starting from the following byte up to the last one, just before a checksum byte and the trailing 0x10 0x03. But if it were as simple as that, the protocol would not be totally robust. Let's imagine for example that the first information-byte, which is the 4th, has the bits set to 0x10 and the following one 0x03. A simple interpreter would get it as a message-finalizer, thus wrongly recognizing a message-escape sequence and causing an error. In order to remove such a possibility, Garmin chose to repeat the corresponding byte whenever 0x10 should be sent, as long as it's an information-byte, so that in the above case we would receive 0x10 0x10 0x03 accounting for only two bytes, thus removing any possible errors. Repeated 0x10 accounts for only one byte as far as the third-byte (count byte) is concerned. Let's see some examples of messages. Move your mouse over the bytes to see their descriptions:

Pocket PC: 10 0A 02 06 00 EE 10 03 - Ask for tracks, the first message sent.
GPS: 10 06 02 0A 00 EE 10 03 - Reply to the previous message, means OK.
GPS: 10 1B 02 05 00 DE 10 03 - Number of records to be sent next: 5.
Pocket PC: 10 06 02 22 00 D6 10 03 - Ask for the next record.
GPS: 10 63 0D 01 FF 41 43 54 49 56 45 20 4C 4F 47 00 D2 10 03 - Track name: ACTIVE LOG.
Pocket PC: 10 06 02 22 00 D6 10 03 - Ask for the next record.
GPS: 10 22 18 01 02 03 04 05 06 07 08 09 10 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 DE 10 03 - Point in track.
Pocket PC: 10 06 02 22 00 D6 10 03 - Ask for the next record.
GPS: 10 0C 02 06 22 CA 10 03 - Track EOF.

Calculating the checksum

In order to calculate the checksum of the message we first sum up all the information-bytes and get the least significant byte of the result by applying an AND (&) operation. Then, we invert the bits by calling XOR (^) 0xff and then we just add 1. The code for doing this is shown below:

C#
private bool CheckSum(System.Collections.ArrayList command, 
                                           bool errorDetails)
{
  int res=0;
  int orig=(byte)command[command.Count-3];

  for(int i= 1;i<command.Count-3;i++)
  {
    res+=(byte)command[i];
  }
  res &= 0xff;
  res ^= 0xff;
  res+=1;
  bool retval=(byte)(res) == (byte)orig;
  if(!retval && errorDetails)
  {
    System.Windows.Forms.MessageBox.Show(
      "Received message:\n" + 
      ToHEXstring(command) + "\n\n" +
      "Received checksum: " + orig.ToString() +
      "\nCalculated checksum:" + res.ToString(),
      "Error details",
      System.Windows.Forms.MessageBoxButtons.OK,
      System.Windows.Forms.MessageBoxIcon.Asterisk,
      System.Windows.Forms.MessageBoxDefaultButton.Button1);
  }
  return ( retval );
}

Extracting data from the messagesTrack points

For each point data sent by the GPS, there is the latitude, longitude, time, altitude and whether, it is a new segment within the same track. Each of the first four fields is composed of 4 bytes, in a way that the most significant one is the one to the right. Next we'll discuss how to obtain data values from the bytes.

Coordinates

Both the latitude and longitude are represented by an int32 ranging from -2147483648 to +2147483647 that should be translated into numbers from -90 to +90 for latitude and from -180 to +180 for longitude. The mathematical formulae for both are the same and are shown in the extract below which comprises of all the parsing of a track point message.

Time

The time is the simplest to retrieve from the message: we just read the four corresponding bytes and store them.

Altitude

The altitude is not easy to calculate from within the .NET Compact Framework. This field is transmitted as a float32 and we must retrieve a variable of that type based on the 4 bytes. This complex routine, which I'll not go into detail here, is also shown in the extract below.

New segment flag

Once I discovered which byte to check for such information, getting its data was fairly trivial.

Retrieving data from a track point message
C#
latitude=( ((byte)command[6]<<24 | 
            (byte)command[5]<<16 | 
            (byte)command[4]<<8 | 
            (byte)command[3]) 
            * ( 180.0 / 2147483648.0) );
longitude=( ((byte)command[10]<<24 | 
             (byte)command[9]<<16 | 
             (byte)command[8]<<8 | 
             (byte)command[7]) 
             * ( 180.0 / 2147483648.0 ) );
time=(uint)((byte)command[14]<<24 | 
            (byte)command[13]<<16 | 
            (byte)command[12]<<8 | 
            (byte)command[11]);
if((byte)command[23]==1)
    isNewSegment=true;
else
    isNewSegment=false;

/* Formula used to get the float represented by 4 bytes */
int h=(byte)command[18]<<24 | 
      (byte)command[17]<<16 | 
      (byte)command[16]<<8 | 
      (byte)command[15];
int exp=(h & 0x7f800000)/(2<<22);
int frac=h & 0x7fffff;
height=1+(float)frac/((float)(2<<22));
height *= 2<<(exp-128);

Tracks

For every new track in the GPS database a message is sent containing the name of a new track and its ID, as seen earlier in the message examples. Next is a snippet from the code that is used to get the track name:

C#
name=""
for(int i=5;i<=(byte)command[2]+1;i++)
    name+=Convert.ToChar((byte)command[i]).ToString();

This is just an outline of what the protocol seems to be according to my analysis. For more information on other types of data that can be transferred, you may refer to this.

Having the protocol interpreter done, the next step was to build the data analysis tools, which was not as easy as expected due to the limitations found while comparing the .NET Framework to the .NET Compact Framework. The main feature usually used in graphing applications is vector graphics, achieved with the aid of matrix-transformations such as scale and translation. As a consequence, all the drawing routines had to be created from scratch.

For serial communications, I grabbed routines from Microsoft and the progress bar used to indicate the transfer progress from a GPS unit was taken from the OpenNETCF Smart Device Extensions project, which adds a fancy gradient background to the control.

Using the code

Three important classes used for communication between a Pocket PC and a Garmin GPS unit are included in the source code: Garmin, Track and TrackPoint. The first one is responsible for communicating with the unit and storing the information received into tracks of type Track, each made of points of type TrackPoint, and stored by each track as ArrayLists.

Making the Garmin class communicate with the GPS seemed to be trivial at first, but came out to be much more complex than expected.

Problems implementing the protocol

I tried two methods of communication before getting to the one that I am now using, which hasn't failed even once throughout my tests with an iPaq H2215 device. However, when I tried it on an older model also from HP, the Jornada 548, I couldn't get it to work, probably because of some difference in the hardware implementation of the serial port. Next, I will describe each of the methods that sometimes worked but were not stable and their problems, and also the reliable one that is currently in use:

  1. Parsing as data was received:

    The first one I tried consisted of parsing the data as it was received from the GPS and creating Tracks and TrackPoints according to what was received. The problem with this was as the serial port is asynchronous, I couldn't find out when the message was over while still not skipping any bytes that are the first ones from the next message unless I wrote a complex routine that would do the trick. After adding some delays to make sure that all the data had come and still not getting the expected results, I decided to try another method.

  2. Parsing as data was received and flooding the GPS with send next commands:

    By using this method, whenever the data came in, a send next command was sent to the GPS so as to avoid the problems created by using the artificial delays of the previous method. This method never got to work due to the bytes being skipped.

  3. The currently-in-use working method:

    In this method, I mix the second one with a new concept to make the code reliable. Whenever data is received it is added to an ArrayList and, when an EOF (End of File) is received a function is called to split the messages from the array of bytes stored in the ArrayList and then the Track and its TrackPoints are added. By using this method, the saving and loading of files was made much easier, as the process of loading data from the GPS or from a file would be the same. To save data I need to just copy the bytes in the ArrayList to the storage memory and to load it, do just the reverse and call the function that splits the messages accordingly.

Splitting the messages

Creating a code that would flawlessly break the bytes at every message-end took many hours until I got the idea of an algorithm that would do the trick. As mentioned earlier, when an information-byte has a value of 0x10, it is repeated. So, if we count the number of adjacent 0x10 present in the actual message we'll always get an even number as there will never be a single 0x10, they come in pairs. However, if there's an ending 0x10 byte we'll get an odd number because the ending-byte is always sent on its own. That's it! Breaking the messages was as simple as counting adjacent 0x10 and checking the result. If it is even we should go on. Otherwise, we have found a message break:

C#
private static System.Collections.ArrayList SplitBytes(
                System.Collections.ArrayList arrayListBytes)
{
  System.Collections.ArrayList retval=
    new System.Collections.ArrayList(10);
  System.Collections.ArrayList tempBytes;
  int count=0;

  for(int i= 0;i<arrayListBytes.Count;i++)
  {
    if((byte)arrayListBytes[i]==0x10)
    { /* First byte in a message */
      tempBytes=new System.Collections.ArrayList(8);
      tempBytes.Add(arrayListBytes[i]);
      for(int x=i+1;;x++)
      {
        i=x;
        tempBytes.Add(arrayListBytes[x]);
        if((byte)arrayListBytes[x]==0x10)
          ++count;
        else if((byte)arrayListBytes[x]==0x03)
        {
            if(count%2==1) /*  If found an odd number of 
                            * 0x10 followed  by  0x03 we 
                            * have to  split the message 
                            * here */
              break;
        }
        else
            count=0;
      }
      retval.Add(tempBytes);
    }
  }
  return retval;
}

Getting the connection up and running

In order to connect your Pocket PC to a GPS unit software-wise you just need to create a variable of type Garmin and call its .GetTracks.

Apart from the software, you'll have to establish a real physical connection between the device's serial port and the GPS. For this, I personally recommend Pfranc plugs. I got mine here in Brazil with no problems and they work just great. On the other end of the cable you'll have the Pocket PC. In my case, I had an iPaq which comes with a power adapter. I used this adapter and added some extra wires to it so that it became a charging and serial port connector. Below is an image showing the HP iPaq universal 22-pin connector diagram that shows the wires to connect to the GPS. If you need assistance in creating the cables feel free to e-mail me, but keep in mind that the connections you make on your own could void your warranty and damage your equipment. I got mine to work with no problems at all, but there is a possibility of damaging both the devices. Do whatever you do at your own risk.

Image 2

Solder-side view of the iPaq power adapter and the pins to solder.

Image 3

eTrex Pfranc plug, frontal view from the latch.

Image 4

This is the result of the modifications made to the original plugs.

Points of interest

One interesting feature added to the sample application is the ability to save a spreadsheet file which can be read by most of the spreadsheet editors available in the market. It saves files using the known CSV (Comma Separated Values) format. However, while implementing the save function, I discovered a problem with the CSV format which I couldn't overcome: The decimal-separator character seems to depend on the current system's regional settings, and as a consequence we can't guarantee that a saved file will be read properly by a configuration. Their might be some way to elegantly fix this problem, but the way I figured out works satisfactorily: I use Excel formulae to overcome this issue. Non-integer numbers are multiplied by 1010 and Excel is made to calculate the result of the division of such a number by 1010. This way the decimal separator will always be displayed according to each system's configuration and still will be made possible for reading by whichever configuration. Let's see an example of the formula we use for the altitude measurement in Excel: =7749426479104/10000000000 which accounts for approximately 774.9426 meters.

History

  • 12.03.2005: version 1.06
    • First release.
  • 12.10.2005: version 1.07
    • Fixed an issue in Plotter in which track segments made of only one point would make the program crash.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
Brazil Brazil
I am currently 18 years old and since I was very little I've been into computer-programming. Back then I used to play with a Basic compiler and now I am using the most powerful development environments out there for creating games and windows applications in general.

Comments and Discussions

 
QuestionGarmin C# code example Pin
Gene Swiech22-Oct-11 7:47
Gene Swiech22-Oct-11 7:47 
GeneralFakeGps Pin
dreamgirlcoder25-Dec-08 8:28
dreamgirlcoder25-Dec-08 8:28 
GeneralVB.net app to read garmin Pin
Dennis J. Laney10-Dec-08 16:12
Dennis J. Laney10-Dec-08 16:12 
GeneralRe: VB.net app to read garmin Pin
David Nissimoff12-Dec-08 14:47
David Nissimoff12-Dec-08 14:47 
GeneralExcellent Pin
SiliconValleyDreamer18-Jul-08 17:13
SiliconValleyDreamer18-Jul-08 17:13 
GeneralAnother way of calculating checksum Pin
ttamuka22-Mar-07 0:31
ttamuka22-Mar-07 0:31 
AnswerRe: Another way of calculating checksum Pin
David Nissimoff14-Jul-07 18:11
David Nissimoff14-Jul-07 18:11 
Questiongiving error... Pin
Suhasni2-May-06 22:49
Suhasni2-May-06 22:49 
AnswerRe: giving error... Pin
David Nissimoff3-May-06 10:12
David Nissimoff3-May-06 10:12 
GeneralNMEA checksum calculation Pin
wwwillem11-Feb-06 9:54
wwwillem11-Feb-06 9:54 
GeneralIt's not NMEA Pin
David Nissimoff12-Feb-06 8:55
David Nissimoff12-Feb-06 8:55 
GeneralRe: It's not NMEA Pin
wwwillem12-Feb-06 16:44
wwwillem12-Feb-06 16:44 
QuestionImpressive, interested in more? Pin
Tom@August8-Feb-06 4:22
Tom@August8-Feb-06 4:22 
GeneralDon`t work on my pocket pc Pin
MH25382-Jan-06 20:57
MH25382-Jan-06 20:57 
QuestionWhat's this? Pin
hoxsiew21-Dec-05 8:32
hoxsiew21-Dec-05 8:32 
AnswerRe: What's this? Pin
David Nissimoff27-Dec-05 16:10
David Nissimoff27-Dec-05 16:10 
GeneralNice Pin
Richard Jones20-Dec-05 4:10
Richard Jones20-Dec-05 4:10 
GeneralRe: Nice Pin
David Nissimoff20-Dec-05 12:04
David Nissimoff20-Dec-05 12:04 
GeneralFantastic stuff!! Pin
Sreekanth Muralidharan19-Dec-05 18:31
Sreekanth Muralidharan19-Dec-05 18:31 
GeneralRe: Fantastic stuff!! Pin
David Nissimoff20-Dec-05 12:14
David Nissimoff20-Dec-05 12:14 
GeneralWicked! Pin
Chris Maunder14-Dec-05 21:05
cofounderChris Maunder14-Dec-05 21:05 
QuestionReading data Pin
Etosoerc7-Dec-05 0:10
Etosoerc7-Dec-05 0:10 
AnswerRe: Reading data Pin
David Nissimoff8-Dec-05 4:54
David Nissimoff8-Dec-05 4:54 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.