SACC

/ / navigation

Assignment
Reverse Geo-caching
Dispensing
Instructions

/ / Assignment



The city is a dirty a place, but that's no reason not to have an easter egg hunt. You will design and program a device that will do three things:
  1. Allow you to set locations (where your virtual easter eggs will be hidden)


  2. Once the hiding places have been set, your device will help a seeker, through a series of clues, find the hiding spots.


  3. When a hiding spot has been discovered, a treat will be dispensed






GPS modules typically put out a series of standard strings of information, under something called the National Marine Electronics Association (NMEA) protocol. More information on NMEA standard data strings can be found at this site

The tutorial code at the bottom of this page demonstrates how to decode and display the most common string, called $GPRMC. If all you need is date, time and position, you can to skip reading this, and just run the code below.

While you can write software to serially request other strings from the Parallax module, the following strings are automatically transmitted when the "/RAW" pin is pulled low.
  • $GPGGA: Global Positioning System Fix Data
  • $GPGSV: GPS satellites in view
  • $GPGSA: GPS DOP and active satellites
  • $GPRMC: Recommended minimum specific GPS/Transit data


Each of these sentences contains a wealth of data. For example, here are a few instances of the $GPRMC string, aka the "Recommended minimum specific GPS/Transit data" string:

 eg1. $GPRMC,081836,A,3751.65,S,14507.36,E,000.0,360.0,130998,011.3,E*62
 eg2. $GPRMC,225446,A,4916.45,N,12311.12,W,000.5,054.7,191194,020.3,E*68

           225446       Time of fix 22:54:46 UTC
           A            Navigation receiver warning A = Valid position, V = Warning
           4916.45,N    Latitude 49 deg. 16.45 min. North
           12311.12,W   Longitude 123 deg. 11.12 min. West
           000.5        Speed over ground, Knots
           054.7        Course Made Good, degrees true
           191194       UTC Date of fix, 19 November 1994
           020.3,E      Magnetic variation, 20.3 deg. East
           *68          mandatory checksum
           
eg3. $GPRMC,220516,A,5133.82,N,00042.24,W,173.8,231.8,130694,004.2,W*70
              1    2    3    4    5     6    7    8      9     10  11 12

      1   220516     Time Stamp
      2   A          validity - A-ok, V-invalid
      3   5133.82    current Latitude
      4   N          North/South
      5   00042.24   current Longitude
      6   W          East/West
      7   173.8      Speed in knots
      8   231.8      True course
      9   130694     Date Stamp
      10  004.2      Variation
      11  W          East/West
      12  *70        checksum

 eg4. for NMEA 0183 version 3.00 active the Mode indicator field is added
     $GPRMC,hhmmss.ss,A,llll.ll,a,yyyyy.yy,a,x.x,x.x,ddmmyy,x.x,a,m*hh
 Field #
 1    = UTC time of fix
 2    = Data status (A=Valid position, V=navigation receiver warning)
 3    = Latitude of fix
 4    = N or S of longitude
 5    = Longitude of fix
 6    = E or W of longitude
 7    = Speed over ground in knots
 8    = Track made good in degrees True
 9    = UTC date of fix
 10   = Magnetic variation degrees (Easterly var. subtracts from true course)
 11   = E or W of magnetic variation
 12   = Mode indicator, (A=Autonomous, D=Differential, E=Estimated, N=Data not valid)
 13   = Checksum





Parsing the data
Move TX to pin 2 and RX to pin 3, POWER to pin 4.

Upload this code:
// A simple sketch to read GPS data and parse the $GPRMC string 
// see http://www.ladyada.net/make/gpsshield for more info

#include <NewSoftSerial.h>

NewSoftSerial mySerial =  NewSoftSerial(2, 3);
#define powerpin 4

#define GPSRATE 4800
//#define GPSRATE 38400


