Monday, March 11, 2013

TrunkTrackerino v .9 finished

OK I can officially put this down for a while! TrunkTrackerino .9 is finally working - I'm recording GPS coordinates and writing them when I have a valid fix. I had issues with the log file, but I've worked those out. All that's left now:
  • Have to fix an issue with the log file time stamp
  • Have to add in logic to sleep when speed ! >= 1
  • Have to slow down the read (it's every .5 seconds right now)
  • Bonus: add logic to put the device to sleep for 5 min when speed !>=.5, to save on battery power
Once those changes are done (and I'm kind of burned out on the Arduino now), then I'm ready to throw this in the trunk of my car and do a little 'applied testing' before I let my boys show me how they manage their driving.

After that, my next addition will be an APRS shield so I can start communicating geo-loc over radio. That is the base bundle for the balloon instrument pack.

Code:
// TrunkTracker sketch combines code from GPSTest_RMC and SD_GPSLogger
// Throw your Arduino in the trunk to see where your teens really go
// and how fast they really drive.

#include "Arduino.h"
//#include "GPSconfig.h"
#include <avr/sleep.h>
#include <SD.h>

// If running Arduino < 1.0, add references to WProgram.h and NewSoftSerial.h
#include "SoftwareSerial.h"

// Set this to 0 when in production; all serial output will be diasabled
#define DEBUG 0

// My shield is wired with RX/TX swapped
//SoftwareSerial mySerial = SoftwareSerial(2, 3);
SoftwareSerial mySerial = SoftwareSerial(3, 2);

// power saving modes
#define SLEEPDELAY 0    /* power-down time in seconds. Max 65535. Ignored if TURNOFFGPS == 0 */
#define TURNOFFGPS 0    /* set to 1 to enable powerdown of arduino and GPS. Ignored if SLEEPDELAY == 0 */
#define LOG_RMC_FIXONLY 0  /* set to 1 to only log to SD when GPS has a fix */

// Set up pins used
#define powerpin 4      // Use pin 4 to control power to GPS
#define led1Pin 5
#define led2Pin 6

// Set GPSRATE to 4800 for my GPS
#define GPSRATE 4800

// Create a buffer of 90 to store GPS data. Usually about 80 chars so 90 is plenty
#define BUFFSIZE 90

// Create an int for iterating
uint8_t i;

// Create instance of logfile
File logFile;

// On the Ethernet Shield, CS is pin 4. Note that even if it's not
// used as the CS pin, the hardware CS pin (10 on most Arduino boards,
// 53 on the Mega) must be left as an output or the SD library
// functions will not work.
const int chipSelect = 10;

// Global variables
char buffer[BUFFSIZE];     // string buffer for the sentence
char *parseptr;            // a character pointer for parsing the GPS sentence
char buffidx;              // an indexer into the buffer
bool fix = false;          // current fix on data
bool gotGPRMC = false;             // true if current data is a GPRMC string

// The time date, location, data, etc.
uint8_t hour, minute, second, year, month, date;
uint32_t latitude, longitude;
uint8_t groundspeed, trackangle;
char latdir, longdir;
char validity;

// Setup the sketch
void setup()
{
  // First set up GPS comms
  if (powerpin)
  {
    pinMode(powerpin, OUTPUT);
  }
  // Use the pin 13 LED as an indicator
  pinMode(13, OUTPUT);
  // connect to the serial terminal at 9600 baud
  Serial.begin(9600);
  // connect to the GPS at the appropriate rate
  // IF THIS IS WRONG for your GPS, change it above
  mySerial.begin(GPSRATE);
  // print title with ending line break
  if (DEBUG)
     Serial.println("GPS Parser started");
  digitalWrite(powerpin, LOW);  //pull low to turn on
 
 
  // Now set up SD for logging
  // This code lifted from "Datalogger" sample sketch
  if (DEBUG)
    Serial.println("Initializing SD card...");
 // make sure that the default chip select pin is set to
  // output, even if you don't use it:
  pinMode(10, OUTPUT);
 // see if the card is present and can be initialized:
  if (!SD.begin(chipSelect)) {
    Serial.println("Card init. failed!");
    error(1);
  }
  // Now create a unique file
  strcpy(buffer, "GPSLOG00.TXT");
  for (i = 0; i < 100; i++) {         
    // Keep iterating till you find unique file name
    // Create if does not exist, do not open existing, write, sync after write
    buffer[6] = '0' + i/10;
    buffer[7] = '0' + i%10;
    if (! SD.exists(buffer)) {
      break;
    }
  }
  // Now open the file just created
  logFile = SD.open(buffer, FILE_WRITE);
  if( ! logFile ) {
    if (DEBUG)
    {
       Serial.print("Couldnt create "); Serial.println(buffer);
    }
    error(3);
  }
  if (DEBUG)
  {
     Serial.print("Writing to "); Serial.println(buffer);
  }
  // Done opening log file
}

uint32_t parsedecimal(char *str)
{
  uint32_t d = 0;
 
  while (str[0] != 0)
  {
    if ((str[0] > '9') || (str[0] < '0'))
       return d;
    d *= 10;
    d += str[0] - '0';
    str++;
  }
  return d;
}

// Read line from GPS unit
void readline(void)
{
  if (DEBUG)
    Serial.print("Input from GPS: ");
  char c;
  buffidx = 0; // start at beginning of line
  while (1)
  {
    c = mySerial.read();
    if (c == -1)
       continue;
    // Output status to screen
    if (DEBUG)
       Serial.print(c);
    if (c == '\n')
       continue;
      
    // if we're nearing the end of buffer, terminate
    if ((buffidx == BUFFSIZE-1) || (c == '\r'))
    {
      buffer[buffidx] = 0;
      return;
    }
    // Write each char into buffer
    buffer[buffidx++] = c;
  }
}

// Main program - keep doing this over and over
void loop()
{
  uint32_t tmp;
 
  if (DEBUG)
     Serial.print("\n\rRead: ");
  readline();
 
  // Check if $GPRMC (global positioning fixed data)
  if (strncmp(buffer, "$GPRMC",6) == 0)
  {
    // Set gotGPRMC
    gotGPRMC = true;
    //hhmmss time data
    parseptr = buffer+7;
    tmp = parsedecimal(parseptr);
    hour = tmp / 10000;
    minute = (tmp / 100) % 100;
    second = tmp %100;
   
    parseptr = strchr(parseptr, ',') + 1;
    validity = parseptr[0];
    parseptr +=2;
   
    /*
    // Break out if "validity" = "V"
    if(validity = 'V')
    {
      if(DEBUG)
         Serial.print("\nGPS navigation error. Trying again...");
      return;
    }
    */
   
    // grab latitude & longitude data
    // latitude
    latitude = parsedecimal(parseptr);
    if (latitude != 0)
    {
      latitude *= 10000;
      parseptr = strchr(parseptr, '.') + 1;
      latitude += parsedecimal(parseptr);
    }
    parseptr = strchr(parseptr, ',') + 1;
   
    // read latitude N/S data
    if (parseptr[0] != ',')
    {
      latdir = parseptr[0];
    }
   
    // longituide
    parseptr = strchr(parseptr, ',')+1;
    longitude = parsedecimal(parseptr);
    if (longitude != 0)
    {
      longitude *= 10000;
      parseptr = strchr(parseptr, '.') + 1;
      longitude += parsedecimal(parseptr);
    }
    parseptr = strchr(parseptr, ',') + 1;
    // read longitude E/W data
    if (parseptr[0] != ',')
    {
      longdir = parseptr[0];
    }
   
    // groundspeed
    parseptr = strchr(parseptr, ',') + 1;
    groundspeed = parsedecimal(parseptr);
   
    // trackangle, just 'cause
    parseptr = strchr(parseptr, ',') + 1;
    trackangle = parsedecimal (parseptr);
   
    // date
    parseptr = strchr(parseptr, ',') + 1;
    tmp = parsedecimal(parseptr);
    date = tmp / 10000;
    month = (tmp / 100) % 100;
    year = tmp % 100;
   
    // OK - we've parsed the full string. Now let's print to serial if in debug mode
    if (DEBUG)
    {
        Serial.print("\n\tTime: ");
        Serial.print(hour, DEC); Serial.print(':');
        Serial.print(minute, DEC); Serial.print(':');
        Serial.println(second, DEC);
        Serial.print("\tDate: ");
        Serial.print(month, DEC); Serial.print('/');
        Serial.print(date, DEC); Serial.print('/');
        Serial.println(year, DEC);
        Serial.print("\n\tStatus: "); Serial.print(validity);
       
        Serial.print("\n\tLat: ");
        if (latdir == 'N')
           Serial.print('+');
        else if (latdir == 'S')
           Serial.print('-');
   
        Serial.print(latitude/1000000, DEC); Serial.print("* ");
        Serial.print((latitude/10000)%100, DEC); Serial.print('\''); Serial.print(' ');
        Serial.print((latitude%10000)*6/1000, DEC); Serial.print('.');
        Serial.print(((latitude%10000)*6/10)%100, DEC); Serial.println('"');
      
        Serial.print("\tLong: ");
        if (longdir == 'E')
           Serial.print('+');
        else if (longdir == 'W')
           Serial.print('-');
        Serial.print(longitude/1000000, DEC); Serial.print("* ");
        Serial.print((longitude/10000)%100, DEC); Serial.print('\''); Serial.print(' ');
        Serial.print((longitude%10000)*6/1000, DEC); Serial.print('.');
        Serial.print(((longitude%10000)*6/10)%100, DEC); Serial.println('"');
       
        Serial.print("\tGroundspeed: ");
        Serial.println(groundspeed);
       
        Serial.println("Now writing to SD card...\n\t");
        Serial.println(buffer);
    }
   
    // Now log tab separated string
    digitalWrite(led2Pin, HIGH);    // First turn on LED2 to indicate writing to SD
    logFile.write((uint8_t *) buffer, buffidx);  // write the string to the SD card
    logFile.println("");
    logFile.flush();
   
    digitalWrite(led2Pin, LOW);    // turn off LED2 (write to SD is finished)
   
    buffidx = 0;                 // reset buffer pointer
   
    if(fix)
    {
      if ((TURNOFFGPS) && (SLEEPDELAY))
      {
        digitalWrite(powerpin, HIGH); // turn off GPS
        delay(100);                   // wait for serial monitor write to finish
        sleep_sec(SLEEPDELAY);         // turn off CPU
       
        digitalWrite(powerpin, LOW);  // turn on GPS
      }  // if (TURNOFFGPS)
    } // if (fix)
   
    return;
  } // if(gotGPRMC)
   
}

// Handling Errrs HERE
// blink out an error code
void error(uint8_t errno) {
  if (DEBUG)
  {
    /* Need to investigate
      if (SD.errorCode()) {
        putstring("SD error: ");
        Serial.print(card.errorCode(), HEX);
        Serial.print(',');
        Serial.println(card.errorData(), HEX);
      }
      */
     Serial.print("Error writing to SD card. ");

  }

  while(1) {
    for (i=0; i<errno; i++) {
      digitalWrite(led1Pin, HIGH);
      digitalWrite(led2Pin, HIGH);
      delay(1000);
      digitalWrite(led1Pin, LOW);
      digitalWrite(led2Pin, LOW);
      delay(100);
    }
    for (; i<10; i++) {
      delay(200);
    }
  }
}

void sleep_sec(uint16_t x) {
  while (x--) {
     // set the WDT to wake us up!
    WDTCSR |= (1 << WDCE) | (1 << WDE); // enable watchdog & enable changing it
    WDTCSR = (1<< WDE) | (1 <<WDP2) | (1 << WDP1);
    WDTCSR |= (1<< WDIE);
    set_sleep_mode(SLEEP_MODE_PWR_DOWN);
 //   sleep_enable();
    sleep_mode();
//    sleep_disable();
  }
}


 

Sunday, March 10, 2013

TrunkTrackerino

It's simple, really:
I have teens...
They drive my car...
They are boys...
I am an engineer...
I like life hacks...
I have an Arduino...
And a GPS logging shield....

Therefore, TrunkTrackerino.

More than two years ago I started a push to get my Arduino working with the GPS logger shield. It failed, mostly because I had some other life pressures. I recently pulled out my Arduino and started messing with it again, not really sure why... And I got the GPS working. And then I thought... I can build a simple device to throw in the trunk that will record driving speed! And thus TrunkTrackerino was born.

There are sketches for using a GPS. There are also sketches for logging GPS data to a card reader. But I really didn't like any of them individually, so I have merged them together. And as of today, I believe I have it working (not entirely sure, since I'm in my hotel and can't pick up signal--I will know more tomorrow).

The goal is to build an Arduino-based device which
* tracks speed and location
* sleeps when there is no motion
* logs nothing when there's no valid data
* stores data on an SD card
* can be imported into XL to view historical speed data
* can be imported into Google Earth for bread crumb data

Eventually this project will end up as the base for the APRS balloon tracking system. For now, it's enough to know my boys are as honest as they say they are.

Update: I took a brief stroll around the hotel entrance today. For some reason, the logging code is logging an empty file. The GPS is working great--when I watch in Arduino's serial monitor, I can see good valid strings which are being parsed great. I'd say I'm 10 lines of code from a working beta.