Introduction: Hacker Tracker - in Depth

In this Instructable you will learn how to incorporate a MicroSD Card module, a GPS module, and a Digital Compass into your future Arduino creations.

This Instructable is intended to be an in depth, a very detailed, and a more beginner friendly version of the original HackerBoxes 0021: Hacker Tracker. I noticed some things different in my kit that were not addressed, and I had to overcome some obstacles which others may benefit from. I also took it further one more logical step and got both sensors writing periodic data to the SD card -- you'll find out how to do that here, and you'll see the code I used to get it working.

The kit can still be ordered through HackerBoxes.com, and below I'll provide links to everything for you to order the parts for yourself.

I try as much as possible to use graphics, pictures and illustrations to communicate the techniques and procedures. Don't forget you can click through the picture to get the high resolution version if you need it.

Step 1: Gather the Resources

You'll need the items listed below. I tried to find the exact model/chip/versions that I received and used to make this indestructible and provide links to them here. You'll need to solder a few headers on, but that's really the extent of "electronics" you'll be dealing with for this project. The everything else is simply aligning things on a breadboard and using jumpers everywhere -- very simple stuff.

Arduino Nano

Arduino Nano from robotdyn.com

otherPartsNEO 6M GPS : This comes with a ceramic antenna already attached, and the headers separate which you'll need to solder.

3-Axis Compass (HMC5883L) : This is the model which HackerBoxes 0021 covered, however not what arrived in my box. My Instructable references the QMC5883L, not the HMC5883L. I've found various references to this on in internet, some say the HMC is out of production. The Main difference is a difference in registers used, which means you'll need to follow the appropriate instructions for your coding (My Instructable will address the QMC5883L). Here is a closeup of the chip on the module which has markings consistent with the QMC5883L (DA 5883 6014) vs the HMC5883L(L883 2107):
Arduino Nano

Micro SD writer/reader: You'll likely need to solder the header onto this as well.

Breadboard & jumpers can be acquired in any basic starter kit, such as this one for $3, or this $& kit from Adafruit.

Any generic MicroSD Card with an adapter to read it in your computer (like this one from Adafruit or this combo MicroSD and SD Adapter).

Also, download the ArduinoIDE here.

Step 2: Setting the Arduino

First place the Arduino Nano module on the breadboard (as shown) and connect it to your computer via USB. Align it to the same numbered rows for simplicity to follow along in this tutorial.

Boot up the Arduino IDE and follow this excellent tutorial if you do not know how to burn your code onto the Arduino. Load up the blink sketch (as the tutorial shows) and ensure you get the blinky LED. Modify the wait times of the code and reload to ensure mastery of this concept.

Ensure you have connectivity and there is an LED blinking. I did a simple variation of the blinky LED and made it do the Morse Code version of SOS -- the file is uploaded to this step, and below (which could easily be modified with loops):

// the setup function runs once when you press reset or power the board
void setup() {
  // initialize digital pin LED_BUILTIN as an output.
  pinMode(LED_BUILTIN, OUTPUT);
}

// the loop function runs over and over again forever
void loop() {
  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(300);                       // wait for a second
  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
  delay(200);                       // wait for a second
  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(300);                       // wait for a second
  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
  delay(200);                       // wait for a second
  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(300);                       // wait for a second
  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
  delay(200);                       // wait for a second
  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(900);                       // wait for a second
  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
  delay(200);                       // wait for a second
  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(900);                       // wait for a second
  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
  delay(200);                       // wait for a second
  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(900);                       // wait for a second
  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
  delay(200);                       // wait for a second
  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(300);                       // wait for a second
  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
  delay(200);                       // wait for a second
  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(300);                       // wait for a second
  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
  delay(200);                       // wait for a second
  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(300);                       // wait for a second
  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
  delay(1000);                       // wait for a second
}

Step 3: The MicroSD Module

Add the SD Card Module and connect the jumper wires to the breadboard as shown above.

My breadboard look like this after wiring it up:

This Arduino tutorial shows the code you'll need to write to your SD card. It is also included as an *.ino file below. and here:

/*
  SD card read/write

 This example shows how to read and write data to and from an SD card file
 The circuit:
 * SD card attached to SPI bus as follows:
 ** MOSI - pin 11
 ** MISO - pin 12
 ** CLK - pin 13
 ** CS - pin 4 (for MKRZero SD: SDCARD_SS_PIN)

 created   Nov 2010
 by David A. Mellis
 modified 9 Apr 2012
 by Tom Igoe

 This example code is in the public domain.

 */

#include <SPI.h>
#include <SD.h>

File myFile;