// GPS parser for 406a
#define BUFFSIZ 90 // plenty big
char buffer[BUFFSIZ];
char *parseptr;
char buffidx;
uint8_t hour, minute, second, year, month, date;
uint32_t latitude, longitude;
uint8_t groundspeed, trackangle;
char latdir, longdir;
char status;

void setup() 
{ 
  if (powerpin) {
    pinMode(powerpin, OUTPUT);
  }
  pinMode(13, OUTPUT);
  Serial.begin(GPSRATE);
  mySerial.begin(GPSRATE);
   
  // prints title with ending line break 
  Serial.println("GPS parser"); 
 
   digitalWrite(powerpin, LOW);         // pull low to turn on!
} 
 
 
void loop() 
{ 
  uint32_t tmp;
  
  Serial.print("\n\rread: ");
  readline();
  
  // check if $GPRMC (global positioning fixed data)
  if (strncmp(buffer, "$GPRMC",6) == 0) {
    
    // hhmmss time data
    parseptr = buffer+7;
    tmp = parsedecimal(parseptr); 
    hour = tmp / 10000;
    minute = (tmp / 100) % 100;
    second = tmp % 100;
    
    parseptr = strchr(parseptr, ',') + 1;
    status = parseptr[0];
    parseptr += 2;
    
    // grab latitude & long 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];
    }
    
    //Serial.println(latdir);
    
    // longitude
    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);

    // track angle
    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;
    
    Serial.print("\nTime: ");
    Serial.print(hour, DEC); Serial.print(':');
    Serial.print(minute, DEC); Serial.print(':');
    Serial.println(second, DEC);
    Serial.print("Date: ");
    Serial.print(month, DEC); Serial.print('/');
    Serial.print(date, DEC); Serial.print('/');
    Serial.println(year, DEC);
    
    Serial.print("Lat: "); 
    if (latdir == 'N')
       Serial.print('+');
    else if (latdir == 'S')
       Serial.print('-');

    Serial.print(latitude/1000000, DEC); Serial.print('\°', BYTE); 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("Long: ");
    if (longdir == 'E')
       Serial.print('+');
    else if (longdir == 'W')
       Serial.print('-');
    Serial.print(longitude/1000000, DEC); Serial.print('\°', BYTE); 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.println(buffer);
}

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;
}

void readline(void) {
  char c;
  
  buffidx = 0; // start at begninning
  while (1) {
      c=mySerial.read();
      if (c == -1)
        continue;
      Serial.print(c);
      if (c == '\n')
        continue;
      if ((buffidx == BUFFSIZ-1) || (c == '\r')) {
        buffer[buffidx] = 0;
        return;
      }
      buffer[buffidx++]= c;
  }
}


This next example uses TinyGPS and only acquires data if you have a valid fix:
#include <NewSoftSerial.h>
#include <TinyGPS.h>

/* This sample code demonstrates the normal use of a TinyGPS object.
   It requires the use of NewSoftSerial, and assumes that you have a
   4800-baud serial GPS device hooked up on pins 2(rx) and 3(tx).
*/

TinyGPS gps;
NewSoftSerial nss(2, 3);
#define powerpin 4

void gpsdump(TinyGPS &gps);
bool feedgps();
void printFloat(double f, int digits = 2);

void setup()
{
  if (powerpin) {
    pinMode(powerpin, OUTPUT);
  }
  Serial.begin(115200);
  nss.begin(4800);
  
  Serial.print("Testing TinyGPS library v. "); Serial.println(TinyGPS::library_version());
  Serial.println("by Mikal Hart");
  Serial.println();
  Serial.print("Sizeof(gpsobject) = "); Serial.println(sizeof(TinyGPS));
  Serial.println();
}

void loop()
{
  bool newdata = false;
  unsigned long start = millis();

  // Every 5 seconds we print an update
  while (millis() - start < 5000)
  {
    if (feedgps())
      newdata = true;
  }
  
  if (newdata)
  {
    Serial.println("Acquired Data");
    Serial.println("-------------");
    gpsdump(gps);
    Serial.println("-------------");
    Serial.println();
  }
}

