Go Down

Topic: GPS logger (Read 10804 times) previous topic - next topic

kas

Nov 14, 2013, 04:48 pm Last Edit: Nov 15, 2013, 07:31 am by kas Reason: 1
Yes this is another GPS Logger project
Track is recorded on the SD card as a .CVS file which is then converted as a Google Earth .kml file
Just download and double click on the attached sample file (you should be logged to the forum to see the file)
It will directly open in Google Earth and display the track.



This logger design is focused on low power consumption by combining several technologies that can be reused for other, non related, projects:
A stripped down Arduino (plain AtMega 328P, $2.99 shipped) removes Led's and FTDI overheads. Processor works @ 8Mhz, 3V.
The board is directly powered by a single 3.2V lithium iron phosphate battery; no need for a power hungry DC/DC converter.
Finally, the processor is put asleep below 3V to avoid battery deep discharge.
By combining all those, I was able to improve battery life from a couple of hours to 20+ hours





This project is rather modular and educational, it will let you master several techniques
- Arduino on a board (thanks Super Nick  ;))
- LiFePO4 battery technology
- battery monitoring w/o voltage regulator (not trivial...)
- Arduino sleeping modes (thanks again Nick)
- GPS NMEA 0183 serial protocol
- SD Card SPI communication
- Python scripting
- KML format
- soldering and packaging in a nice waterproof box

Disclaimer: I invented nothing, just putting stuff together  ;)

Should you need additional info's and links on specific points, let me know

el_supremo

#1
Nov 14, 2013, 05:23 pm Last Edit: Nov 14, 2013, 06:09 pm by el_supremo Reason: 1
If you don't actually need the .CSV file itself, you could record the data directly to the SD card as a GPX file which can be read by Google Earth and Garmin software (amongst others). Saves having to translate the file but makes the file somewhat larger - but that's not usually an issue.

I made essentially the same kind of logger using an Adafruit Ultimate GPS, Teensy 3 (Correction: It's a Teensy 2) and the PJRC uSD card/adapter. It is nowhere near as good looking as your installation though :-)

Pete
Don't send me technical questions via Private Message.

kas

Hi Pete,

Quote
If you don't actually need the .CSV file itself, you could record the data directly to the SD card


You are right, no real need for the cvs file
I choose this road for three reasons:

1) Terminate the file with the relevant ending Tags
Code: [Select]

      </coordinates>
    </LineString>
  </Placemark>
</Document>
</kml>


Could be done on the fly:
- open SD card file
- rewind 56 Bytes (erase previous ending Tags)
- Write Long + Lat
- write ending Tags
- close file

2) compute average speed, cumulative uphill and downhill...

3) Get a final file with date stamp, which is not possible within Arduino ecosystem

Thanks for your interest

Peter_I

#3
Nov 14, 2013, 07:23 pm Last Edit: Nov 14, 2013, 08:27 pm by Peter_I Reason: 1
Nice!


And I like the box.
Is it a waterproof cigarette case?





And a pleasure to see an exhibition post with some good pictures and a text , not just an external "generate some traffic on my blog or youtube"-link.
"Nothing is foolproof to a sufficiently talented fool"

el_supremo

I write a record every 5 seconds so there's plenty of time to open the file, seek to near the end, write the new stuff and close it again. Like this:
Code: [Select]

  SD_open_file();
  unsigned long filesize = myFile.fileSize();
  // back up the file pointer to just before the closing tags
  filesize -= 27;
  //Serial.println(filesize);
  if(!myFile.seekSet(filesize)) {
#ifndef BATTERY
    // Can't win 'em all. Print error message, but can't do anything
    // other than keep going and hope for the best!
    Serial.println("Seek failed");
#endif
  }
  // print the GPS data to the file
  myFile.print(trk_buf);
  // This will be overwritten by the next write to the file, if there is one,
  // and if there isn't, the file is complete and will read as a valid GPX
  myFile.print(F("</trkseg>\r\n</trk>\r\n</gpx>\r\n"));
  myFile.close();


Pete
Don't send me technical questions via Private Message.

kas

#5
Nov 14, 2013, 09:50 pm Last Edit: Nov 15, 2013, 07:57 am by kas Reason: 1
@Peter_I

Quote
And I like the box.
Is it a waterproof cigarette case?

Just a general purpose box
Search for "waterproof" "box" "plastic" on eBay
example: ($2.85 shipped)
http://www.ebay.com/itm/Outdoor-Waterproof-Plastic-Container-Key-Money-Storage-Box-Case-Holder-/180917961914?pt=LH_DefaultDomain_0&hash=item2a1f8d04ba



Thanks for the comments

kas

@el_supremo

Yeah, that the way to go
Closing Tags should always be added after each Long/Lat recording, as the logger may be switch off at any time.
Out of curiosity, what's about ???
Code: [Select]
#ifndef BATTERY
.....

el_supremo

If it is running on a battery, which it does when I'm driving and it's recording the track, there's no point trying to talk to the Serial port. I suppose it doesn't really matter - the bits won't go anywhere :-).
If I'm testing it at home, then I do want to see the failure message on the Serial monitor. Basically, it's a bit of old debugging.

Pete
Don't send me technical questions via Private Message.

Peter_I


@Peter_I

Quote
And I like the box.
Is it a waterproof cigarette case?

Just a general purpose box, $2.85 shipped
http://www.ebay.com/itm/Outdoor-Waterproof-Plastic-Container-Key-Money-Storage-Box-Case-Holder-/180917961914?pt=LH_DefaultDomain_0&hash=item2a1f8d04ba

Thanks for the comments


Thanks.
I can see quite a few uses for those!
"Nothing is foolproof to a sufficiently talented fool"

rbright

Nice logger.... kas are you going to release your code and stripboard layout?

thanks

kas

#10
Nov 15, 2013, 04:07 pm Last Edit: Nov 15, 2013, 04:18 pm by kas Reason: 1
Quote
If it is running on a battery, which it does when I'm driving and it's recording the track, there's no point trying to talk to the Serial port. I suppose it doesn't really matter - the bits won't go anywhere :-).
If I'm testing it at home, then I do want to see the failure message on the Serial monitor. Basically, it's a bit of old debugging


In this project, error Messages are "Bip coded" and transmitted both through the built in buzzer
and the status LED



If everything is OK, I expect 2 short Bips at startup
If I forget to insert the SD card, I am warned with 4 long Bips  :smiley-red:

Code: [Select]

void setup()    {
....
 if (!sd.begin(CS) || !getConfig())  {                    // Initialize the SD and get configuration
   bip(bipPin, 250, 3);                                   // error
   blinkLED(logLED, 10, 3);
   while(1);
 }
 if (!file.open("LOG.CSV", O_CREAT | O_WRITE | O_APPEND))  {// create or open the data file for append.
   bip(bipPin, 250, 4);                                     // error
   blinkLED(logLED, 10, 4);
   while(1);
 }
....
 blinkLED(logLED, 10, 2);
 bip(bipPin, 10, 2);                                       // ready
}


void bip(int pin, int duration, int n)    {                 // ms and number of bips
 for(int i=0; i<n; i++)  {  
    digitalWrite(bipPin, HIGH);        
    delay(duration);
    digitalWrite(bipPin, LOW);        
    delay(75);
 }
}