void setup() {
  // Open serial communications and wait for port to open:
  Serial.begin(9600);
  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB port only
  }


  Serial.print("Initializing SD card...");

  if (!SD.begin(4)) {
    Serial.println("initialization failed!");
    return;
  }
  Serial.println("initialization done.");

  // open the file. note that only one file can be open at a time,
  // so you have to close this one before opening another.
  myFile = SD.open("test.txt", FILE_WRITE);

  // if the file opened okay, write to it:
  if (myFile) {
    Serial.print("Writing to test.txt...");
    myFile.println("testing 1, 2, 3.");
    // close the file:
    myFile.close();
    Serial.println("done.");
  } else {
    // if the file didn't open, print an error:
    Serial.println("error opening test.txt");
  }

  // re-open the file for reading:
  myFile = SD.open("test.txt");
  if (myFile) {
    Serial.println("test.txt:");

    // read from the file until there's nothing else in it:
    while (myFile.available()) {
      Serial.write(myFile.read());
    }
    // close the file:
    myFile.close();
  } else {
    // if the file didn't open, print an error:
    Serial.println("error opening test.txt");
  }
}

void loop() {
  // nothing happens after setup
}

*Note: Insert the Micro SD card for this to work!

This demo also writes to the Serial Terminal so you can see it working. If you are unfamiliar with using the serial terminal, Adafruit did a real good job in this tutorial. There you will learn how to interact with your Arduino on the serial interface.

You can also take the card out and put it in your computer (via a microSD adapter) to see the results.

**As an interesting side note, apparently the world of SD cards is a lot more interesting than I ever knew! See these blog posts for some information about counterfeit SD cards, learn about SD card architecture, and potential ways SD cards can be hacked!

http://www.bunniestudios.com/blog/?page_id=1022

https://www.bunniestudios.com/blog/?page_id=3592

This is the MicroSD card I got with my kit, for reference:

Step 4: Soldering the Headers (for Better or for Worse)

I am not really good at desoldering more than one flexible leg at a time - so 5 inflexible legs linked together looked daunting. Instead of unsoldering, I just heated each point and pushed the pins through one at a time. In this application - the spacer to the header ends up on the other side, but it still worked out to fit nicely into my breadboard. The graphic below shows the process.


Next I soldered the header on the digital compass module. I did not make sure it was straight before applying solder, therefore it came out crooked. This was a little easier fixed by trying to heat all the pins while bending it straight. It ended up fitting in just fine with all my other messed up pieces -- and they all functioned beautifully in the end.


