Search This Blog

Wednesday, September 22, 2010

Arduino GPS Tracker completed

Wrapped up the Arduino based GPS tracker project!

This project used a SIRF GPS module I bought off Ebay, integrated with a data logging shield that can write to an SD card.  The Arduino microcontroller board makes it all work together.  The code supports and LCD readout of the GPS coordinates, heading, speed, etc.   In the photos below the LCD is removed because it didn't fit in the case nicely.  I'm making another version that has a readout when the parts come.

Basically this is a teen tracker.  As long as it is powered, it logs GPS coordinates of the trip every few seconds.  It records speed as well (not perfectly accurate).   Using gpsvisualizer web site, you can pull out the SD card, put it in your computer, point to the file that is saved and draw a map of the trip.  I even tested this in the trunk of the car, and it worked perfectly. 

Here is a photo of the completed unit, in it's box.   Also shown is the cell phone USB battery that I got at Target, foam taped to the side of the unit.  This might seem odd, but the unit would normally draw power from a USB adapter from a car, not from this battery.



Here is a shot with the case open, showing the adafruit data logger shield with the GPS module hanging off to the side, installed in the prototype area of the shield.  The Arduino itself is beneath the shield that you can see.
It all snaps inside this project box from Sparkfun.



I polished up the code so the waypoints are taken more often, unless the speed is low in which case some delays are added to keep the file down.  I added some LCD display improvements including displaying the compass direction next to the heading (i.e. N, E, S, W, NE, etc).  Took it bike riding at it worked perfectly too.

Earlier posts have links to the build, but a couple notes on the circuit, since i havent drawn it.

The box is $11.95 from Sparkfun http://www.sparkfun.com/commerce/product_info.php?products_id=10088

The adafruit sd logger card is the base prototype board.  I built it up according to it's directions, it is a nice prototype card.  I attached the GPS in the empty prototype area.  $19.50 and worth every penny.
http://www.adafruit.com/index.php?main_page=product_info&cPath=17_21&products_id=243
It supports the SDFat library, which writes to and SD card in a format that is directly readable by your PC in a .csv file.

You can get several LCD modules from Digikey if you want one.  I used this one for $9.25
http://search.digikey.com/scripts/DkSearch/dksus.dll?WT.z_header=search_go&lang=en&site=us&keywords=NHD-0208AZ-FL-YBW-ND&x=21&y=15

The GPS module came for $30 from ebay http://cgi.ebay.com/ws/eBayISAPI.dll?ViewItem&item=250686123796&ssPageName=STRK:MEWAX:IT#ht_500wt_898

The GPS has a two wire serial interface, described in it's documentation and the photo below.  Instead of pins 2,3 I moved the interface to pins 8,9 to avoid conflicts with the LCD and SD card.   Pins 10 and above are used by the SD card interface built into the proto board.   I did not use a serial LCD like in this example, i used a parallel one.   The LCD is hooked up like the Arduino library examples, except the pins are moved to 7,6,5,4,3,2.
(photo borrowed from i182will)

Here is a dump of the code.  Atrocious coding as it is.  I works though!  I am a brute force programmer.




#include <NewSoftSerial.h>
#include <TinyGPS.h>
#include <LiquidCrystal.h>

//Reverse geocoding website
//http://www.gpsvisualizer.com/map_input?form=google

/* 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).
*/

// sdfat data logging stuff
#include <SdFat.h>
#include <SdFatUtil.h>

Sd2Card card;
SdVolume volume;
SdFile root;
SdFile file;

//int greenLED = 1;

// store error strings in flash to save RAM
#define error(s) error_P(PSTR(s))

void error_P(const char* str) {
  PgmPrint("error: ");
  SerialPrintln_P(str);
  if (card.errorCode()) {
    PgmPrint("SD error: ");
    Serial.print(card.errorCode(), HEX);
    Serial.print(',');
    Serial.println(card.errorData(), HEX);
  }
  while(1);
}