void blinkLED(int pin, int duration, int n)    {             // ms and number of blinks
 for(int i=0; i<n; i++)  {  
  digitalWrite(pin, HIGH);        
  delay(duration);
  digitalWrite(pin, LOW);
  if(n > 1)    delay(200);  // ex500
 }


kas

Quote
Nice logger.... kas are you going to release your code and stripboard layout?


Thanks rbright

I will clean my code and post it within a few days

For the layout ...  :smiley-red: :smiley-red:
would the breadboard prototype photo help ??



rbright

kas the protoboard image is really helpful, so would be a schematic & eventually the code.
What you have produced is an excellent example of a minimum component data logger which by its nature has a low current drain especially with what appears to be no UART implimented. People could remove the GPS module and add a RTC (to provide accurate time/date) and then have a logger suitable for say temperatures, or that what I'd expect from what I've seen to date.

Regards

kas

#13
Nov 19, 2013, 02:09 pm Last Edit: Nov 19, 2013, 02:16 pm by kas Reason: 1
Here is the kasGPS V3.0 source code:

Code: [Select]
// GPS Logger kasGPS V3.0        Kas 2013

// SD wiring:
// MOSI: D11, MISO: D12, CLK: D13, CS: D10 (D53 for Mega)

// V30: Use sdfat (http://arduino.cc/forum/index.php/topic,149504.0.html)
// .....
// V1.0 Initial release

#include <SdFat.h>
#include <avr/sleep.h>
#include <TinyGPS.h>

#define PMTK_SET_NMEA_OUTPUT_RMCONLY "$PMTK314,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*29"     // only RMC data

TinyGPS gps;                              // TinyGPS object
SdFat sd;                                 // SD file system.
SdFile file;                              // Log file.
int CS = 10;                              // SPI chip select
int logLED = 8;                
int bipPin = 9;  
int minDist = 30;                         // Minimum distance in meters between logs
long logRate = 3000;                      // Minimun time in ms between logs
int voltMin = 300;                        // Minimum battery voltage
static char dtostrfbuffer[20];
float flat, flon;
float flat_ant=0, flon_ant=0;
unsigned long age;
long distance;
long startLog = 0;
String SD_lat = "invalid";
String SD_lon = "invalid";
String dataString ="";
int maxSkip = 5;                          // do not log initial inaccurate values
int skip = 1;

static bool feedgps();

void setup()    {
 pinMode(CS, OUTPUT);                                     // Chip Select Pin for SD Card
 pinMode(bipPin, OUTPUT);                                 // Buzzer
 pinMode(logLED, OUTPUT);                                 // LED Indicator
 Serial.begin(9600);
 Serial.println(PMTK_SET_NMEA_OUTPUT_RMCONLY);            // turn on only RMC
 if (!sd.begin(CS) || !getConfig())  {                    // Initialize SD card and get configuration
   bip(bipPin, 250, 3);                                   // error
   blinkLED(logLED, 10, 3);
   while(1);
 }
 if (!file.open("LOG.CSV", O_CREAT | O_WRITE | O_APPEND)) {// Initialize the SD and create or open the CVS data file for append.
   bip(bipPin, 250, 4);                                    // error
   blinkLED(logLED, 10, 4);
   while(1);
 }
 blinkLED(logLED, 10, 2);
 bip(bipPin, 10, 2);                                      // ready
}

void loop()    {
 bool newdata = false;

 if(getBatVolt() < voltMin)  {                           // battery monitoring
   bip(bipPin, 200, 5);
   GoToSleep();                                          // ATMega low hibernation mode
 }

 unsigned long start = millis();
 while (millis() - start < 1500)   {                    // data acquisition from GPS module
   if (feedgps())  newdata = true;
 }
 if(newdata)  {  
   gps.f_get_position(&flat, &flon, &age);
   if(flat == TinyGPS::GPS_INVALID_F_ANGLE)    {
     SD_lat = "***";
     SD_lon = "***";
   }  else  {
     SD_lat = dtostrf(flat,8,5,dtostrfbuffer);    
     SD_lon = dtostrf(flon,8,5,dtostrfbuffer);
   }
   dataString = SD_lat + "," + SD_lon;  
   feedgps();
   
   if(dataString != "***,***")  {  
     if(skip > maxSkip)  {                                  // discard first values
       if((millis() - startLog > logRate))  {               // time log conditions
         startLog = millis();  
         distance = TinyGPS::distance_between(flat, flon, flat_ant, flon_ant);
         if(distance > minDist)      {                      // distance log conditions
           if(abs(distance > 1000))    distance = 0;        // max speed 1200Km/h
           file.println(dataString);                        // write to file
           file.sync();                                     // Use sync instead of close.
           flat_ant = flat;    
           flon_ant = flon;
           bip(bipPin, 3, 1);                              
           blinkLED(logLED, 10, 1);
        }  
       }  
     }  else {
       skip++;
       blinkLED(logLED, 10, 1);
     }
   }  else    { blinkLED(logLED, 200, 1);  blinkLED(logLED, 10, 1); }
 }  else   digitalWrite(logLED, HIGH);
}

static bool feedgps()  {
 while(Serial.available())  {
   if(gps.encode(Serial.read()))    return true;
 }
 return false;
}

void bip(int pin, int duration, int n)    {                 // Bip piezo: duree en ms et repetition
 for(int i=0; i<n; i++)  {  
    digitalWrite(bipPin, HIGH);        
    delay(duration);
    digitalWrite(bipPin, LOW);        
    delay(75);
 }
}

void blinkLED(int pin, int duration, int n)    {            
 for(int i=0; i<n; i++)  {  
  digitalWrite(pin, HIGH);        
  delay(duration);
  digitalWrite(pin, LOW);
  if(n > 1)    delay(200);  // ex500
 }
}  

int getBatVolt()      {
int results;
const long InternalReferenceVoltage = 1098L; //1050L;  // Adjust value to your boards specific internal BG voltage x1000
  // REFS1 REFS0    --> 0 1, AVcc internal ref.    // MUX3 MUX2 MUX1 MUX0  --> 1110 1.1V (VBG)
 for (int i=0; i <= 3; i++)     {  //4 readings required for best stable value?
   ADMUX = (0<<REFS1) | (1<<REFS0) | (0<<ADLAR) | (1<<MUX3) | (1<<MUX2) | (1<<MUX1) | (0<<MUX0);
   ADCSRA |= _BV( ADSC );                                                    // Start a conversion
   while( ( (ADCSRA & (1<<ADSC)) != 0 ) );                                   // Wait for it to complete
   results = (((InternalReferenceVoltage * 1023L) / ADC) + 5L) / 10L;        // Scale the value
}
return results;
}

void GoToSleep()    {
 Serial.end();
 ADCSRA = 0;                                        // disable ADC
 set_sleep_mode (SLEEP_MODE_PWR_DOWN);  
 sleep_enable();
 MCUCR = _BV (BODS) | _BV (BODSE);                  // turn on brown-out enable select
 MCUCR = _BV (BODS);                                // this must be done within 4 clock cycles of above
 sleep_cpu ();                                      // sleep within 3 clock cycles of above
}  

boolean getConfig()  {                              // Read the Configuration information (Config.txt)
boolean result = true;                              // file content: X;Y
 if (file.open("Config.txt", O_READ) )    {        // X=seconds Y=metersX10 (between 2 records)
   if(file.available() >= 3)  {    
     logRate = (file.read() - '0') * 1000;
     file.read();                                  // drops ";"
     minDist = (file.read() - '0') * 10;
   }    else  result = false;
   file.close();
 }    else    result = false;
 return result;
}


I use two nice libraries for this project
- SdFat from William Greiman
- TinyGPS from Mikal Hart

Both libraries come with sample code I happily reused
Kudos to these two gentlemen for their hard works

gabi8es

Hi Kas

  First of all, congratulations for your project. I have liked it a lot, specifically the consumption matter, it's very interesting to have 20 hours of atonomy, I can track my hikings with my phone, but 20 hours of atonomy is a complete weekend log !!!

  I was thinking in something similar and your experiences are very helpful to me.

  I have some questions:

       - Have you thought about include another battery and measure the impact in the autonomy ? It must duplicate it, doesn't it? I'm thinking too in a temperature logger. I guess a temperature sensor will consume less power than a gps so the autonomy with two of those batteries could increase to a few days.

       - What's the point of the CR1220 battery? are there a realtime clock? or it's used for another reason?

      - Which GPS module has been used for your project?

Thanks in advance and congratulations one more time

Gabi

Go Up