HOME

Wonderful, inexpensive, small, elegant GPS receiver

(filename: iec217.htm)

Gentle reader: Trust me. This little guy is really cool. And only about $18! And the things it does! Don't let the simplicity of it blind you to how cool it is.

-

Three wires...

Give the little darling the first two connections, and at least a view out a window, and immediately "stuff" will stream out of the device as serial data (9600 baud) on the white wire.

Well, "Of course", you say, in your "too- many- times- dazzled" way.

NO! This is really cool. I'm sorry.

Just before we start...

Just before we go into the delights of the GP-20U7 GPS module, I'd commend to you a little study of what we did before GPS. So you can marvel at what people did in the (not-so-good) good old days. I've found my latitude for myself! Mostly the way they did it before GPS.

The study of "the old ways" isn't for everyone... but then, you are reading a page about making a GPS receiver, so you aren't "everyone", are you? More accessible, and something that's given me hours of... fun?... is DIY map making.

Details of inexpensive GPS receiver...

As I said, data begins to flow immediately. A simple stream of ASCII.

Buried in it will be a line something like the following. (It will be sent again and again, several times a second, with other "sentences" between the sendings of that line.)

$GPRMC,074223.00,V,,,,,,,200820,,,N*75

This shows that the unit is "working". We want the "sentence" that begins $GPRMC. After that, the sentence is split up into "values" by the commas. The one above has only 4 non-blank values. :-(

We ignore what's between the first two commas. And we look at the third item, the "V". It stands for "Void", and tells us that the device isn't yet sending useful data. But don't be down-hearted! We only just gave it power, remember?

In well under a minute... it should happen within 30 seconds, but cut it some slack, okay?... in well under a minute, we should see something like this in the $GPRMC sentences being pushed out over the serial line. (The messages just keep coming. There is no flow control. They are "NMEA formatted data".)

$GPRMC,202332.00,A,
4340.42623,N,01115.77047,E,
0.244,,190820,,,A*6B

(The line breaks I've inserted wouldn't be there in actual output from one of these, but they make it easier to read here, don't they??)

The first thing to notice is the 3rd item, the "A". ("Items" are separated by the commas, remember.) That "A" stands for "Active", and says "this data was taken at the time indicated... the device was active then."

The 2nd item, 202332.00 says the fix was taken at 20:23:32.00, UTC. (About 8:20 in the evening, in England).

The second line, as set out above, says that the device was at...
   latitude 43 degrees, 40.42623 minutes North
   longitude 011 degrees, 15.77047 minutes East.

(I.e. Roughly speaking, in the Piazza della Signoria, Florence Italy!)

Which brings us to item 8, "0.244". That's what the device's speed over the ground was, in knots.

Item 9 is the "nothing" between the two commas after "0.244". If there were something there, it would be the track angle (in degrees from true north) of the device's movement.

Item 10: "190820" tells us the data was collected on the 19th of August (8th month), in the year (20)20.

Two more "nothing" items follow. If there were a number in the first, it would be the local magnetic variation, i.e. the difference between magnetic and true north. The second would be "E" or "W", for East or West.

The "A*6B" is a checksum". (Checksums let you check that data hasn't been scrambled in transmission.)

I'm not sure why there was no data for items 9, 11 and 12 when I was testing my unit. I suspect that it is just that the Sparkfun GPS-13740 doesn't supply those values.

My thanks to Dale DePriest for his very clear explanation of this and many similar NMEA data format matters, at https://www.gpsinformation.org.


NOTICE!!! Knowing where something is isn't the only reason to put a GPS receiver into a system. For a mere $18, you can know the current date and time. You have a repeatedly "self-setting" "clock". You also know, directly, how fast the receiver is moving! (You don't have to note two position fixes, and the time between them, and do complicated mathematics.) You do need a reasonably unobstructed view of the sky, though. Some modern parking meters have continually-right time-of-day clocks via GPS, for instance.



Obtaining the device....

I'm sure there are many similar devices. The one I'm writing about is available from Sparkfun, part number GPS-13740. $18, August 2020. In the UK, the nice people at Hobbytronics.co.uk have taken care of all the hassle of getting a supply across the Atlantic, and sell them at pretty well what you'd pay Sparkfun. Minus the foreign exchange fees, customs, hassle and delays.

Order a JST connector when you order the receiver, unless you have one of the connectors in your "parts" bin.



Using the device....

This can, of course, be plugged anything than can interpret a stream of 3v3 serial data.

I'm using mine with an Arduino.

There are several moving parts. I started with a program which simply transcribed the incoming data to the Arduino IDE's Serial Monitor. I don't know quite how I managed to be so stupid, but even that took a bit of work to get right. For heaven's sake. It's not rocket science.

It didn't help that when I started I had a very limited idea of what the unit did, how to "make it work". You won't have that handicap, having read the first part of this page.


I had to start with a little Arduino program that JUST shows me what, if anything, is being sent to the Arduino on the Arduino pin of my choice. That is a general tool, and it has its own webpage: How to watch data arriving as a stream of ASCII.

I hope you won't need to "go there". Give the program supplied on this page a try. If that works, well and good. If it doesn't, give the one in "How to watch data arriving..." try. It shouldn't take long, and will let you make sure you have a good GPS receiver, that it has an adequate view of the sky, that you've matched the pins you are using with all of the necessary changes in your code, etc, etc.

Here you go....

Try the following basic program first. It has the crucial ingredients. Later I present a more thorough program, but it is full of "stuff" which obscures the core.

#include <TinyGPS.h>
#include <SoftwareSerial.h>

//See Serial.Println in setup() for version ID
//Simple demo GP-20U7 GPS module...

//Written for Quduino... not that it should much matter.
//  ... nothing exotic here!

//You can usually leave serial monitor open. All should be
//well, even on multiple compiles.

SoftwareSerial GPS_Serial(9,10); // RX, TX
//Connect a9600 baud serial stream of ASCII with
//NMEA sentences to the pin with the number that appears
//first in the SoftwareSerial statement above.
//
//9,10 WORKS for SoftwareSerial with Qduino,
// at least 9 for Rx. (10 not used, but a pin has
// to be specified to make SoftwareSerial happy.)

TinyGPS gps;

float TargetLat;
float TargetLon;
int Status = 0;

void getGPS(float* lat, float* lon, int* Status);

void setup() {
  Serial.begin(9600);
  GPS_Serial.begin(9600);
  delay(2000);//give ports chance to initialize
  //pinMode(led, OUTPUT);//Just for "proof of life"!

  Serial.println("---");
  Serial.println(" ");
  Serial.println("Hello from simple version of ar790");
  Serial.println("Vers 22 Aug 20, 08:30");
  Serial.println("See https://wywtk.com/ec/iec/iec217.htm");
  Serial.println("... for details of this code.");
  Serial.println("Assumes input via pin 9 of 9600 baud serial stream of NMEA ASCII");
  Serial.println("Nothing appearing? Probably nothing arriving via the input. Should");
  Serial.println("Give SOMETHING immediately after power up.");
  Serial.println("First byte of line is status of GPS receiver...");
  Serial.println("0= No Lock, 1= Old Data(>5 sec old),2= Good Data");
  Serial.println(" ");
  Serial.println("Next two are lat/long");
  Serial.println("Date, time, speed, and more also available, with further programming.");
  Serial.println(" ");
  Serial.println(" ");
  Serial.println(" ");
  Serial.println(" ");
  Serial.println(" ");
  delay(2000);// Give reader a chance to see the output.
}

void loop() {
  getGPS(&TargetLat, &TargetLon, &Status);

  Serial.print(Status);
  //Print status to console to know if you are getting good data or not.
  //No Lock = 0, Old Data(>5 sec old) = 1, Good Data = 2
  Serial.print("  ");
  Serial.print(TargetLon);
  Serial.print("  ");
  Serial.println(TargetLat);
}//end of loop()

void getGPS( float* lat, float* lon, int* Status)
/*My thanks to Sparkfun for the following, which was DEEP INSIDE their code at...
  https://learn.sparkfun.com/tutorials/gps-differential-vector-pointer?_ga=2.164764732.241967599.1597170078-1918172881.1594807152#code
  ( (The "me" of that "my" being: TK Boyd, author of ar790 and https://wywtk.com/ec/iec/iec217.htm)

  I have made tweaks, if only to the comments.

  This function reads data from the buffer associated with the softserial input set up for.
  It receives NEMA data from a GPS receiver which is passed into a TinyGPS Object and
  parsed using its internal functions for $GPRMC info.
  This function uses pointers to pass information to pass back to the code that calls it.
  As presented below, latitude, longitude, and the status of the GPS signal are harvested.
  The following are also available: Date, time, velocity and heading.*/

{
  float flat;
  float flon;
  unsigned long fix_age;
  //Look for serial data from GPS and loop until the end of NEMA string
  //A STREAM of ASCII should be arriving from the GPS unit.
  //If no data is arriving, this will be passed over, and
  //you may well get a report of a GPS unit that is "not locked on" (Stautus=0)

  int c;
  while (GPS_Serial.available())
  {
   c=GPS_Serial.read();
   if (gps.encode(c));
         {}
  }//end of while

  //Pull parsed data from gps object
  gps.f_get_position(&flat, &flon, &fix_age);
  *lat = flat;
  *lon = flon;
  //  float falt = gps.f_altitude(); // +/- altitude in meters
  //  float fc = gps.f_course(); // course in degrees
  //  float fmps = gps.f_speed_mps(); // speed in m/sec

  // check if data is relevant
  if (fix_age == TinyGPS::GPS_INVALID_AGE)
    //No fix detected;
  {
    *Status = 0;
  }

  else if (fix_age > 5000)
    //Warning: possible stale data!;
  {
    *Status = 1;
  }

  else
    //Data is current;
  {
    *Status = 2;
  }
}//end of GetGPS

How it works... Level 1 explanation

At the core of this is the...

void getGPS( float* lat, float* lon, int* Status)

... subroutine.

When you call that, some bytes will be harvested from the serial port buffer, and the three variables lat, lon, and Status will be filled with values. Note that the variables lat, lon, and Status are defined in the declaration of the getGPS subroutine, and there are some "even more local" additional variables, flat and flon declared within it. (Apologies for the inconsistencies in variable naming within the program.)

When getGPS is called... just after the line with "loop()" on it... the "ordinary", names-up-to-you variables TargetLat, TargetLon andStatus are filled with values. (What is in them before you make the call doesn't have any effect.)

They are filled with a "clever" scheme which is triggered by the &s and *s. It isn't particularly familiar to me... but it works!

For "Chapter and verse" on the standard, officially-supported, TinyGPS library....

http://arduiniana.org/libraries/tinygps/

I infer from something I saw there that the standard TinyGPS only looks at the $GPRMC sentence... which does not contain altitude information. There's also an extended, unofficial version... at least I think that's what it was. (Maybe the "extensions" have now been incorporated into the "standard" TinyGPS?). It is called "TinyGPS++", aka "TinyGPSPlus" and there's more on that at http://arduiniana.org/libraries/tinygpsplus/ from Mikal Hart. In August 2020, the page's most recent update had been in 2014, but the comment stream had entries from 2018.

TinyGPS++ offers the fix's altitude and the number of satellites used. It also offers some "simple" mathematical functions, such as units conversions and distance-between-two-places calculators.

But I digress. Use the standard library and my little programs to "walk" before you try to "run"!

How it works... Level 2 explanation

What's happening inside getGPS?

It is deceptively "simple"... as all good code usually is.

It starts with...

while (GPS_Serial.available())
  {
   c=GPS_Serial.read();
   if (gps.encode(c));
         {}
  }//end of while

That "sucks" the current serial buffer "dry", passing each byte there, in turn, to gps.encode.

gps.encode uses what it is given to fill some of the... "properties"?... of the object.

Once the above code has been executed, if there was satisfactory material in the serial buffer, things like gps.f_get_position and gps.crack_datetime(&year, &month, &day, &hour, &bLMinute, &bLSecond, &bLHundredth); work. Those functions allow you to "harvest" numbers from the "gps" object, and put them in "your" variables, for use in your code.

I take it on faith that part of using what is sent includes seeing that the checksum validates the data. I trust that the checksum test is part of setting the value my code returns in the Status variable.

Bigger and Better....

Here is the promised "more thorough program"...

#include <TinyGPS.h>
#include <SoftwareSerial.h>

//See Serial.Println in setup() for version ID
//Simple demo GP-20U7 GPS module...

//Be careful about adding bits in... If you get
//status=0 for more than 50 seconds (it shouldn't last
// for more than 30, actually), then take out
// things that prevent rapid repeats of the getGPS
// sub-routine.

//Written for Qduino... not that it should much matter.
//  ... nothing exotic here!

//You can usually leave serial monitor open. All should be
//well, even on multiple compiles.

/*======== COULD BE INCORPORATED...
// returns speed in 100ths of a knot
speed = gps.speed();

// course in 100ths of a degree
course = gps.course();

float flat, flon;

// returns +/- latitude/longitude in degrees
gps.f_get_position(&flat, &flon, &fix_age);
float falt = gps.f_altitude(); // +/- altitude in meters
float fc = gps.f_course(); // course in degrees
float fk = gps.f_speed_knots(); // speed in knots
float fmph = gps.f_speed_mph(); // speed in miles/hr
float fmps = gps.f_speed_mps(); // speed in m/sec
float fkmph = gps.f_speed_kmph(); // speed in km/hr

=========*/

SoftwareSerial GPS_Serial(9,10); // RX, TX
//Connect a 9600 baud serial stream of ASCII with
//NMEA sentences to the pin with the number that appears
//first in the SoftwareSerial statement above.
//
//9,10 WORKS for SoftwareSerial with Qduino,
// at least 9 for Rx. (10 not used, but a pin has
// to be specified to make SoftwareSerial happy.)

TinyGPS gps;
//For "Chapter and verse" on the TinyGPS library, see
//http://arduiniana.org/libraries/tinygps/
//(You might want to consider TinyGPS++, if you want "more"... including the device's altitude)

float TargetLat;
float TargetLon;
int Status = 0;
int iGlobalYear;
byte bGlobalMonth, bGlobalDay, bGlobalHour, bGlobalMinute, bGlobalSecond, bGlobalHundredth;

void getGPS(float* lat, float* lon, int* Status,
  int* iLocalYear,
  byte* bLocalMonth, byte* bLocalDay,
  byte* bLocalHour, byte* bLocalMinute,
  byte* bLocalSecond, byte* bLocalHundredth);

void AddStatusDescriptor(byte bStatus);

void setup() {
  Serial.begin(9600);
  GPS_Serial.begin(9600);
  delay(2000);//give ports chance to initialize
  //pinMode(led, OUTPUT);//Just for "proof of life"!

  Serial.println("---");
  Serial.println(" ");
  Serial.println("Hello from ar790");
  Serial.println("Vers 23 Aug 20, 09:30");
  Serial.println("See https://wywtk.com/ec/iec/iec217.htm");
  Serial.println("... for discussion of this code.");
  Serial.println("Assumes input via pin 9 of a 9600 baud serial stream of NMEA ASCII");
  Serial.println("Nothing appearing? Probably nothing arriving via the input. Should");
  Serial.println("give SOMETHING immediately after power up.");
  Serial.println(" ");
  Serial.println(" ");
  Serial.println(" ");
  delay(2000);// Give reader a chance to see the output.
}

void loop() {
  static long liCycles = 0;
  static byte bGlobalSecondPrev=61;
  //The program doesn't much like the use of things like delay(200);...
  //... it starts returning 0 in he Status variable for a reason I
  //haven't yet figured out.

  getGPS(&TargetLat, &TargetLon, &Status,
    &iGlobalYear,
    &bGlobalMonth, &bGlobalDay,
    &bGlobalHour, &bGlobalMinute,
    &bGlobalSecond, &bGlobalHundredth);

  //if ((liCycles % 80)==0) <scrap of alternate approach to
  //  reducing the number of output lines.

  if (not(bGlobalSecondPrev==bGlobalSecond))
  {
    //Serial.print(liCycles);
    //Serial.print("  "); <scraps of alternate approach to
    //  reducing the number of output lines.

    Serial.print(Status);

    //Print status to console to know if you are getting good data or not.
    //No Lock = 0, Old Data(>5 sec old) = 1, Good Data = 2
    AddStatusDescriptor(Status);
    //Previous is just a fancy replacement for...
    //  Serial.print("(status)");
    //  ... which is what would be here in a simple
    //  ... version of this.

    if (Status==2)
    {
    Serial.print(TargetLat);
    Serial.print("/");
    Serial.print(TargetLon);
    Serial.print("(lat/long)  ");
    Serial.print(bGlobalDay);
    Serial.print("/");
    Serial.print(bGlobalMonth);
    Serial.print("/");
    Serial.print(iGlobalYear);
    Serial.print("(dd/mm/yr)  ");
    Serial.print(bGlobalHour);
    Serial.print(":");
    Serial.print(bGlobalMinute);
    Serial.print(":");
    if (bGlobalSecond<10) {
      Serial.print("0");
      }
    Serial.print(bGlobalSecond);
    bGlobalSecondPrev=bGlobalSecond;
    Serial.print(".");
    if (bGlobalHundredth<10) {
      Serial.print("0");}
    Serial.print(bGlobalHundredth);
    Serial.print("(hr:min:sec.fraction, UTC)");
    }//end of "then" of if status==2
    Serial.println();
  }//end of "then" of if not(bGlobalSecondPrev==bGlobalSecond)
  liCycles++;
}//end of loop()


void getGPS(float* lat, float* lon, int* Status,
  int* iLocalYear,
  byte* bLocalMonth, byte* bLocalDay,
  byte* bLocalHour, byte* bLocalMinute,
  byte* bLocalSecond, byte* bLocalHundredth)
/*My thanks to Sparkfun for the following, which was DEEP INSIDE their code at...
  https://learn.sparkfun.com/tutorials/gps-differential-vector-pointer?_ga=2.164764732.241967599.1597170078-1918172881.1594807152#code
  (The "me" off that "my thanks" being TK Boyd, author of ar790 and https://wywtk.com/ec/iec/iec217.htm)

  I have made tweaks, if only to the comments.

  This function reads data from the buffer associated with the SoftSerial input set up for.
  It receives NEMA data from a GPS receiver which is passed into a TinyGPS Object and
  parsed using its internal functions for $GPRMC info.
  This function uses pointers to pass information to pass back to the code that calls it.
  As presented below, latitude, longitude, and the status of the GPS signal are harvested.
  The following are also available: Date, time, velocity and heading.*/

{
  float flat;
  float flon;
  unsigned long fix_age;
  //Look for serial data from GPS and loop until the end of NEMA string
  //A STREAM of ASCII should be arriving from the GPS unit.
  //If no data is arriving, this will be passed over, and
  //you may well get a report of a GPS unit that is "not locked on" (Stautus=0)

  int year;
  byte month, day, hour, bLMinute, bLSecond, bLHundredth;

  int iByteFrmGPS;
  while (GPS_Serial.available())
  {
   iByteFrmGPS=GPS_Serial.read();
   if (gps.encode(iByteFrmGPS));
         {}
  }//end of while

  //Pull parsed data from gps object
  gps.f_get_position(&flat, &flon, &fix_age);
  *lat = flat;
  *lon = flon;
  //  THESE MAY ALSO BE AVAILABLE... though you may need to use
  //     the non-standard TinyGPS++ (aka TinyGPSPlus)
  //     (http://arduiniana.org/libraries/tinygpsplus/) to get them.
  //  They are not remmed out for any
  //     particular reason... I just haven't got to them yet.
  //  float falt = gps.f_altitude(); // +/- altitude in meters
  //  float fc = gps.f_course(); // course in degrees
  //  float fmps = gps.f_speed_mps(); // speed in m/sec

  //Pull datetime...
  gps.crack_datetime(&year, &month, &day,
  &hour, &bLMinute, &bLSecond, &bLHundredth);

  *iLocalYear = year;
  *bLocalMonth = month;
  *bLocalDay = day;
  *bLocalHour = hour;
  *bLocalMinute = bLMinute;
  *bLocalSecond = bLSecond;
  *bLocalHundredth = bLHundredth;

  // check if data is relevant
  if (fix_age == TinyGPS::GPS_INVALID_AGE)
    //No fix detected;
  {
    *Status = 0;
  }

  else if (fix_age > 5000)
    //Warning: possible stale data!;
  {
    *Status = 1;
  }

  else
    //Data is current;
  {
    *Status = 2;
  }
}//end of GetGPS

void AddStatusDescriptor(byte bStatus)
  {
    //Just a fancy replacement for...
    //  Serial.print(bStatus);
    //....but with bStutus interpreted.
    if (bStatus==0) {
    Serial.print("(no lock) ");
    }
    if (bStatus==1) {
    Serial.print("(old data) ");
    }
    if (bStatus==2) {
    Serial.print("(good data) ");
    }
    if (bStatus>2) {
    Serial.print("(unknown status code) ");
    }
}//end of AddStatusDescriptor


A few words from wywtk.com's ("What You Want To Know") sponsors...

Please get in touch if you discover flaws in this page. Please mention the page's URL. (wywtk.com/ec/iec/iec217.htm).

If you found this of interest, please mention in forums, give it a Facebook "like", Google "Plus", or whatever. If you want more of this stuff, help!? There's not much point in me writing these things, if no one feels they are of any use.



index sitemap
What's New at the Site Advanced search
Search tool (free) provided by FreeFind... whom I've used since 2002. Happy with it, obviously!

Unlike the clever Google search engine, this one merely looks for the words you type, so....
*    Spell them properly.
*    Don't bother with "How do I get rich?" That will merely return pages with "how", "do", "I"....

Please also note that I have three other sites, and that this search will not include them. They have their own search buttons.

My SheepdogSoftware.co.uk site, where you'll find my main homepage. It has links for other areas, such as education, programming, investing.

My SheepdogGuides.com site.

My site at Arunet.




How to email or write this page's editor, Tom Boyd. Please cite page's URL if you write.


Valid HTML 4.01 Transitional Page has been tested for compliance with INDUSTRY (not MS-only) standards, using the free, publicly accessible validator at validator.w3.org. Mostly passes.

AND passes... Valid CSS!


Why does this page cause a script to run? Because of the Google panels, and the code for the search button. Also, I have my web-traffic monitored for me by eXTReMe tracker. They offer a free tracker. If you want to try one, check out their site. Why do I mention the script? Be sure you know all you need to about spyware.

....... P a g e . . . E n d s .....