Introduction: Personal Black Box - Arduino Mega + Ultimate GPS Shield + LSM303

About: I am a recent graduate from the University of Pittsburgh. While i was there i got a Bachelors degree in Computer Science and explored a few new hobbies, one of which being circuitry. I purchased a few Arduino'…
Personal Black Box using:
-
Arduino Mega 2560
-Ultimate GPS datalogger shield
-Triple-axis Accelerometer + Magnetometer (LSM303)


After experiencing many difficulties using the gps shield with an Arduino Uno, i soon learned i was maxing out the capabilities of the unit itself. The Uno can handle the GPS, Datalogger, and the accelerometer, but it cannot handle all three at once. With what i was planning on using this for, i needed something a little beefier.

I decided to use an Arduino Mega 2560 instead (a leonardo would also do the job). However this opened up new difficulties which were not the easiest to find information on. Once i had a working model i wanted to share my success here first!

Let me also mention, my project is in no way protected from falls, water damage, or even heavy shaking. It could be, but i simply used a little bit of foam to keep the device still in the project box.


The Steps are broken down as such:
Step 1: pull data from GPS shield, store it into sd card in readable format
Step 2: store data onto sd card in csv format for GPS Visualizer
Step 3: pull data from Accelerometer and Magnetometer (LSM303)
Step 4: Final Results

Lets Get Started!

Step 1: Arduino Mega + Ultimate GPS Datalogger