void printFloat(double number, int digits)
{
  // Handle negative numbers
  if (number < 0.0)
  {
     Serial.print('-');
     number = -number;
  }

  // Round correctly so that print(1.999, 2) prints as "2.00"
  double rounding = 0.5;
  for (uint8_t i=0; i<digits; ++i)
    rounding /= 10.0;
  
  number += rounding;

  // Extract the integer part of the number and print it
  unsigned long int_part = (unsigned long)number;
  double remainder = number - (double)int_part;
  Serial.print(int_part);

  // Print the decimal point, but only if there are digits beyond
  if (digits > 0)
    Serial.print("."); 

  // Extract digits from the remainder one at a time
  while (digits-- > 0)
  {
    remainder *= 10.0;
    int toPrint = int(remainder);
    Serial.print(toPrint);
    remainder -= toPrint; 
  } 
}

void gpsdump(TinyGPS &gps)
{
  long lat, lon;
  float flat, flon;
  unsigned long age, date, time, chars;
  int year;
  byte month, day, hour, minute, second, hundredths;
  unsigned short sentences, failed;

  gps.get_position(&lat, &lon, &age);
  Serial.print("Lat/Long(10^-5 deg): "); Serial.print(lat); Serial.print(", "); Serial.print(lon); 
  Serial.print(" Fix age: "); Serial.print(age); Serial.println("ms.");
  
  feedgps(); // If we don't feed the gps during this long routine, we may drop characters and get checksum errors

  gps.f_get_position(&flat, &flon, &age);
  Serial.print("Lat/Long(float): "); printFloat(flat, 5); Serial.print(", "); printFloat(flon, 5);
  Serial.print(" Fix age: "); Serial.print(age); Serial.println("ms.");

  feedgps();

  gps.get_datetime(&date, &time, &age);
  Serial.print("Date(ddmmyy): "); Serial.print(date); Serial.print(" Time(hhmmsscc): "); Serial.print(time);
  Serial.print(" Fix age: "); Serial.print(age); Serial.println("ms.");

  feedgps();

  gps.crack_datetime(&year, &month, &day, &hour, &minute, &second, &hundredths, &age);
  Serial.print("Date: "); Serial.print(static_cast<int>(month)); Serial.print("/"); Serial.print(static_cast<int>(day)); Serial.print("/"); Serial.print(year);
  Serial.print("  Time: "); Serial.print(static_cast<int>(hour)); Serial.print(":"); Serial.print(static_cast<int>(minute)); Serial.print(":"); Serial.print(static_cast<int>(second)); Serial.print("."); Serial.print(static_cast<int>(hundredths));
  Serial.print("  Fix age: ");  Serial.print(age); Serial.println("ms.");
  
  feedgps();

  Serial.print("Alt(cm): "); Serial.print(gps.altitude()); Serial.print(" Course(10^-2 deg): "); Serial.print(gps.course()); Serial.print(" Speed(10^-2 knots): "); Serial.println(gps.speed());
  Serial.print("Alt(float): "); printFloat(gps.f_altitude()); Serial.print(" Course(float): "); printFloat(gps.f_course()); Serial.println();
  Serial.print("Speed(knots): "); printFloat(gps.f_speed_knots()); Serial.print(" (mph): ");  printFloat(gps.f_speed_mph());
  Serial.print(" (mps): "); printFloat(gps.f_speed_mps()); Serial.print(" (kmph): "); printFloat(gps.f_speed_kmph()); Serial.println();

  feedgps();

  gps.stats(&chars, &sentences, &failed);
  Serial.print("Stats: characters: "); Serial.print(chars); Serial.print(" sentences: "); Serial.print(sentences); Serial.print(" failed checksum: "); Serial.println(failed);
}
  
bool feedgps(){
  while (nss.available()){
    if (gps.encode(nss.read()))
      return true;
  }
  return false;
}


