DIY GPS Logger
I need a GPS logger, don’t have one, don’t want to buy one, so let’s build one! I have all the necessary parts, so let’s start!
1. What do I want?
I need a device that logs location, speed and course on an SD card. I could use my Android phone to do this, but the GPS would drain the battery within the hour. So let’s build a dedicated device.
Because it won’t have a display, it needs to have a way to show if it has a GPS fix. It needs to run on a 3s lipo (11,1v) for as long as possible (preferably >12 hours), but I also want it to warn me when the battery voltage gets too low because lipo’s should never be discharged below 3v per cell (9v total) or they get damaged.
All that must fit in a waterproof case. Why waterproof? That will be clear in a future post 🙂
2. What do I need?
- 1x Arduino Uno (€23,95, http://www.iprototype.be)
- 1x Openlog (€24,95, https://www.sparkfun.com)
- 1x GPS module (€39,90, http://www.flytron.com)
- A small breadboard
- A led digit, I chose red
- 1x 74HC595N shift-register (€0,69, http://www.iprototype.be)
- 1x led, I chose a 3 mm blue one
- 9x 150 Ohm resistors
- 1x 220 Ohm resistor
- 1x 100 Ohm resistor
- 1x 11,1v 3S lipo, I used a 2200 mAh Zippy Flightmax from Hobbyking ($8,99, http://www.hobbyking.com)
- A bunch of breadboard wires
- A watertight box, I chose the transparent Otterbox 2000 (€15,65, http://www.otterbox.be)
I gave some prices and websites where I get my stuff. Prices are constantly changing so they are just an indication.
3. The build
Start with connecting the GPS module to the Arduino. I will be using SoftwareSerial on digital pins 2 and 3. When the Arduino starts receiving NMEA sentences from the GPS, it will be processed using the TinyGPS library. Every five seconds, the data will be sent using pins 0 and 1 (hardware serial) to an OpenLog, which writes it to a micro SD card. The led digit will light up for one second showing the amount of GPS satellites it has a fix with. If it’s more than 9, only the dot will light up, telling me there’s more than enough signal.
The led digit is driven by a 74HC595 shift register. It accepts serial data from the Arduino using digital pins 8, 9 and 10 and switches the different leds of the digit in parallel. Using a shift register has the advantage that it only requires three pins of the Arduino. If I wanted to drive the led digit directly from the arduino, I would require eight. Each led of the digit needs 2v at 20 mA, so use 150 Ohm resistors.
The battery is connected to a voltage divider. Depending on the battery voltage, it sends a signal between 0 and 5v to pin A0. The resistors were chosen so that 5v corresponds to a fully charged lipo, 12,6v. I was lazy and used a voltage divider calculator. This gave me this result: R1 = 150 Ohm, R2 = 100 Ohm.
I however measured the real value of the resistors and use those in my code to gain a bit of precision.
I chose an alarm voltage of 9,6v (3,2v per cell). When the Arduino detects a voltage equal or below 9,6v, it will light up the led connected on digital pin 7 (with a 220 Ohm resistor in between). Ideally you’ll want an audio alarm as well, for example a piezo alarm.
You’ll need to figure out which led digit pin lights which segment. In my case it was trial and error because it wasn’t as described in the datasheet. I’ve marked the names of the segments in my code (B=upper, M=middle, O=under, L=left, R=right).
I’ve made a quick wire diagram in Fritzing. I’m used to Eagle, so bear with me.

Here are some pictures and a video of the logger:







4. The code
//voltage divider:
// (+) ---------R1----pin A0-----R2------- (-)
//R1 = 148.23 R2 = 100.04
//Vout = Vin * R2/(R1+R2)
#include <SoftwareSerial.h>
#include <TinyGPS.h>
#define UPDATETIME 5000 //This defines how many milliseconds need to be between each update i.e. writing the values to the SD card and flashing the LED digit.
#define R1 148.23
#define R2 100.04
#define ALARMVOLTAGE 9.6 //battery voltage the alarm LED needs to start blinking
int BatAnalogValue;
float BatVoltageValue;
float BatAnalogLimit = (1023 / 5.0) * ALARMVOLTAGE * R2 / (R1 + R2); //for 9.6v this should be about 791
//LED digit
int SER_Pin = 8; //pin 14 on the 75HC595
int RCLK_Pin = 9; //pin 12 on the 75HC595
int SRCLK_Pin = 10; //pin 11 on the 75HC595
#define number_of_74hc595s 1 //How many of the shift registers - change this
#define numOfRegisterPins number_of_74hc595s * 8 //do not touch
boolean registers[numOfRegisterPins];
//LED digit END
//GPS
TinyGPS gps;
#define RXPIN 2
#define TXPIN 3
SoftwareSerial nss(RXPIN, TXPIN); //for the gps
float GPS_flat, GPS_flon, GPS_falt, GPS_fc, GPS_fkmph;
unsigned long GPS_number_of_satellites = 0;
int GPS_year = 0 ;
byte GPS_month, GPS_day, GPS_hour, GPS_minute, GPS_second, GPS_hundredths = 0 ;
unsigned long GPS_fix_age = 0;
//GPS END
unsigned long previousTime, currentTime;
void setup()
{
pinMode(SER_Pin, OUTPUT);
pinMode(RCLK_Pin, OUTPUT);
pinMode(SRCLK_Pin, OUTPUT);
clearRegisters(); //reset all register pins
writeRegisters();
previousTime = currentTime = 0;
pinMode(7, OUTPUT);
nss.begin(9600);
Serial.begin(9600);
delay(1000); //needed to let the OpenLog boot up
Serial.println("Mils\tDay\tMon\tYr\tHr\tMin\tSec\tLat\tLon\tAge\tSats\tkm/h\tCrs\tAlt\tBatV");
}
void clearRegisters(){ //set all register pins to LOW
for(int i = numOfRegisterPins - 1; i >= 0; i--){
registers[i] = LOW;
}
}
//Set and display registers
//Only call AFTER all values are set how you would like (slow otherwise)
void writeRegisters(){
digitalWrite(RCLK_Pin, LOW);
for(int i = numOfRegisterPins - 1; i >= 0; i--){
digitalWrite(SRCLK_Pin, LOW);
int val = registers[i];
digitalWrite(SER_Pin, val);
digitalWrite(SRCLK_Pin, HIGH);
}
digitalWrite(RCLK_Pin, HIGH);
}
//set an individual pin HIGH or LOW
void setRegisterPin(int index, int value){
registers[index] = value;
}
void digitOne()
{
clearRegisters();
setRegisterPin(1, HIGH); // RB
setRegisterPin(5, HIGH); // RO
writeRegisters();
}
void digitTwo()
{
clearRegisters();
setRegisterPin(1, HIGH); // RB
setRegisterPin(3, HIGH); // B
setRegisterPin(6, HIGH); // O
setRegisterPin(0, HIGH); // M
setRegisterPin(7, HIGH); // LO
writeRegisters();
}
void digitThree()
{
clearRegisters();
setRegisterPin(1, HIGH); // RB
setRegisterPin(3, HIGH); // B
setRegisterPin(6, HIGH); // O
setRegisterPin(0, HIGH); // M
setRegisterPin(5, HIGH); // RO
writeRegisters();
}
void digitFour()
{
clearRegisters();
setRegisterPin(1, HIGH); // RB
setRegisterPin(0, HIGH); // M
setRegisterPin(5, HIGH); // RO
setRegisterPin(2, HIGH); // LB
writeRegisters();
}
void digitFive()
{
clearRegisters();
setRegisterPin(2, HIGH); // LB
setRegisterPin(3, HIGH); // B
setRegisterPin(6, HIGH); // O
setRegisterPin(0, HIGH); // M
setRegisterPin(5, HIGH); // RO
writeRegisters();
}
void digitSix()
{
clearRegisters();
setRegisterPin(2, HIGH); // LB
setRegisterPin(3, HIGH); // B
setRegisterPin(6, HIGH); // O
setRegisterPin(0, HIGH); // M
setRegisterPin(5, HIGH); // RO
setRegisterPin(7, HIGH); // LO
writeRegisters();
}
void digitSeven()
{
clearRegisters();
setRegisterPin(1, HIGH); // RB
setRegisterPin(5, HIGH); // RO
setRegisterPin(3, HIGH); // B
writeRegisters();
}
void digitEight()
{
clearRegisters();
setRegisterPin(2, HIGH); // LB
setRegisterPin(3, HIGH); // B
setRegisterPin(6, HIGH); // O
setRegisterPin(0, HIGH); // M
setRegisterPin(5, HIGH); // RO
setRegisterPin(7, HIGH); // LO
setRegisterPin(1, HIGH); // RB
writeRegisters();
}
void digitNine()
{
clearRegisters();
setRegisterPin(2, HIGH); // LB
setRegisterPin(3, HIGH); // B
setRegisterPin(6, HIGH); // O
setRegisterPin(0, HIGH); // M
setRegisterPin(5, HIGH); // RO
setRegisterPin(1, HIGH); // RB
writeRegisters();
}
void digitZero()
{
clearRegisters();
setRegisterPin(2, HIGH); // LB
setRegisterPin(3, HIGH); // B
setRegisterPin(6, HIGH); // O
setRegisterPin(5, HIGH); // RO
setRegisterPin(7, HIGH); // LO
setRegisterPin(1, HIGH); // RB
writeRegisters();
}
void loop()
{
currentTime = millis();
BatAnalogValue = analogRead(A0);
BatVoltageValue = 5.0/1023 * BatAnalogValue * (R1 + R2)/R2;
if (BatAnalogValue <= BatAnalogLimit) {
digitalWrite(7, HIGH);
}
else {
digitalWrite(7, LOW);
}
while (nss.available())
{
int c = nss.read();
if (gps.encode(c))
{
// returns +/- latitude/longitude in degrees
gps.f_get_position(&GPS_flat, &GPS_flon, &GPS_fix_age);
GPS_falt = gps.f_altitude(); // +/- altitude in meters
GPS_fc = gps.f_course(); // course in degrees
GPS_fkmph = gps.f_speed_kmph(); // speed in km/hr
gps.crack_datetime(&GPS_year, &GPS_month, &GPS_day, &GPS_hour, &GPS_minute, &GPS_second, &GPS_hundredths, &GPS_fix_age);
GPS_hour = GPS_hour+2;
if (GPS_hour >= 24) GPS_hour = GPS_hour -24;
GPS_number_of_satellites = gps.satellites();
}
}
if (currentTime - previousTime >= UPDATETIME) {
if (GPS_number_of_satellites > 0) {
Serial.print (currentTime);
Serial.print ("\t");
Serial.print (GPS_day);
Serial.print ("\t");
Serial.print (GPS_month);
Serial.print ("\t");
Serial.print (GPS_year);
Serial.print ("\t");
Serial.print (GPS_hour);
Serial.print ("\t");
Serial.print (GPS_minute);
Serial.print ("\t");
Serial.print (GPS_second);
Serial.print ("\t");
Serial.print (GPS_flat,5);
Serial.print ("\t");
Serial.print (GPS_flon,5);
Serial.print ("\t");
Serial.print (GPS_fix_age);
Serial.print ("\t");
Serial.print (GPS_number_of_satellites);
Serial.print ("\t");
Serial.print (GPS_fkmph);
Serial.print ("\t");
Serial.print (GPS_fc);
Serial.print ("\t");
Serial.print (GPS_falt);
Serial.print ("\t");
Serial.println (BatVoltageValue);
switch (GPS_number_of_satellites) { //display number of satellites on the LED digit
case 1:
digitOne();
break;
case 2:
digitTwo();
break;
case 3:
digitThree();
break;
case 4:
digitFour();
break;
case 5:
digitFive();
break;
case 6:
digitSix();
break;
case 7:
digitSeven();
break;
case 8:
digitEight();
break;
case 9:
digitNine();
break;
default:
clearRegisters();
setRegisterPin(4, HIGH); //dot
writeRegisters();
}
GPS_number_of_satellites = 0; //set it to 0 because otherwise if the GPS looses signal, the number of satellites stores its last value
}
else {
Serial.print (currentTime);
Serial.print ("\t");
Serial.print ("No GPS fix");
Serial.print ("\t\t\t\t\t\t\t\t\t\t\t\t\t");
Serial.println (BatVoltageValue);
digitZero();
}
previousTime = currentTime;
}
if (currentTime - previousTime >= 1000) { //disable the LED digit after one second to conserve power
clearRegisters();
writeRegisters();
}
}
5. Current usage
I’ve measured the current used during signal acquisition with a Watt’s Up meter. The GPS uses the most current during signal acquisition, so I kept the GPS inside the house so it couldn’t get a fix. This should give me the worst case current usage.
I measured during 855398 millis, so 855 seconds and during that period the current used was 0,030 Ah. This means that during that period the average current was 126 mA (30 mA * 3600 s / 855 s). Using a 2200 mAh battery, the GPS logger should run at least (worst case) about 17,5 hours. Already more than enough for what I will be using it for. The GPS module uses about 10 mA less when it has a fix. This gives a runtime of about 19 hours.
6. Output and data processing
Openlog stores a txt file with tab separated values. These are a few lines:
Mils Day Mon Yr Hr Min Sec Lat Lon Age Sats km/h Crs Alt BatV 5007 No GPS fix 11.23 10039 No GPS fix 11.26 15068 No GPS fix 11.26 535523 22 10 2012 8 41 12 51.13767 4.17538 72 7 89.56 293.16 13.80 11.23 540523 22 10 2012 8 41 17 51.13831 4.17349 71 8 88.01 301.46 17.70 11.23 545523 22 10 2012 8 41 22 51.13907 4.17186 75 8 86.89 310.30 20.00 11.23 550523 22 10 2012 8 41 27 51.13996 4.17043 68 8 75.36 319.45 22.30 11.23 555523 22 10 2012 8 41 32 51.14080 4.16947 76 8 66.89 326.39 23.80 11.23 560523 22 10 2012 8 41 37 51.14139 4.16892 77 8 51.82 337.25 22.90 11.23
Next you can map the data points using for example GPS Visualizer. I’m not going to fully explain that as GPS Visualizer has a great help/FAQ section.
An example of the data above:
