A quick exercise in understanding and applying GPS data

Time Required: 2 Hours

2 Hours Cost: $75–$150

For makers, it has become quite cheap to incorporate high-quality geospatial data into electronics projects. And in the last few years, GPS (Global Positioning System) receiver modules have grown much more diverse, powerful, and easy to integrate with development boards like Arduino, PIC, Teensy, and Raspberry Pi. If you’ve been thinking of building around GPS, you’ve picked a good time to get started.

HOW IT WORKS

A GPS module is a tiny radio receiver that processes signals broadcast on known frequencies by a fleet of satellites. These satellites whirl around the Earth in roughly circular orbits, transmitting extremely precise position and clock data to the ground below. If the earthbound receiver can “see” enough of these satellites, it can use them to calculate its own location and altitude.

FUN FACT: GPS could not work without Einstein’s theory of relativity, as compensation must be made for the 38 microseconds the orbiting atomic clocks gain each day from time dilation in Earth’s gravitational field.

When a GPS message arrives, the receiver first inspects its broadcast timestamp to see when it was sent. Because the speed of a radio wave in space is a known constant (c), the receiver can compare broadcast and receive times to determine the distance the signal has traveled. Once it has established its distance from four or more known satellites, calculating its own position is a fairly simple problem of 3D triangulation. But to do this quickly and accurately, the receiver must be able to nimbly crunch numbers from up to 20 data streams at once.

Since the GPS system has a published goal of being usable everywhere on Earth, the system must ensure that at least four satellites — preferably more — are visible at all times from every point on the globe. There are currently 32 GPS satellites performing a meticulously choreographed dance in a sparse cloud 20,000 kilometers high.

COUNTING STARS

One evening I built a little Arduino/GPS test gizmo to spy on the GPS satellite constellation. I was able to count all 32 distinct satellites over a 24-hour period, with as many as 13 visible at once. You can grab the sketch from my Github repo and try it yourself with the setup described below.

GETTING STARTED

Whatever your project, GPS is simple to integrate. Most receiver modules communicate with a straightforward serial protocol, so if you can find a spare serial port on your controller board, it should take just a handful of wires to make the physical connection. And even if not, most controllers support an emulated “software” serial mode that you can use to connect to arbitrary pins.

For beginners, Adafruit’s Ultimate GPS Breakout module is a good choice. There are a lot of competing products on the market, but the Ultimate is a solid performer at a reasonable price, with big through-holes that are easy to solder or connect to a breadboard.

First, connect ground and power. In Arduino terms, this means connecting one of the microcontroller GND pins to the module’s GND, and the +5V pin to the module’s VIN. To manage data transfer, you also need to connect the module’s TX and RX pins to the Arduino. I’m going to arbitrarily select Arduino pins 2 (TX) and 3 (RX) for this purpose, even though pins 0 and 1 are specifically designed for use as a “hardware serial port” or UART.

Why? Because I don’t want to waste the only UART these low-end AVR processors have. Arduino’s UART is hard-wired to the onboard USB connector, and I like to keep it connected to my computer for debugging.

Sketch 1: A Toe in the Datastream

The instant you apply power, a GPS module begins sending chunks of text data on its TX line. It may not yet see a single satellite, much less have a “fix,” but the data faucet comes on right away, and it’s interesting to see what comes out. Our first simple sketch (below) does nothing but display this unprocessed data.

#include <SoftwareSerial.h> #define RXPin 2 #define TXPin 3 #define GPSBaud 4800 #define ConsoleBaud 115200 // The serial connection to the GPS device SoftwareSerial ss(RXPin, TXPin); void setup() { Serial.begin(ConsoleBaud); ss.begin(GPSBaud); Serial.println("GPS Example 1"); Serial.println("Displaying the raw NMEA data transmitted by GPS module."); Serial.println("by Mikal Hart"); Serial.println(); } void loop() { if (ss.available() > 0) // As each character arrives... Serial.write(ss.read()); // ... write it to the console. }

DisplayRawData.ino

NOTE:

The sketch defines the receive pin (RXPin) as 2, even though we said earlier that the transmit (TX) pin would be connected to pin 2. This is a common source of confusion. RXPin is the receive pin (RX) from the Arduino’s point of view. Naturally, it must be connected to the module’s transmit (TX) pin, and vice versa.