I started by modifying shield_sdlog to work with the Arduino Mega. As you will read on (https://learn.adafruit.com/adafruit-ultimate-gps-logger-shield) the Arduino Mega does not support SoftSerial on pins 7 and 8. I ran two wires from the TX and the RX on the GPS shield to pins 18(TX1) and 19(RX1). Make sure to have the GPS TX go to the Mega RX, and the GPS RX to the Mega TX.

Then simply comment out:

    //SoftwareSerial mySerial(8, 7);
And add:
    HardwareSerial mySerial = Serial1;
Serial1 refers to TX1 and RX1

Also make sure your code matches this section:
    if (!SD.begin(chipSelect, 11, 12, 13)) {
    //if (!SD.begin(chipSelect)) {      // if you're using an UNO, you can use this line instead
    Serial.println("Card init. failed!");
    error(2);
We are obviously not using an Uno on this project.


Remove this section:
    char *stringptr = GPS.lastNMEA();
    uint8_t stringsize = strlen(stringptr);
    if (stringsize != logfile.write((uint8_t *)stringptr, stringsize))    //write the string to the SD file
      error(4);
    if (strstr(stringptr, "RMC"))   logfile.flush();
    Serial.println();

And replace it with your own:
      logfile.print("\nTime: ");
      logfile.print(GPS.hour, DEC);
      logfile.print(':');
      logfile.print(GPS.minute, DEC);
      logfile.print(':');
      logfile.print(GPS.seconds, DEC);
      logfile.print('.');
      logfile.println(GPS.milliseconds);
      logfile.print("Date: ");     
      logfile.print(GPS.month, DEC);
      logfile.print('/');
      logfile.print(GPS.day, DEC);
      logfile.print("/20");
      logfile.println(GPS.year, DEC);
     
      logfile.print("Location: ");
      logfile.print(GPS.latitude, 4);
      logfile.print(GPS.lat);
      logfile.print(", ");
      logfile.print(GPS.longitude, 4);
      logfile.println(GPS.lon);
     
      logfile.print("Speed (knots): ");
      logfile.println(GPS.speed);
     
      logfile.print("Angle: ");
      logfile.println(GPS.angle);
     
      logfile.print("Altitude: ");
      logfile.println(GPS.altitude);
     
      logfile.print("Satellites: ");
      logfile.println((int)GPS.satellites);
      logfile.flush();
      Serial.println("success."); 

Your results will look something like this:
Time: 19:24:4.0
Date: 4/6/2014
Location: XXXX.XXXXN, XXXX.XXXXW
Speed (knots): 0.79
Angle: 118.05
Altitude: 118.80
Satellites: 6

This writes out a pretty easy to read txt file which alone might be enough for some. If you want to turn your file into a GPS file, you'll have to format it into something a little different. I chose comma-separated values (CSV) because it is easy to work with. Simply write your data into a blank notepad using commas to separated each column.

Step 2: Arduino Mega + Ultimate GPS Datalogger CSV

If you want to write to a CSV file, change the (.TXT) in the filename to (.CSV).
    char filename[15];
    strcpy(filename, "GPSLOG00.CSV");
    for (uint8_t i = 0; i < 100; i++) {
    filename[6] = '0' + i/10;
    filename[7] = '0' + i%10;

Next, find this line:
  if( ! logfile ) {
      Serial.print("Couldnt create "); Serial.println(filename);
      error(3);
    }
    Serial.print("Writing to "); Serial.println(filename);
 
And add:
  logfile.println("Time, Date, Latitude, Longitude, Elevation, Speed (Knots), Angle, Satellites");
  logfile.flush();
This will start off your csv file with some headers. Just make sure to keep your formatting here to begin the csv file.


I'm sure you remember where we changed this next section at. Use this to print out the data to your CSV files in the correct format:
      logfile.print(GPS.hour, DEC);
      logfile.print(':');
      logfile.print(GPS.minute, DEC);
      logfile.print(':');
      logfile.print(GPS.seconds, DEC);
      logfile.print('.');
      logfile.print(GPS.milliseconds);
      logfile.print(",");

      logfile.print(GPS.month, DEC); 
      logfile.print('/');
      logfile.print(GPS.day, DEC);
      logfile.print("/20");
      logfile.print(GPS.year, DEC);
      logfile.print(",");

      logfile.print(GPS.latitude, 4);
      logfile.print(GPS.lat);
      logfile.print(", ");
      logfile.print(GPS.longitude, 4);
      logfile.print(GPS.lon);
      logfile.print(",");
      logfile.print(GPS.altitude);
      logfile.print(",");
      logfile.print(GPS.speed);
      logfile.print(",");
      logfile.print(GPS.angle);
      logfile.print(",");
      logfile.println((int)GPS.satellites);
      logfile.flush();
      Serial.println("success."); 

Your results will look beautiful if you open the csv file in Excel, however if you open it in notepad you'll see something like this:
Time, Date, Latitude, Longitude, Elevation, Speed (Knots), Angle, Satellites
18:37:0.0,0/0/200,XXXX.XXXXN, XXXX.XXXXW,137.30,0.00,0.00,8
18:37:0.0,4/6/2014,XXXX.XXXXN, XXXX.XXXXW,137.30,0.06,195.78,8
18:37:0.984,4/6/2014,XXXX.XXXXN, XXXX.XXXXW,137.30,0.06,195.78,8
18:37:0.984,4/6/2014,XXXX.XXXXN, XXXX.XXXXW,137.30,0.08,195.78,8
18:37:2.0,4/6/2014,XXXX.XXXXN, XXXX.XXXXW,137.30,0.08,195.78,8
18:37:2.0,4/6/2014,XXXX.XXXXN, XXXX.XXXXW,137.30,0.12,195.78,8
18:37:3.0,4/6/2014,XXXX.XXXXN, XXXX.XXXXW,137.30,0.12,195.78,8
18:37:3.0,4/6/2014,XXXX.XXXXN, XXXX.XXXXW,137.30,0.29,195.78,8
18:37:4.0,4/6/2014,XXXX.XXXXN, XXXX.XXXXW,137.30,0.29,195.78,8
18:37:4.0,4/6/2014,XXXX.XXXXN, XXXX.XXXXW,137.30,0.29,195.78,8

Now head over toGPS VIsualizerto see your results on a map!

Step 3: Arduino Mega + Accelerometer (LSM303)

To hook up the accelerometer, simply attached the SCL and SDA pins on the LSM303 to the Mega's SCL(21) and SDA (20) pins. (That's LSM303 SCL to Mega SCL, then LSM303 SDA to Mega SDA)

Then attach the 5v and Ground to the appropriate locations. I also managed to use a piece of anti static foam underneath the device with a little hot glue to mount the accelerometer to the GPS's proto board.


i started with the LSM303 library from (https://github.com/pololu/lsm303-arduino). This contains three examples, one of which, "Calibrate", i ended up using quite often. This worked for me without any modification and allowed me to test my wiring. I used this to pull the minimum and maximum values for the accelerometer x,y,x axis, these values will be used to balance out the device when calculating G-Forces.

G-Forces:
To calculate the G-Forces on the LSM303 we just have to balance out the accelerometer values and then scale them down. I cannot find the site where i first got the code so i apologize in advance but i hope to soon have that updated.


First! Run the calibrate sketch from the LSM303 library to get your x,y,z max and min values. To do that, hold your LSM303 on each of the six axis and refresh the sketch holding it very still. You don't want to cause any shakes to the device as that will throw off its readings, this took me several tries to finally get it calibrated correctly.

When you are done, on a flat surface you should get a reading like this
(:: 0.00G, -0.00G, 1.00G) where x and y are 0 and z is 1. it might not be exact but it should float around those numbers.

#include <Wire.h>
#include <LSM303.h>

LSM303 compass;
// these are my values, they most likely will not work for you
// run the calibrate sketch from the LSM303 library to get your
// maximum and minimum values for x, y, z
int xRawMin = -16240;
int xRawMax = 15616;
int yRawMin = -17216;
int yRawMax = 16992;
int zRawMin = -14560;
int zRawMax = 18448;

void setup()
{
  Serial.begin(14400);
  Wire.begin();
  compass.init();
  compass.enableDefault();
}

void loop()
{
  compass.read();
  long xRaw = compass.a.x;
  long yRaw = compass.a.y;
  long zRaw = compass.a.z;
 
    // Convert raw values to 'milli-Gs"
    long xScaled = map(xRaw, xRawMin, xRawMax, -1000, 1000);
    long yScaled = map(yRaw, yRawMin, yRawMax, -1000, 1000);
    long zScaled = map(zRaw, zRawMin, zRawMax, -1000, 1000);
 
    // re-scale to fractional Gs
    float xAccel = xScaled / 1000.0;
    float yAccel = yScaled / 1000.0;
    float zAccel = zScaled / 1000.0;
 
    Serial.print(" :: ");
    Serial.print(xAccel);
    Serial.print("G, ");
    Serial.print(yAccel);
    Serial.print("G, ");
    Serial.print(zAccel);
    Serial.println("G");
 
  delay(200);
  }



Pitch and Roll:
I found a thread on the arduino forums by "ArthurSpooner" for calculating Pitch and Roll
, i simply modified the existing code for my LSM303. Add in the libraries and setup from the calibrate sketch, then just remove the analogread's and replace it with your compass.a.x.

Here is the modified version of Arnie's code that i used:

   //code by Arne
   //the acclerometer it set up the way that the z acceleration looks
   //to the sky, and x y is flat --> like most cartesian coordinate systems
   //I use an A7270 as an Accelerometer -> they are on a breakout board for approx $20 USD
   //and a HMC5843 as a triple axis magnetic Sensor $50 USD
   //feel free to comment on the code -> improvement is always appreciated
   //you can ask questions in English or German

   //required for calculations
   #include <math.h>
   #include <Wire.h>
   #include <LSM303.h>
   LSM303 compass;
   //by increasing alphaAccel the response will become faster
   //but the noise will increae [alpha must be between 0 and 1]
   //values for digital lowpass
   float alphaAccel = 0.4;
   float alphaMagnet = 0.4;
   unsigned int xOffset=0; unsigned int yOffset=0; unsigned int zOffset=0;                      
   float Pitch=0;
   float Roll=0;
   float Yaw=0;
   int xRaw=0; int yRaw=0; int zRaw=0;
   float xFiltered=0; float yFiltered=0; float zFiltered=0;
   float xFilteredOld=0; float yFilteredOld=0; float zFilteredOld=0;
   float xAccel=0; float yAccel=0; float zAccel=0;

   void setup()
   {
  Serial.begin(115200);       //initialize serial port
  analogReference(EXTERNAL);  //use external reference voltage (3,3V)
  delay(2000);  //calibrate sensor after a short delay
  Wire.begin();
  compass.init();
  compass.enableDefault();
  getAccelOffset();           //keep it flat and non moving on the table
  //there are other ways to calibrate the offset, each has some advantes
  //and disadvantes..
  //compare application note AN3447
  //http://www.freescale.com/files/sensors/doc/app_note/AN3447.pdf
  }


 void FilterAD()
 {
  // read from AD and subtract the offset
  xRaw=compass.a.x-xOffset;
  yRaw=compass.a.y-yOffset;
  zRaw=compass.a.z-zOffset; 
  //Digital Low Pass - compare: (for accelerometer)
  //http://en.wikipedia.org/wiki/Low-pass_filter
  xFiltered= xFilteredOld + alphaAccel * (xRaw - xFilteredOld);
  yFiltered= yFilteredOld + alphaAccel * (yRaw - yFilteredOld);
  zFiltered= zFilteredOld + alphaAccel * (zRaw - zFilteredOld); 
  xFilteredOld = xFiltered;
  yFilteredOld = yFiltered;
  zFilteredOld = zFiltered;   
  }
 

void AD2Degree()
  {
  // 3.3 = Vref; 1023 = 10Bit AD; 0.8 = Sensivity Accelerometer
  // (compare datasheet of your accelerometer)
  // the *Accel must be between -1 and 1; you may have to
  // to add/subtract +1 depending on the orientation of the accelerometer
  // (like me on the zAccel)
  // they are not necessary, but are useful for debugging
  xAccel=xFiltered *3.3 / (1023.0*0.8);      
  yAccel=yFiltered *3.3 / (1023.0*0.8);      
  zAccel=zFiltered *3.3 / (1023.0*0.8)+1.0;
 
  // Calculate Pitch and Roll (compare Application Note AN3461 from Freescale
  // http://www.freescale.com/files/sensors/doc/app_note/AN3461.pdf
  // Microsoft Excel switches the values for atan2
  // -> this info can make your life easier :-D
  //angled are radian, for degree (* 180/3.14159)
  Roll   = atan2(  yAccel ,  sqrt(sq(xAccel)+sq(zAccel))); 
  Pitch  = atan2(  xAccel ,   sqrt(sq(yAccel)+sq(zAccel)));  
}

void loop()
{
  compass.read();
  FilterAD();
  AD2Degree(); 
  Serial.print("Pitch: ");
  Serial.print(int(Pitch*180/PI));
  Serial.print(" Roll: ");
  Serial.println(int(Roll*180/PI));
  delay(50);
}
 
void getAccelOffset()
{ //you can make approx 60 iterations because we use an unsigned int
  //otherwise you get an overflow. But 60 iterations should be fine
   compass.read();
   for (int i=1; i <= 60; i++){       
     xOffset += compass.a.x;    
     yOffset += compass.a.y;
     zOffset += compass.a.z;
     }
   xOffset /=60;  
   yOffset /=60;
   zOffset /=60;
   Serial.print("xOffset: "); Serial.print(xOffset); 
   Serial.print("   yOffset: "); Serial.print(yOffset);
   Serial.print("   zOffset: "); Serial.println(zOffset);
  
}

The code from ArthurSpooner also contains the compass heading, however i managed to find this code on adafruit and decided to use theirs instead, i tried using compass.heading() but i didnt get accurate results:

    compass.read();
    int mx = compass.m.x;
    int my = compass.m.y;

    float Pi = 3.14159;
    float heading = (atan2(my,mx) * 180) / Pi;
    // Normalize to 0-360
    if (heading < 0)
    {
      heading = 360 + heading;
    }
    Serial.println(heading);


Step 4: Put It All Together

Now that we've gotten results from the accelerometer, we just need to combine step 3 and step 4.

Once you have the accelerometer working with the GPS, add in a few lines to continue writing the data into the CSV file.
      logfile.print(",");
      logfile.print(int(Pitch*180/PI));
      logfile.print(",");
      logfile.print(int(Roll*180/PI));
      logfile.print(","); 
      logfile.print(xAccel);
      logfile.print("G, ");
      logfile.print(yAccel);
      logfile.print("G, ");
      logfile.print(zAccel);
      logfile.print("G");
      logfile.print(",");
      logfile.print(heading);
      logfile.print(",");
      logfile.println();
      logfile.flush();

Also make sure to update your CSV header

logfile.println("Time, Date, Latitude, Longitude, Elevation, Speed (Knots), Angle, Satellites, Pitch, Roll, G-Force x, y, z, Direction");

Once the code is combined you can go off in your own direction and create whatever comes to your mind. I chose to add a NeoPixel strip of 8 led's as a status light. The first 4 led's turn blue (success) or red (fail) when the SD card initializes, creates a file, writes to the file, and if the GPS gets a fix. The next 4 led's tell me the compass orientation (North, East, South, West).

Thanks for reading!

Sensors Contest

Participated in the
Sensors Contest

Arduino Contest

Participated in the
Arduino Contest