I like how HackerBoxes, in their FAQ, says: "The goal is progress, not perfection". You'll likely screw some things up. Hopefully you've kept track of what you did (documentation! Logging!) then you can understand how to not do that again! (also, don't hide it - others can learn from your mistakes!)

Step 5: The GPS Module

Emplace the GPS Module as shown in the graphic above. Note that the GPS Module and the SD Card module will be sharing the same VCC and GND rows, which are connected via jumper to the NANO.

Mine looks like this after connecting the two jumpers.:

I found this site that provided links to the GPS Module datasheet, which could come in handy depending on how deep in the weeds you want to get with this module. Some documents will be listed below for convenience.


The following code is what the HackerBoxes Instructable provided, attributed to a Mr. Ken Burns who made a very fun looking project over at Make: . Load this up in your Arduino IDE and it will start pushing out the GPS data to your SD card and to your Serial Port.

/*
   Log GPS NMEA data to an SD card every second
   
   This code was adapted from the MAKE: GPS Cat Tracker Project 
   <a href="http://makezine.com/projects/make-37/gps-cat-tracker-2/"> <a href="http://makezine.com/projects/make-37/gps-cat-trac...</a"> <a href="http://makezine.com/projects/make-37/gps-cat-trac...</a"> http://makezine.com/projects/make-37/gps-cat-trac...</a>>>
*/
 
#include <SoftwareSerial.h>
#include <SPI.h>
#include <SD.h>
 
 
// Arduino pins used by the GPS module
static const int GPS_RXPin = A1;
static const int GPS_TXPin = A0;
static const int GPSBaud = 9600;

// Arduino pin for SD Card
static const int SD_ChipSelect = 4;
 
// The GPS connection is attached with a software serial port
SoftwareSerial Gps_serial(GPS_RXPin, GPS_TXPin);
 
 
void setup()
{    
  
  // Open the debug serial port at 9600
  Serial.begin(9600);
 
  // Open the GPS serial port  
  Gps_serial.begin(GPSBaud);  
   
  Serial.print("Initializing SD card...");
  // make sure that the default chip select pin is set to
  // output, even if you don't use it:
  pinMode(SD_ChipSelect, OUTPUT);
  
  // see if the card is present and can be initialized:
  if (!SD.begin(SD_ChipSelect)) {
    Serial.println("Card failed, or not present");
    // don't do anything more:
    return;
  }
  Serial.println("card initialized.");   
}
 
 
int inByte = 0;         // incoming serial byte
byte pbyGpsBuffer[100];
int byBufferIndex = 0;
 
void loop()
{
  byte byDataByte;
  
  if (Gps_serial.available())
  {
     byDataByte = Gps_serial.read();
    
     Serial.write(byDataByte);
     pbyGpsBuffer[ byBufferIndex++ ] = byDataByte;
     
     if( byBufferIndex >= 100 )
     {
       byBufferIndex = 0;       
       File dataFile = SD.open("gps.txt", FILE_WRITE);
    
       // if the file is available, write to it:
       if (dataFile) {
        dataFile.write(pbyGpsBuffer, 100);
        dataFile.close();
      }  
      // if the file isn't open, pop up an error:
      else {
        Serial.println("error opening gps.txt");
      }        
     }      
  }
}


Now, you might want to know what all those letters and numbers mean. That is the NMEA protocol or format. If you want to learn to parse the data, you'll want to reference that site. Also, to "translate" that into something google friendly, and view it on a map, this site is great! You can just paste the data in, or upload the file.

When the GPS is not tracking much, you'll see lines like this:

$GPRMC,000009.800,V,,,,,0.00,0.00,060180,,,N*43
$GPVTG,0.00,T,,M,0.00,N,0.00,K,N*32
$GPGGA,000010.800,,,,,0,0,,,M,,M,,*41
$GPGSA,A,1,,,,,,,,,,,,,,,*1E
$GPRMC,000010.800,V,,,,,0.00,0.00,060180,,,N*4B
$GPVTG,0.00,T,,M,0.00,N,0.00,K,N*32
$GPGGA,000011.800,,,,,0,0,,,M,,M,,*40
$GPGSA,A,1,,,,,,,,,,,,,,,*1E
$GPRMC,000011.800,V,,,,,0.00,0.00,060180,,,N*4A
$GPVTG,0.00,T,,M,0.00,N,0.00,K,N*32
$GPGGA,000012.800,,,,,0,0,,,M,,M,,*43
$GPGSA,A,1,,,,,,,,,,,,,,,*1E
$GPGSV,1,1,00*79

All those commas that have nothing between them are delimiting pieces of information that the GPS does not yet know - because it is not yet tracking. Those websites can tell you basically all you'd want to know about what all that data means. It is telling you the sattelites it is tracking, the Lat/Long coordinates, the GPS time, etc.

Later on, when I put together all the sensors, I do some parsing, if you want an example of pulling out just a part of the data and recording that.

Step 6: The 3-Axis Digital Compass Module

Wire is up according the the graphic above. Below, you'll see what it looks like what I've got mine completely wired up.

This website provides a great tutorial, pictures and code for this compass. - and it is useful except it is for the Digital Compass Module with the correct chip. Remember this problem from a previous step:

I found this library on GitHub, which works perfectly for me for this particular chip. I ended up using it without exploring the issue too much further or having any problems getting data.

If you do not know how to download a library and make it work with your Arduino IDE, this tutorial should help. In a nutshell, you click the "Clone or Download" link in GitHub and download the ZIP. Then unzip that folder into your Arduino Libraries folder:

"User-created libraries as of version 0017 go in a subdirectory of your default sketch directory. For example, on OSX, the new directory would be ~/Documents/Arduino/libraries/. On Windows, it would be My Documents\Arduino\libraries\. To add your own library, create a new directory in the libraries directory with the name of your library. The folder should contain a C or C++ file with your code and a header file with your function and variable declarations. It will then appear in the Sketch | Import Library menu in the Arduino IDE."

This guide also provides some more insight into Arduino IDE libraries.

I include a datasheet below for the HMC5883L -- which should be quite accurate for most features of the chip.

Step 7: Synthesis -- Logging the Compass and the GPS Data Together

So, in order to do some custom parsing of the GPS data, and to keep tracking the compass data, and logging it at a less than insane rate, this was mostly a code exercise.

Here is the code I Frankensteined together (tried to comment as much as possible to explain the logic / thought process:

/*
   Log GPS NMEA data to an SD card every second
   
   This code was adapted from the MAKE: GPS Cat Tracker Project 
   http://makezine.com/projects/make-37/gps-cat-tracker-2/

   AND https://www.arduino.cc/en/Tutorial/ReadWrite

   AND https://github.com/mechasolution/Mecha_QMC5883L

*/
 
#include    //GPS
#include               //SD Card & GPS
#include                //SD Card & GPS
#include              //Compass
#include      //Compass
#include            //My Synthesis Contribution -- doing the parsing

MechaQMC5883 qmc;

// Arduino pins used by the GPS module
static const int GPS_RXPin = A1;
static const int GPS_TXPin = A0;
static const int GPSBaud = 9600;

// Arduino pin for SD Card
static const int SD_ChipSelect = 4;
 
// The GPS connection is attached with a software serial port
SoftwareSerial Gps_serial(GPS_RXPin, GPS_TXPin);
 
void setup()
{    
  Wire.begin();   //part of Compass initialization 
  // Open the debug serial port at 9600
  Serial.begin(9600);
  qmc.init();   //part of Compass initialization 
  // Open the GPS serial port  
  Gps_serial.begin(GPSBaud);  
   
  Serial.print("Initializing SD card...");
  // make sure that the default chip select pin is set to
  // output, even if you don't use it:
  pinMode(SD_ChipSelect, OUTPUT);
  
  // see if the card is present and can be initialized:
  if (!SD.begin(SD_ChipSelect)) {
    Serial.println("Card failed, or not present");
    // don't do anything more:
    return;
  }
  Serial.println("card initialized.");   
}

int inByte = 0;         // incoming serial byte
char pbyGpsBuffer[200];
int byBufferIndex = 0;
byte ex[3] = "x: ";     //will be used to build the output for the Compass Data
byte why[4] = " y: ";
byte zee[4] = " z: ";
byte newl[2] = {'\r','\n'}; // crlf

void getGPSline() {
    //gets an entire line and returns the size of the line
    byBufferIndex = 0;
    byte byDataByte;
    bool moreLine = true;
    while(moreLine){
      if (Gps_serial.available()){
        byDataByte = Gps_serial.read();
        pbyGpsBuffer[ byBufferIndex++ ] = byDataByte;
        if(byDataByte == '\n' || byDataByte == '\r') {
          moreLine = false;
        }
      }
    }
    return;
}

bool isLatLong(){
  //looks at pbyGpsBuffer and determines if it has Lat Long NEMA formatted data (GPRMC
  //http://www.gpsinformation.org/dale/nmea.htm#RMC
  //find '$GPRMC' anywhere in the buffer, cut out, that is now pbyGpsBuffer
  const char s[2] = "$";              //this is how the lines al start
  const char selector[5] = "GPRMC";   //this is the line of data I want
  char *token;

  token = strtok(pbyGpsBuffer, s); //split up the buffer by $'s
  while( token != NULL ) {
      if(memcmp(selector,token,5) == 0) { //if it finds GPRMC in the first 5
        return true;
      }
      token = strtok(NULL, s);    //loads the next token into token
  }
  memset(pbyGpsBuffer, '\0', 200); //clear / erase it
  return false;
}

void loop()
{
  int x,y,z;
  getGPSline(); //checks to see if GPS data is availible, then fills the 
                //buffer: pbyGpsBuffer. It only fills up to a \n\r
                //then it returns here

  if(isLatLong()){
    //when the LatLong data is detected in the pbyGpsBuffer
    Serial.println(pbyGpsBuffer); //send the GPS data to the serial port

    qmc.read(&x,&y,&z);        //get compass info - store and send to serial
      Serial.print("x: ");
      Serial.print(x);
      Serial.print(" y: ");
      Serial.print(y);
      Serial.print(" z: ");
      Serial.println(z);

    File dataFile = SD.open("gps.txt", FILE_WRITE);
    // if the file is available, write to it:
    if (dataFile) {
      //first the compass info line formatted: x: y: z:
      dataFile.write(ex, 3);
      dataFile.print(x);
      dataFile.write(why, 4);
      dataFile.print(y);
      dataFile.write(zee, 4);
      dataFile.println(z);

      //this is the GPS data line
      dataFile.println(pbyGpsBuffer);
      dataFile.close();
    }  
    // if the file isn't open, pop up an error:
    else {
      Serial.println("error opening gps.txt");
    }   
  }
}

Step 8: Conclusion

I loaded this code up, plugged this all into a battery, and went for a drive. I even left it on while I was in the grocery store. No problems, it tracked for over an hour, producing my location and the compass data every hour into the MicroSD Card. Then I copy/pasted it into that site, and it showed me my route beautifully! It was even accurate as to which lane I was in, and the side of the lane (passenger side, as it sat on my seat).

That animated GIF above shows the red light on the GPS will blink off as it gets data, and the blue light on the Arduino blinks as it is getting serial data and writing the the SD card every second (I believe).

For anyone interested, over the course of the 1 hour and 7 minutes, my battery went from 75.448 W-h to 75.029 W-h.

If you made it this far, please leave any and all constructive feedback! I'd also like to know how you plan to incorporate this in a future project.