Upload this sketch and open Serial Monitor at 115,200 baud. If everything’s working, you should see a dense, endless stream of comma-separated text strings. Each will look something like this:

These distinctive strings are known as NMEA sentences, so called because the format was invented by the National Maritime Electronics Association. NMEA defines a number of these sentences

for navigational data ranging from the essential (location and time), to the esoteric (satellite signal-to-noise ratio, magnetic variance, etc.). Manufacturers are inconsistent about which sentence types their receivers use, but GPRMC is essential. Once your module gets a fix, you should see a fair number of these GPRMC sentences.

Sketch 2: Finding Yourself

It’s not trivial to convert the raw module output into information your program can actually use. Fortunately, there are some great libraries already available to do this for you. Limor Fried’s popular Adafruit GPS Library is a convenient choice if you’re using their Ultimate breakout. It’s written to enable features unique to the Ultimate (like internal data logging) and adds some snazzy bells and whistles of its own. My favorite parsing library, however — and here I am of course completely unbiased — is the one I wrote called TinyGPS++. I designed it to be comprehensive, powerful, concise, and easy to use. Let’s take it for a spin.

CODING WITH TINYGPS++

From the programmer’s point of view, using TinyGPS++ is very simple:

Create an object gps. Route each character that arrives from the module to the object using gps.encode(). When you need to know your position or altitude or time or date, simply query the gps object.

#include <SoftwareSerial.h> #include <TinyGPS++.h> #define RXPin 2 #define TXPin 3 #define GPSBaud 4800 #define ConsoleBaud 115200 // The serial connection to the GPS device SoftwareSerial ss(RXPin, TXPin); // The TinyGPS++ object TinyGPSPlus gps; void setup() { Serial.begin(ConsoleBaud); ss.begin(GPSBaud); Serial.println(" GPS Example 2 "); Serial.println(" A simple tracker using TinyGPS++. "); Serial.println(" by Mikal Hart "); Serial.println(); } void loop() { // If any characters have arrived from the GPS, // send them to the TinyGPS++ object while (ss.available() > 0) gps.encode(ss.read()); // Let's display the new location and altitude // whenever either of them have been updated. if (gps.location.isUpdated() || gps.altitude.isUpdated()) { Serial.print(" Location: "); Serial.print(gps.location.lat(), 6); Serial.print(" , "); Serial.print(gps.location.lng(), 6); Serial.print(" Altitude: "); Serial.println(gps.altitude.meters()); } }

FindingYourself.ino

Our second application continually displays the receiver’s location and altitude, using TinyGPS++ to help with parsing. In a real device, you might log this data to an SD card or display it on an LCD. Grab the library and sketch FindingYourself.ino (above). Install the library, as usual, in the Arduino libraries folder. Upload the sketch to your Arduino and open Serial Monitor at 115,200 baud. You should see your location and altitude updating in real time. To see exactly where you stand, paste some of the resulting latitude/longitude coordinates into Google Maps. Now hook up your laptop and go for a stroll or a drive. (But remember to keep your eyes on the road!)

THE FOURTH DIMENSION

Though we associate GPS with location in space, don’t forget those satellites are transmitting time- and datestamps, too. The average GPS clock is accurate to one ten-millionth of a second, and the theoretical limit is even higher. Even if you only need your project to keep track of time, a GPS module may still be the cheapest and easiest solution.

To turn FindingYourself.ino into a super-accurate clock, just change the last few lines like this:

if (gps.time.isUpdated()) { char buf[80]; sprintf(buf, " The time is %02d:%02d:%02d ", gps.time.hour(), gps.time.minute(), gps.time.second()); Serial.println(buf); }

Sketch 3: Finding Your Way

Our third and final application is the result of a personal challenge to write a readable TinyGPS++ sketch, in fewer than 100 lines of code, that would guide a user to a destination using simple text instructions like “keep straight” or “veer left.”