This next sketch just prints out the lat and long in a format google earth and google maps can use:
#include <NewSoftSerial.h>
#include <TinyGPS.h>

/* This sample code demonstrates the normal use of a TinyGPS object.
   It requires the use of NewSoftSerial, and assumes that you have a
   4800-baud serial GPS device hooked up on pins 2(rx) and 3(tx).
*/

TinyGPS gps;
NewSoftSerial nss(2, 3);
#define powerpin 4

void gpsdump(TinyGPS &gps);
bool feedgps();
void printFloat(double f, int digits = 2);

void setup()
{
  if (powerpin) {
    pinMode(powerpin, OUTPUT);
  }
  Serial.begin(115200);
  nss.begin(4800);
  
  Serial.print("Testing TinyGPS library v. "); Serial.println(TinyGPS::library_version());
  Serial.println("by Mikal Hart");
  Serial.println();
  Serial.print("Sizeof(gpsobject) = "); Serial.println(sizeof(TinyGPS));
  Serial.println();
}

void loop()
{
  bool newdata = false;
  unsigned long start = millis();

  // Every 5 seconds we print an update
  while (millis() - start < 5000)
  {
    if (feedgps())
      newdata = true;
  }
  
  if (newdata)
  {
    Serial.println("Acquired Data");
    Serial.println("-------------");
    gpsdump(gps);
    Serial.println("-------------");
    Serial.println();
  }
}

void printFloat(double number, int digits)
{
  // Handle negative numbers
  if (number < 0.0)
  {
     Serial.print('-');
     number = -number;
  }

  // Round correctly so that print(1.999, 2) prints as "2.00"
  double rounding = 0.5;
  for (uint8_t i=0; i<digits; ++i)
    rounding /= 10.0;
  
  number += rounding;

  // Extract the integer part of the number and print it
  unsigned long int_part = (unsigned long)number;
  double remainder = number - (double)int_part;
  Serial.print(int_part);

  // Print the decimal point, but only if there are digits beyond
  if (digits > 0)
    Serial.print("."); 

  // Extract digits from the remainder one at a time
  while (digits-- > 0){
    remainder *= 10.0;
    int toPrint = int(remainder);
    Serial.print(toPrint);
    remainder -= toPrint; 
  } 
}

void gpsdump(TinyGPS &gps)
{
  long lat, lon;
  float flat, flon;

  feedgps(); // If we don't feed the gps during this long routine, we may drop characters and get checksum errors

  gps.f_get_position(&flat, &flon);
  Serial.print("Lat/Long(float): "); 
  printFloat(flat, 5);
  Serial.print(", ");
  printFloat(flon, 5);
   Serial.println(" ");
}
  
bool feedgps()
{
  while (nss.available())
  {
    if (gps.encode(nss.read()))
      return true;
  }
  return false;
}

Here is the same code with static data
#include <TinyGPS.h>
#include <avr/pgmspace.h>


/* This sample code demonstrates the basic use of a TinyGPS object.
   Typically, you would feed it characters from a serial GPS device, but 
   this example uses static strings for simplicity.
*/

prog_char str1[] PROGMEM = "$GPRMC,201547.000,A,3014.5527,N,09749.5808,W,0.24,163.05,040109,,*1A";
prog_char str2[] PROGMEM = "$GPGGA,201548.000,3014.5529,N,09749.5808,W,1,07,1.5,225.6,M,-22.5,M,18.8,0000*78";
prog_char str3[] PROGMEM = "$GPRMC,201548.000,A,3014.5529,N,09749.5808,W,0.17,53.25,040109,,*2B";
prog_char str4[] PROGMEM = "$GPGGA,201549.000,3014.5533,N,09749.5812,W,1,07,1.5,223.5,M,-22.5,M,18.8,0000*7C";
prog_char *teststrs[4] = {str1, str2, str3, str4};

void sendstring(TinyGPS &gps, const PROGMEM char *str);
void gpsdump(TinyGPS &gps);

void setup()
{
  Serial.begin(115200);
  
  Serial.print("Testing TinyGPS library v. "); Serial.println(TinyGPS::library_version());
  Serial.println("by Mikal Hart");
  Serial.println();
  Serial.print("Sizeof(gpsobject) = "); Serial.println(sizeof(TinyGPS));
  Serial.println();

  for (int i=0; i<4; ++i)
  {
    TinyGPS test_gps;
    Serial.print("Test string #"); Serial.println(i+1);
    Serial.println("--------------");
    sendstring(test_gps, teststrs[i]);
    gpsdump(test_gps);
    Serial.println();
  }
}

void loop()
{
}

void printFloat(double number, int digits=5){
  // Handle negative numbers
  if (number < 0.0)
  {
     Serial.print('-');
     number = -number;
  }

  // Round correctly so that print(1.999, 2) prints as "2.00"
  double rounding = 0.5;
  for (uint8_t i=0; i<digits; ++i)
    rounding /= 10.0;
  
  number += rounding;

  // Extract the integer part of the number and print it
  unsigned long int_part = (unsigned long)number;
  double remainder = number - (double)int_part;
  Serial.print(int_part);

  // Print the decimal point, but only if there are digits beyond
  if (digits > 0)
    Serial.print("."); 

  // Extract digits from the remainder one at a time
  while (digits-- > 0)
  {
    remainder *= 10.0;
    int toPrint = int(remainder);
    Serial.print(toPrint);
    remainder -= toPrint; 
  } 
}

void sendstring(TinyGPS &gps, const PROGMEM char *str){
  while (true)
  {
    char c = pgm_read_byte_near(str++);
    if (!c) break;
    Serial.print(c);
    gps.encode(c);
  }
  Serial.println();
  gps.encode('\r');
  gps.encode('\n');
}

void gpsdump(TinyGPS &gps){
 float flat, flon;

   gps.f_get_position(&flat, &flon);
  Serial.print("Lat/Long(float): ");

  printFloat(flat); 
  Serial.print(", ");
  printFloat(flon);
 
}


Here is code to log data to the SD card. TX goes to pin 0, RX to 1, POWER to 2, led1Pin to 4 and led2Pin to 3
#include "AF_SDLog.h"
#include "util.h"
#include <avr/pgmspace.h>
#include <avr/sleep.h>

// Pins used for LED indicators for debugging.
#define led1Pin    4
#define led2Pin    3

// Pin used for GPS power control.
#define powerPin  2

// We want to use the RMC (basic location data) with WAAS augmentation,
// but we don't care about any other information from the GPS.
#define RMC_ON   "$PSRF103,4,0,1,1*21\r\n"  // the command we send to turn RMC on
#define WAAS_ON  "$PSRF151,1*3F\r\n"        // the command we send to turn WAAS on
#define GGA_OFF  "$PSRF103,0,0,0,1*24\r\n"  // the command we send to turn GGA off
#define GSA_OFF  "$PSRF103,2,0,0,1*26\r\n"  // the command we send to turn GSA off
#define GSV_OFF  "$PSRF103,3,0,0,1*27\r\n"  // the command we send to turn GSV off

// Size of buffer for reading GPS data line-by-line.
#define BUFFSIZE  75

// The buffer, a character array.
char buffer[BUFFSIZE];

// Where we are in the buffer (i.e., how many characters have been read so far.
uint8_t bufferidx = 0;

// An object that allows us to write to the SD card.
AF_SDLog card;

// The file that we will create on the SD card.
File f;





// Read a Hex value and return the decimal equivalent.
uint8_t parseHex(char c) {
  if (c < '0') return 0;
  if (c <= '9') return c - '0';
  if (c < 'A') return 0;
  if (c <= 'F') return (c - 'A')+10;
  
  // What if none of the above was true???
}

// Blink out an error code.
void error(uint8_t errno) {
  uint8_t i;
  while(1) {
    for (i=0; i<errno; i++) {
      digitalWrite(led1Pin, HIGH);
      digitalWrite(led2Pin, HIGH);
      delay(100);
      digitalWrite(led1Pin, LOW);
      digitalWrite(led2Pin, LOW);
      delay(100);
    }
    for (; i<10; i++) {
      delay(200);
    }
  }
}

void setupSerialPort() {
  // For efficiency, the serial port is shared between the USB (used for
  // debugging) and the GPS (used for getting location information).
  Serial.begin(4800);
  
  // Print "hello, world!" to the serial port to show that we are alive.
  putstring_nl("GPSlogger");
}

void setupGPS() {
  
  // Turn on the GPS by writing LOW to the powerPin.
  pinMode(powerPin, OUTPUT);
  digitalWrite(powerPin, LOW);
  
  // Turn on WAAS.
  putstring(WAAS_ON);
  
  // Turn on RMC (basic location data).
  putstring(RMC_ON);
  
  // Turn off everything else that might be on by default.
  putstring(GSV_OFF);
  putstring(GSA_OFF);
  putstring(GGA_OFF);
  
}

// This function uses the global variables "buffer[]" and "card".
void setupSDCard() {
  // If any of the following fails, we call "error()" which
  // is an endless loop that indicates the error using LEDs.
  
  // Try to initialize the card.
  if (!card.init_card()) {
    putstring_nl("Card init. failed!");
    error(1);
  }
  
  // Try to open a partition on the card.
  if (!card.open_partition()) {
    putstring_nl("No partition!");
    error(2);
  }
  
  // Try to open a file system on the card.
  if (!card.open_filesys()) {
    putstring_nl("Can't open filesys");
    error(3);
  }
  
  // Try to open the base directory on the card.
  if (!card.open_dir("/")) {
    putstring_nl("Can't open /");
    error(4);
  }
  
  // Try to find a good name for the logging file. Do this by
  // iterating through the numbers in the base name GPSLOG**.TXT.
  // Stop when we find the name of a file that does not yet exist.
  strcpy(buffer, "GPSLOG00.TXT");
  for (buffer[6] = '0'; buffer[6] <= '9'; buffer[6]++) {
    for (buffer[7] = '0'; buffer[7] <= '9'; buffer[7]++) {
      f = card.open_file(buffer);
      if (!f) {
        // We found a good name.
        break;
      }
      card.close_file(f);
    }
    if (!f) {
      // We found a good name. This is obviously a duplicate check.
      // This double-loop could have been implemented in a better way.
      break;
    }
  }
  
  // Try to create a file with the good name that we found.
  if(!card.create_file(buffer)) {
    putstring("couldnt create "); 
    Serial.println(buffer);
    error(5);
  }
  
  // Try to open the file that we just created for writing.
  f = card.open_file(buffer);
  if (!f) {
    putstring("error opening "); 
    Serial.println(buffer);
    card.close_file(f);
    error(6);
  }
  
  // Write a debug message saying that we're ready to write.
  putstring("writing to "); 
  Serial.println(buffer);
  putstring_nl("ready!");
  
}

// Updates the global variables buffer[] and bufferidx.
boolean gpsGetCharacter() {
  char c;
  
  // Check if any data is available to be read over the serial port.
  if (Serial.available()) {
    // Get the first available character.
    c = Serial.read();
    
    // If we aren't already in the middle of receiving a line of GPS data,
    // keep reading characters until we get one that indicates the start of
    // a line of GPS data.
    if (bufferidx == 0) {
      while (c != '$') {
        c = Serial.read();
      }
    }
    
    // Add the first character to the buffer.
    buffer[bufferidx] = c;
    
    // Check if we're at the end of a line of GPS data.
    if (c == '\n') {
      // Add a terminal character symbol to the end of the buffer.
      buffer[bufferidx+1] = 0;
      return true;
    } else {
      // Not at the end of a line yet, so increment bufferidx, which
      // says how many characters are currently in the buffer.
      bufferidx++;
      
      // Check if we are going to overflow the buffer. If so, print debugging
      // information over the serial port and discard the line.
      if (bufferidx == BUFFSIZE-1) {
        Serial.print('!', BYTE);
        bufferidx = 0;
      }
      
      return false;
    }
  } else {
    // No data available.
    return false;
  }
}

// Updates the global variables buffer[] and bufferidx.
boolean gpsChecksumIsOK() {
  uint8_t sum;
  uint8_t i;
  
  // Make sure there is a checksum. If there isn't, set the bufferidx
  // back to zero and return false, effectively ignoring the line of
  // GPS data we just finished reading.
  if (buffer[bufferidx-4] != '*') {
    Serial.print('*', BYTE);
    bufferidx = 0;
    return false;
  }
  
  // Get the checksum.
  sum = parseHex(buffer[bufferidx-3]) * 16;
  sum += parseHex(buffer[bufferidx-2]);
  
  // Verify the checksum. Again, ignore the line and return if it doesn't
  // match the data.
  for (i=1; i < (bufferidx-4); i++) {
    sum ^= buffer[i];
  }
  if (sum == 0) {
    return true;
  } else {
    Serial.print('~', BYTE);
    bufferidx = 0;
    return false;
  }
}

// Updates the global variables buffer[] and bufferidx.
boolean gpsGotRMCData() {
  // Verify that we got RMC (basic location) data, and that the GPS
  // device actually has a good "fix" on its location. If not, ignore
  // the line of data and return.
  if (strstr(buffer, "GPRMC")) {
    // Create a pointer to access a specific part of the buffer.
    char *p = buffer;
    // Skip to the second item (after the first comma).
    p = strchr(p, ',')+1;
    // Skip to the third item (after the second comma).
    p = strchr(p, ',')+1;
    
    // If the third item begins with the character 'V', then we have no fix.
    if (p[0] == 'V') {
      // Indicate we don't have a fix using LED1 and using the serial port.
      digitalWrite(led1Pin, LOW);
      Serial.print('_', BYTE);
      bufferidx = 0;
      return false;
    } else {
      // Indicate over the serial port and with LED1 that we are finally sure
      // that we have collected good data.
      Serial.print('#', BYTE);
      digitalWrite(led1Pin, HIGH);
      return true;
    }
  } else {
    return false;
  }
  
}

// Updates the global variables buffer[] and bufferidx.
// Also uses the global variables "card" and "f".
void sdWriteGPSData() {
  // Indicate with LED2 that we are trying to write.
  digitalWrite(led2Pin, HIGH);
  
  // Try to log the data. In doing this, we check to make sure that
  // we have written the number of characters that we expected to write.
  // If this does not occur, we display an error message over the serial port.
  if(card.write_file(f, (uint8_t *) buffer, bufferidx) != bufferidx) {
    putstring_nl("can't write!");
  }
  
  // Indicate with LED2 that we are done trying to write.
  digitalWrite(led2Pin, LOW);
  
  // Put us back at the beginning of a line.
  bufferidx = 0;
}







void setup()
{
  setupSerialPort();
  
  // Sets two digital pins as output; these are connected to LEDs
  // that we will use to show status. For example, these LEDs are
  // used by the "error()" function to indicate that something has
  // gone very wrong.
  pinMode(led1Pin, OUTPUT);
  pinMode(led2Pin, OUTPUT);
  
  setupGPS();
  setupSDCard();
  
  // Pause for one second.
  delay(1000);
  
}

void loop()
{
  // Read a character from the GPS device over the serial port.
  if (gpsGetCharacter()) {
    // Verify the checksum.
    if (gpsChecksumIsOK()) {
      // Verify that we got a fix and that we got RMC (basic location) data.
      if (gpsGotRMCData()) {
        // Try to write the basic location data to the SD card.
        sdWriteGPSData();
      }
    }
  }
}

/ / Geo-caching



The Traveling Geocache