TinyGPS gps;
//NewSoftSerial nss(2, 3);
// sdfat protoboard the gps has moved to 8,9
NewSoftSerial nss(8, 9);

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

float LAT, LON, ALT, TIM, DAT, AGE, SPD, DIR;
byte MONTH, DAY, HOUR, MIN, SEC;
long int YEAR;

// initialize the library with the numbers of the interface pins
LiquidCrystal lcd(7, 6 , 5, 4, 3, 2);
int lcd_state = 0;

void setup()
{
  Serial.begin(115200);
  nss.begin(4800);
  lcd.begin(8, 2);
  //pinMode(greenLED, OUTPUT);
  //digitalWrite(greenLED, LOW);

  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("SD init");
    // initialize the SD card at SPI_HALF_SPEED to avoid bus errors with
  // breadboards.  use SPI_FULL_SPEED for better performance.
  if (!card.init(SPI_HALF_SPEED)) error("card.init failed");

  // initialize a FAT volume
  if (!volume.init(&card)) error("volume.init failed");

  // open root directory
  if (!root.openRoot(&volume)) error("openRoot failed");

  // create a new file
  char name[] = "LOGGER00.CSV";
  for (uint8_t i = 0; i < 100; i++) {
    name[6] = i/10 + '0';
    name[7] = i%10 + '0';
    if (file.open(&root, name, O_CREAT | O_EXCL | O_WRITE)) break;
  }
  if (!file.isOpen()) error ("file.create");
  Serial.print("Logging to: ");
  Serial.println(name);

  // write header
  file.writeError = 0;
  file.print("millis,time,speed,dir,altitude,latitude,longitude");
  file.println();

  if (file.writeError || !file.sync()) {
    error("write header failed");
  }

  Serial.print("TTinyGPS library v. "); Serial.println(TinyGPS::library_version());
  Serial.println();
  Serial.print("Sizeof(gpsobject) = "); Serial.println(sizeof(TinyGPS));
  Serial.println();
}

