gps logger closedI 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?

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.

Fritzing wire diagram. The GPS is missing because there’s no Fritzing symbol for that and I’m too lazy to create it. It only requires four wires: VCC, GND, TX and RX. The last two are connected to digital pins 2 and 3.

Here are some pictures and a video of the logger:

In this video the blue warning led is missing. This was later added, as seen in above pictures.

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: