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.
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 from robotdyn.com
NEO 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):
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.
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 }
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:
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!)
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.
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.
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"); } } }
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.