void loop()
{
  bool newdata = false;
  lcd_state = lcd_state + 1;
  if (lcd_state >3) lcd_state = 0;
  unsigned long start = millis();
  // clear print error
  file.writeError = 0;


  // Every 1 seconds we print an update
  while (millis() - start < 1000)
  {
    if (feedgps()) {
      newdata = true;
    }
  }

  if (newdata)
  {
    Serial.println("Acquired Data");
    Serial.println("-------------");
    gpsdump(gps);
    Serial.println("-------------");
    Serial.println();

  if ((AGE>0) & (AGE<2000)) {
      // log time
    uint32_t m = millis();
    file.print(m);  file.print(",");
  
    if (SPD < 5.0) delay(1000);
    if (SPD <10.0) delay(1000);
  
    //digitalWrite(greenLED, HIGH);
    if (lcd_state ==0) {
      lcd.clear();
      lcd.setCursor(0,0);
      lcd.print(HOUR,DEC); lcd.print(":"); lcd.print(MIN,DEC); lcd.print(":"); lcd.print(SEC,DEC);
      lcd.setCursor(0,1);
      lcd.print(MONTH,DEC); lcd.print("/"); lcd.print(DAY,DEC); lcd.print("/"); lcd.print(YEAR-2000,DEC);
    }
    file.print(HOUR,DEC); file.print(":"); file.print(MIN,DEC); file.print(":"); file.print(SEC,DEC); file.print(",");
    feedgps(); // If we don't feed the gps during this long routine, we may drop characters and get checksum errors
  
    if (lcd_state ==1) {
      lcd.clear();
      lcd.setCursor(0,0);
      lcd.print(SPD,1);
      lcd.print(" mph");
      lcd.setCursor(0,1);

      //compass headings
      //note overlap and order
      //this allows headings like NNE and WSW
      //crys out for a case or switch statement, but i brute forced
      if ((DIR>56.25) & (DIR<=123.75))  lcd.print("E");
      if ((DIR>146.25) & (DIR<=213.75)) lcd.print("S");
      if ((DIR>236.25) & (DIR<=303.75)) lcd.print("W");
      if ((DIR>326.25) | (DIR<=33.75))  lcd.print("N");
    
      if ((DIR>11.25) & (DIR<=78.75))   lcd.print("NE");
      if ((DIR>101.25) & (DIR<=168.75)) lcd.print("SE");
      if ((DIR>191.25) & (DIR<=258.75)) lcd.print("SW");
      if ((DIR>281.25) & (DIR<=348.75)) lcd.print("NW");
    
      lcd.print(" ");

      lcd.print(DIR,1);
    
    }
    file.print(SPD); file.print(",");
    file.print(DIR); file.print(",");
    delay(100);
    feedgps(); // If we don't feed the gps during this long routine, we may drop characters and get checksum errors
  
     if (lcd_state ==2) {
        lcd.clear();
        lcd.setCursor(0,0);
        lcd.print("alt");
        lcd.setCursor(0,1);
        lcd.print(ALT,1);
        lcd.print(" ft");
     }
    file.print(ALT); file.print(",");
    feedgps(); // If we don't feed the gps during this long routine, we may drop characters and get checksum errors
  
     if (lcd_state ==3) {
        lcd.setCursor(0,0);
        lcd.print(LAT,7);

        lcd.setCursor(0,1);
        lcd.print(LON,7);

        // wait a bit:
        delay(200);
        lcd.scrollDisplayLeft();
        // wait a bit:
        delay(200);
        lcd.scrollDisplayLeft();
        // wait a bit:
        delay(200);
        lcd.scrollDisplayLeft();
        // wait a bit:
        feedgps(); // If we don't feed the gps during this long routine, we may drop characters and get checksum errors
        //lcd.scrollDisplayRight();
        //lcd.scrollDisplayRight();
        //lcd.scrollDisplayRight();
     }

     file.print(LAT,7); file.print(",");
     file.print(LON,7); file.print(" \n");

  } else {
    lcd.clear();
    lcd.setCursor(0,1);
    lcd.print("No Fix");
  }
  
  }else {
    lcd.clear();
    lcd.setCursor(0,0);
    lcd.print("No Signl");
    lcd.setCursor(0,1);
    lcd.print(AGE,4);
    //digitalWrite(greenLED, LOW);
    }
  
   if (file.writeError) {
       error("write data failed");
       lcd.clear();
       lcd.setCursor(0,0);
       lcd.print("SD fail");
   }

  //don't sync too often - requires 2048 bytes of I/O to SD card
  //if ((millis() - syncTime) <  SYNC_INTERVAL) return;
  //syncTime = millis();
  if (!file.sync()) error("sync failed");
}

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.");
  LAT = flat;
  LON = flon;
  AGE = age;

  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.");
  DAT = date;
  TIM = time;

  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.");
  MONTH = static_cast<int>(month);
  DAY = static_cast<int>(day);
  HOUR = static_cast<int>(hour);
  MIN = static_cast<int>(minute);
  SEC = static_cast<int>(second);
  YEAR = year;

  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();
  ALT = gps.f_altitude();
  SPD = gps.f_speed_mph();
  DIR = gps.f_course();

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

4 comments:

  1. Hello!
    Your sketch really helped for me!
    Thanks a lot!

    ReplyDelete
  2. Presently you have a visual showcase that shows you precisely where you are on the guide. So initially you can tell where you are. http://gpscustomerservice.com/

    ReplyDelete
  3. Data Pullers are widely being used in the form of devices containing a GPS receiver and a cell phone which, when sent a special SMS message reply to the message with their location. Kids GPS Tracker

    ReplyDelete
  4. Of course, you'll be able to speak with someone in the emergency department right away with a GPS system. Starting in 2005, the department could readily contact you again after you've made a call to 911. comment localiser un portable

    ReplyDelete