#include <SoftwareSerial.h> #include <TinyGPS++.h> #define RXPin 2 #define TXPin 3 #define GPSBaud 4800 #define ConsoleBaud 115200 // The serial connection to the GPS device SoftwareSerial ss(RXPin, TXPin); // The TinyGPS++ object TinyGPSPlus gps; unsigned long lastUpdateTime = 0; #define EIFFEL_LAT 48.85823 #define EIFFEL_LNG 2.29438 /* This example shows a basic framework for how you might use course and distance to guide a person (or a drone) to a destination. This destination is the Eiffel Tower. Change it as required. The easiest way to get the lat/long coordinate is to right-click the destination in Google Maps (maps.google.com), and choose "What's here?". This puts the exact values in the search box. */ void setup() { Serial.begin(ConsoleBaud); ss.begin(GPSBaud); Serial.println(" GPS Example 3 "); Serial.println(" A Not-so-comprehensive Guidance System "); Serial.println(" by Mikal Hart "); Serial.println(); } void loop() { // If any characters have arrived from the GPS, // send them to the TinyGPS++ object while (ss.available() > 0) gps.encode(ss.read()); // Every 5 seconds, do an update . if (millis() - lastUpdateTime >= 5000) { lastUpdateTime = millis(); Serial.println(); // Establish our current status double distanceToDestination = TinyGPSPlus::distanceBetween( gps.location.lat(), gps.location.lng(),EIFFEL_LAT, EIFFEL_LNG); double courseToDestination = TinyGPSPlus::courseTo( gps.location.lat(), gps.location.lng(), EIFFEL_LAT, EIFFEL_LNG); const char *directionToDestination = TinyGPSPlus::cardinal(courseToDestination); int courseChangeNeeded = ( int )(360 + courseToDestination - gps.course.deg()) % 360; // debug Serial.print(" DEBUG: Course2Dest: "); Serial.print(courseToDestination); Serial.print(" CurCourse: "); Serial.print(gps.course.deg()); Serial.print(" Dir2Dest: "); Serial.print(directionToDestination); Serial.print(" RelCourse: "); Serial.print(courseChangeNeeded); Serial.print(" CurSpd: "); Serial.println(gps.speed.kmph()); // Within 20 meters of destination? We're here! if (distanceToDestination <= 20.0) { Serial.println(" CONGRATULATIONS: You've arrived! "); exit(1); } Serial.print(" DISTANCE: "); Serial.print(distanceToDestination); Serial.println(" meters to go. "); Serial.print(" INSTRUCTION: "); // Standing still? Just indicate which direction to go. if (gps.speed.kmph() < 2.0) { Serial.print(" Head "); Serial.print(directionToDestination); Serial.println(" . "); return; } if (courseChangeNeeded >= 345 || courseChangeNeeded < 15) Serial.println(" Keep on straight ahead! "); else if (courseChangeNeeded >= 315 && courseChangeNeeded < 345) Serial.println(" Veer slightly to the left. "); else if (courseChangeNeeded >= 15 && courseChangeNeeded < 45) Serial.println(" Veer slightly to the right. "); else if (courseChangeNeeded >= 255 && courseChangeNeeded < 315) Serial.println(" Turn to the left. "); else if (courseChangeNeeded >= 45 && courseChangeNeeded < 105) Serial.println(" Turn to the right. "); else Serial.println(" Turn completely around. "); } }

FindingYourWay.ino

Every 5 seconds the code captures the user’s location and course (direction of travel) and calculates the bearing (direction to the destination), using the TinyGPS++ courseTo() method. Comparing the two vectors generates a suggestion to keep going straight or turn, as shown below.

Copy the sketch FindingYourWay.ino (above) and paste it into the Arduino IDE. Set a destination 1km or 2km away, upload the sketch to your Arduino, run it on your laptop, and see if it will guide you there. But more importantly, study the code and understand how it works.

Going Further

The creative potential of GPS is vast. One of the most satisfying things I ever made was a GPS-enabled puzzle box that opens only at one preprogrammed location. If your victim wants to get the treasure locked inside, she has to figure out where that secret location is and physically bring the box there. (See The Reverse Geocache Puzzle, MAKE Volume 25.) A popular first project idea is some sort of logging device that records the minute by minute position and altitude of, say, a hiker walking the Trans-Pennine Trail. Or what about one of those sneaky magnetic trackers the DEA agents in Breaking Bad stick on the bad guys’ cars? Both are totally feasible, and would probably be fun to build, but I encourage you to think more expansively, beyond stuff you can already buy on Amazon. It’s a big world out there. Roam as far and wide as you can.

Share it: #makegps

This article appeared in MAKE Volume 37, page 58.