GPS Logging Shield with Extra Serial Sensor

For other supported Arduino products from Adafruit: Shields, accessories, etc.

Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.
Locked
User avatar
jleeman
 
Posts: 18
Joined: Sat Feb 07, 2015 5:29 pm

GPS Logging Shield with Extra Serial Sensor

Post by jleeman »

Hello all,

I've got the Ultimate GPS Logging Shield on an Uno. I also have an instrument that sends back 9600 baud serial data at 1 Hz. The output of the serial instrument looks like:

Code: Select all

$YXXDR,A,-9162.825,M,N,A,-4062.101,D,E,C,13.73,C,T-N1052*57
and is terminated with a <CR><LF>. I thought combining a simple reader/logger that works for the serial device and the GPS logger would be simple enough, but I seem to get unreliable logging operation.

The Hardware Setup
- The GPS shield is installed as instructed and the GPS serial switch is set to soft serial (pins 8,7)
- The extra instrument is connected to the hardware serial (pins 0,1) with an RS232 level shifter. I have a switch that allows me to connect it to pins 5,6 for soft serial troubleshooting.

Tests
- I can successfully log just tilt meter data
- I can successfully run and log GPS data with the example code from Adafruit

Problems
- If I run my sketch while using interrupt mode, I get very sporadic output. Sometimes good, but most of the time it looks like:

Code: Select all

$GPRMC,234658.000,A,4046.3361,N,07751.4725,W,0.02,38.06,020415,,,D*4B
$YR,AA,1771
$GPRMC,234700.000,A,4046.3360,N,07751.4725,W,0.05,130.67,020415,,,D*7F
- If I run with manual polling (as is shown exactly below), I get generally reliable logging, but sometimes long periods of glitchy data, dropouts, or bad GPS reads.

What is the best way for me to structure the code to read these two serial devices (GPS and external instrument)? Since they are both 1Hz, but not locked together in any way, they could start at practically any phase relation on power up. The external instrument also has a slightly drifty clock, so the phase relation will indeed change. In the end I just want a text file of the RMC, GGA, and sensor strings. You can see that I'm writing to a new log file every day. I have some basic unit tests for that bit https://gist.github.com/jrleeman/3b7c10712112e49d8607.

Thank you for any tips!

Code: Select all

#include <SPI.h>
#include <Adafruit_GPS.h>
#include <SoftwareSerial.h>
#include <SD.h>
#include <avr/sleep.h>

// Make software serial port for GPS and GPS instance
SoftwareSerial gpsSerial(8, 7);
Adafruit_GPS GPS(&gpsSerial);

// Variables and defines
#define REDLED 3
#define GRNLED 4
#define LOGGERNUMBER 1

int current_day = 0;

boolean usingInterrupt = false;
void useInterrupt(boolean); // Func prototype keeps Arduino 0023 happy

File logfile;

void error(uint8_t errno) {
  /*
  Blink out error codes based on an integer input.
  */
  digitalWrite(REDLED, LOW);
  digitalWrite(GRNLED, LOW);

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

void serialFlush(){
  /*
  Since Arduino 1.0 has not cleared the input buffer.
  This function clears the buffers of both the real and
  virtual serial ports.
  */
  while(Serial.available() > 0) {
    char t = Serial.read();
  }

  while(gpsSerial.available() > 0) {
    char t = gpsSerial.read();
  }
}

void setup() {
  /*
  Do the setup of the GPS, tilt meter, output pins, SD card, etc.
  This is where lots could go wrong if things are not hooked up correctly.
  */

  // Setup the LED pins and the CS pin for the SD card as output
  pinMode(REDLED, OUTPUT);
  pinMode(GRNLED, OUTPUT);
  pinMode(10, OUTPUT);

  // Turn on the LEDs to show that we are in setup
  digitalWrite(REDLED, HIGH);
  digitalWrite(GRNLED, HIGH);

  // Start the SD Card
  if (!SD.begin(10, 11, 12, 13)) {
    error(2);
   }


  // Start and setup the GPS
  GPS.begin(9600);
  GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA); // Turn on RMC (recommended minimum) and GGA (fix data) including altitude
  GPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ); // 1Hz Update
  GPS.sendCommand(PGCMD_NOANTENNA); // No antenna status updates
  useInterrupt(false); // Have an interrupt go off to deal with GPS parsing

  // Start and setup the tilt meter
  Serial.begin(9600);
  delay(1000);
  int bytesSent = Serial.write("*9900XY-SET-BAUDRATE,9600\n"); // Set baudrate on tilt meter
  delay(1000);
  bytesSent += Serial.write("*9900SO-XDR\n"); // Set to NMEA XDR format
  delay(1000);
  bytesSent += Serial.write("*9900XYC2\n"); // Set to 1 Hz output
  delay(2000);
  // DO ERROR CHECKING

  // Turn off the LEDs to show that we are done, blink them twice
  digitalWrite(REDLED, LOW);
  digitalWrite(GRNLED, LOW);

  for (int i=0; i < 2; i++) {
    delay(1000);
    digitalWrite(REDLED, HIGH);
    digitalWrite(GRNLED, HIGH);
    delay(1000);
    digitalWrite(REDLED, LOW);
    digitalWrite(GRNLED, LOW);
  }

  // Dump the serial buffers
  // I'm not sure if this really helps eliminate junk at the top of the
  // logfile or not. I'm open to thoughts/tests.
  //serialFlush();
}

SIGNAL(TIMER0_COMPA_vect) {
  /*
  This fires on the interrupt to grab the latest GPS string
  */
  char c = GPS.read();
}

void useInterrupt(boolean v) {
  if (v) {
    // Timer0 is already used for millis() - we'll just interrupt somewhere
    // in the middle and call the "Compare A" function above
    OCR0A = 0xAF;
    TIMSK0 |= _BV(OCIE0A);
    usingInterrupt = true;
  }
  else {
    // do not call the interrupt function COMPA anymore
    TIMSK0 &= ~_BV(OCIE0A);
    usingInterrupt = false;
  }
}

void loop() {
  char c = GPS.read(); 
  // Check if we have new GPS data, if so, deal with it
  if (GPS.newNMEAreceived()) {
    char *stringptr = GPS.lastNMEA();

    // If the sentence doesn't parse correctly, we trash it
    if (!GPS.parse(stringptr)) {
      return; // IS THIS REALLY WHAT I WANT?
    }


    // Check if we need to start a new log file
    if (GPS.day != current_day){
      // Close the old logfile
      logfile.close();

      // Make new file name
      char name[12];
      getLogFileName(name,GPS.day,GPS.month,GPS.year,LOGGERNUMBER);

      // Prevent over-writing by adding to the sub-sequence set
      for (uint8_t i = 0; i < 100; i++) {
        name[6] = '0' + i/10;
        name[7] = '0' + i%10;
        // create if does not exist, do not open existing, write, sync after write
        if (! SD.exists(name)) {
          break;
        }
       }

      // Open the new file and set the current day to the new day
      logfile = SD.open(name, FILE_WRITE);
      if( ! logfile ) {
        error(1);
      }

       current_day = GPS.day;
    }


    if(logfile){
      // Log the GPS data if we have a valid logfile object
      uint8_t stringsize = strlen(stringptr);

      if (stringsize > 0)
        digitalWrite(REDLED, HIGH);

      //if (stringsize != logfile.write((uint8_t *)stringptr, stringsize))    //write the string to the SD file
      //    error(4);
      //if (strstr(stringptr, "RMC") || strstr(stringptr, "GGA"))   logfile.flush();
      logfile.print(stringptr);
      logfile.flush();
      digitalWrite(REDLED, LOW);
      
    // Now check out the tilt meter serial buffer
    // Grab it, flash LED, write it
    String datasensor = Serial.readStringUntil('\n'); // Changing this to \r causes odd behavior 
    digitalWrite(GRNLED, HIGH);
    //logfile.write( ( uint8_t* ) &datasensor[0], datasensor.length() );
    //datasensor.remove(0,1);
    logfile.print(datasensor);
    logfile.flush();
    digitalWrite(GRNLED, LOW);

    }
  }



 

}


char getLogFileName(char *filename, int day, int month, int year, int unitNumber) {
 /*
 Format is DDDYYNNA.DAT
 DDD - Day of year
 YY  - Year
 NN  - Logger unit number
 A - Sub-sequence name, used if more than one file/day
 */

 strcpy(filename, "00000000.DAT");

 // Get day of year and set it
 int doy = calculateDayOfYear(day, month, year);
 filename[0] = '0' + (doy/100)%100;
 filename[1] = '0' + (doy/10)%10;
 filename[2] = '0' + doy%10;

 // Set the year
 filename[3] = '0' + (year/10)%10;
 filename[4] = '0' + year%10;

 // Set the logging unit number
 filename[5] = '0' + unitNumber%10;
}

int calculateDayOfYear(int day, int month, int year) {
  /*
  Given a day, month, and year (2 or 4 digit), returns
  the day of year. Errors return 999.
  */
  // List of days in a month for a normal year
  int daysInMonth[] = {31,28,31,30,31,30,31,31,30,31,30,31};

  // Check if it is a leap year
  if (year%4  == 0)
  {
    daysInMonth[1] = 29;
  }

  // Make sure we are on a valid day of the month
  if (day < 1)
  {
    return 999;
  } else if (day > daysInMonth[month-1]) {
    return 999;
  }
  
  int doy = 0;
  for (int i = 0; i < month - 1; i++) {
    doy += daysInMonth[i];
  }
  
  doy += day;
  return doy;
}
Photo of the setup (power and other sensor come in though Amphenol connectors, GPS antenna out to the left).
Photo of the setup (power and other sensor come in though Amphenol connectors, GPS antenna out to the left).
Logger_Setup.jpg (102.38 KiB) Viewed 652 times

User avatar
adafruit_support_bill
 
Posts: 88086
Joined: Sat Feb 07, 2009 10:11 am

Re: GPS Logging Shield with Extra Serial Sensor

Post by adafruit_support_bill »

Code: Select all

        String datasensor = Serial.readStringUntil('\n'); // Changing this to \r causes odd behavior
This is a blocking call: http://arduino.cc/en/Reference/StreamReadStringUntil
While waiting for the newline, you may be overflowing the GPS buffer. You probably need to read this character by character as you do with the GPS strings.

User avatar
jleeman
 
Posts: 18
Joined: Sat Feb 07, 2015 5:29 pm

Re: GPS Logging Shield with Extra Serial Sensor

Post by jleeman »

I'll give that a shot! I'm guessing something like this should do the business?

Code: Select all

int readline(int port, char *buffer, int len){
  // Reads a line from the tilt meter
  // Based on example from http://hacking.majenko.co.uk/reading-serial-on-the-arduino
  static int pos = 0;
  int rpos;
  
  if (port > 0){
    switch (port) {
      case '\n': // Ignore new lines
        break;
      case '\r': // CR is our return character
        rpos = pos;
        pos = 0; // Reset position index
        return rpos;
      default:
        if (pos < len-1) {
          buffer[pos++] = port;
          buffer[pos] = 0;
        }
    }
  }
  // No end-line so send back -1
  return -1;
}
I should be able to do this outside of the if new NMEA received condition correct? That way if the GPS doesn't have anything, it just reads the instrument and goes back around.

Thanks!

User avatar
adafruit_support_bill
 
Posts: 88086
Joined: Sat Feb 07, 2009 10:11 am

Re: GPS Logging Shield with Extra Serial Sensor

Post by adafruit_support_bill »

That's the right approach. On each pass of the loop, read what is available on the port. When you get the complete line, process it.

User avatar
jleeman
 
Posts: 18
Joined: Sat Feb 07, 2015 5:29 pm

Re: GPS Logging Shield with Extra Serial Sensor

Post by jleeman »

Ok. I can log data, but eventually some funny business creeps in. There appears to be some kind of effect due to the phasing of the GPS and instrument data. If you happen to power on the logger when they are in a nicely alternating sequence (the instrument happens to be sending data in between seconds) then things are fine. If you power it up when they are very close together or let it run for a long time and they drift together, then things get funky. There will be periods where the instrument data is totally missed, the log just showing GPS. The other things I've noticed is that occasionally the GGA gps sentence will drop out, then come back, but RMC seems to always be there. Below is a version of the logger (this changes file names every hour, but the problem occurs even without this). Is this a buffer too small issue? It seems that it shouldn't be missing anything since the loop should indeed run very fast.

I can put the sensor in a call/response mode of sending a command the getting back the reading, but doing that doesn't ever log anything.

Code: Select all

#include <SPI.h>
#include <Adafruit_GPS.h>
#include <SoftwareSerial.h>
#include <SD.h>
#include <avr/sleep.h>

// Make software serial port for GPS and GPS instance
SoftwareSerial mySerial(8, 7);
Adafruit_GPS GPS(&mySerial);

// Variables and defines
#define chipSelect 10
#define REDLED 3
#define GRNLED 4

int current_hour = 99;
boolean usingInterrupt = false;
void useInterrupt(boolean); // Func prototype keeps Arduino 0023 happy

File logfile;

void error(uint8_t errno) {
 /*
 Blink out error codes based on an integer input.
 First making sure that all status LEDS are off.
 */

 digitalWrite(REDLED, LOW);
 digitalWrite(GRNLED, LOW);

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

void setup() {
 /*
 Do the setup of the GPS, tilt meter, output pins, SD card, etc.
 This is where lots could go wrong if things are not hooked up correctly.
 */

 // Setup the LED pins and the CS pin for the SD card as output
 pinMode(REDLED, OUTPUT);
 pinMode(GRNLED, OUTPUT);
 pinMode(10, OUTPUT);

 // Turn on the LEDs to show that we are in setup
 digitalWrite(REDLED, HIGH);
 digitalWrite(GRNLED, HIGH);

 // see if the card is present and can be initialized:
 if (!SD.begin(chipSelect, 11, 12, 13)) {
   error(2);
 }

 /*
 logfile = SD.open(filename, FILE_WRITE);
 if( ! logfile ) {
   error(3);
 }
 */

 // connect to the GPS at the desired rate
 GPS.begin(9600);
 GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA);
 GPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ);  
 GPS.sendCommand(PGCMD_NOANTENNA);
 useInterrupt(true);

 // Start and setup the tilt meter
 Serial.begin(9600);
 delay(1000);
 int bytesSent = Serial.write("*9900XY-SET-BAUDRATE,9600\n"); // Set baudrate on tilt meter
 delay(1000);
 bytesSent += Serial.write("*9900SO-XDR\n"); // Set to NMEA XDR format
 delay(1000);
 bytesSent += Serial.write("*9900XYC2\n"); // Set to 1 Hz output
 delay(2000);
 // DO ERROR CHECKING

 // Turn off the LEDs to show that we are done, blink them twice
 digitalWrite(REDLED, LOW);
 digitalWrite(GRNLED, LOW);

 for (int i=0; i < 2; i++) {
   delay(1000);
   digitalWrite(REDLED, HIGH);
   digitalWrite(GRNLED, HIGH);
   delay(1000);
   digitalWrite(REDLED, LOW);
   digitalWrite(GRNLED, LOW);
 }
}


// Interrupt is called once a millisecond, looks for any new GPS data, and stores it
SIGNAL(TIMER0_COMPA_vect) {
 char c = GPS.read();
}

void useInterrupt(boolean v) {
 if (v) {
   // Timer0 is already used for millis() - we'll just interrupt somewhere
   // in the middle and call the "Compare A" function above
   OCR0A = 0xAF;
   TIMSK0 |= _BV(OCIE0A);
   usingInterrupt = true;
 } 
 else {
   // do not call the interrupt function COMPA anymore
   TIMSK0 &= ~_BV(OCIE0A);
   usingInterrupt = false;
 }
}

void loop() {

 // if a sentence is received, we can check the checksum, parse it...
 if (GPS.newNMEAreceived()) {

   digitalWrite(REDLED, HIGH);
   char *stringptr = GPS.lastNMEA();

   if (!GPS.parse(stringptr))   // this also sets the newNMEAreceived() flag to false
     return;  // we can fail to parse a sentence in which case we should just wait for another

   // Get a new file if needed
   if (GPS.hour != current_hour){
     logfile.close();
     //Serial.println(GPS.hour);
     //Serial.println(current_hour);
     // Make new file name
     char name[13];
     getLogFileName(name,GPS.day,GPS.month,GPS.year,GPS.hour);

     // Prevent over-writing by adding to the sub-sequence set
     for (uint8_t i = 0; i < 10; i++) {
       name[7] = '0' + i;
       // create if does not exist, do not open existing, write, sync after write
       if (! SD.exists(name)) {
         break;
       }
      }

     // Open the new file and set the current day to the new day
     logfile = SD.open(name, FILE_WRITE);
     if( ! logfile ) {
       //Serial.println(name);
       error(1);
     }
      current_hour = GPS.hour;
      Serial.println(name);
   }
   // DONE WITH NEW FILE NONSENSE

   // Sentence parsed! 
   // Log GPS Data
   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") || strstr(stringptr, "GGA"))   logfile.flush();
   digitalWrite(REDLED, LOW);
 }

 // OKAY, Now let's check and log the tilt meter
static char buffer[80];
 if (readline(Serial.read(), buffer, 80) > 0) {
   digitalWrite(GRNLED, HIGH);
   uint8_t datasize = strlen(buffer);
   if (datasize != logfile.write((uint8_t *)buffer, datasize))    //write the string to the SD file
       error(4);
  logfile.flush(); 
  digitalWrite(GRNLED, LOW);
 }

}

int readline(int port, char *buffer, int len){
 // Reads a line from the tilt meter
 // Based on example from http://hacking.majenko.co.uk/reading-serial-on-the-arduino
 static int pos = 0;
 int rpos;

 if (port > 0){
   switch (port) {
     case '\n': // Ignore new lines
       break;
     case '\r': // CR is our return character
       rpos = pos;
       pos = 0; // Reset position index
       return rpos;
     default:
       if (pos < len-1) {
         buffer[pos++] = port;
         buffer[pos] = 0;
       }
   }
 }
 // No end-line so send back -1
 return -1;
}

char getLogFileName(char *filename, int day, int month, int year, int hour) {
/*
Format is DDDYYHHA.DAT
DDD - Day of year
YY  - Year
NN  - Logger unit number
A - Sub-sequence name, used if more than one file/day
*/
strcpy(filename, "");
strcpy(filename, "00000000.DAT");

// Get day of year and set it
int doy = calculateDayOfYear(day, month, year);

filename[0] = '0' + (doy/100)%100;
filename[1] = '0' + (doy/10)%10;
filename[2] = '0' + doy%10;

// Set the year
filename[3] = '0' + (year/10)%10;
filename[4] = '0' + year%10;

// Set the hour
filename[5] = '0' + (hour/10)%10;
filename[6] = '0' + hour%10;

}

int calculateDayOfYear(int day, int month, int year) {
 /*
 Given a day, month, and year (2 or 4 digit), returns
 the day of year. Errors return 999.
 */
 // List of days in a month for a normal year
 int daysInMonth[] = {31,28,31,30,31,30,31,31,30,31,30,31};

 // Check if it is a leap year
 if (year%4  == 0)
 {
   daysInMonth[1] = 29;
 }

 // Make sure we are on a valid day of the month
 if (day < 1)
 {
   return 999;
 } else if (day > daysInMonth[month-1]) {
   return 999;
 }

 int doy = 0;
 for (int i = 0; i < month - 1; i++) {
   doy += daysInMonth[i];
 }

 doy += day;
 return doy;
}
/* End code */

User avatar
adafruit_support_bill
 
Posts: 88086
Joined: Sat Feb 07, 2009 10:11 am

Re: GPS Logging Shield with Extra Serial Sensor

Post by adafruit_support_bill »

Code: Select all

    void loop() {

     // if a sentence is received, we can check the checksum, parse it...
     if (GPS.newNMEAreceived()) {

       digitalWrite(REDLED, HIGH);
       char *stringptr = GPS.lastNMEA();

       if (!GPS.parse(stringptr))   // this also sets the newNMEAreceived() flag to false
         return;  // we can fail to parse a sentence in which case we should just wait for another
When there is GPS data, but not a complete sentence yet, you are exiting the loop early and are ignoring your other sensor data.
You should read any available serial input from either source before you start processing stuff.

User avatar
jleeman
 
Posts: 18
Joined: Sat Feb 07, 2015 5:29 pm

Re: GPS Logging Shield with Extra Serial Sensor

Post by jleeman »

So something in this vein? Thank you for taking a look, I haven't dealt with two serial devices like this before... especially not knowing all of the GPS details.

Code: Select all

void loop() {

  // Grab any serial from the GPS and store it
  if (GPS.newNMEAreceived()) {
    char *stringptr = GPS.lastNMEA();
  }

  // Grab any serial from the Tiltmeter and store it
  while (Serial.available() > 0) {
    if (index < 80) {
      char inChar = Serial.read();
      buffer[index] = inChar;
      index++;
      if (inChar == '\n'){
        writeTilt = true;
        index = 0;
      }
    }
  }
  
  // Process the GPS data if we can
  if (GPS.parse(stringptr)) {
    // Check and deal with the need for a new logging file
    if (GPS.hour != current_hour){
      logfile.close();
      char name[13];
      getLogFileName(name,GPS.day,GPS.month,GPS.year,GPS.hour);

      // Prevent over-writing by adding to the sub-sequence set
      for (uint8_t i = 0; i < 10; i++) {
        name[7] = '0' + i;
        if (! SD.exists(name)) {
          break;
        }
      }

      // Open the new file and set the current hour to the new hour
      logfile = SD.open(name, FILE_WRITE);
      if( ! logfile ) {
        error(1);
      }
      current_hour = GPS.hour;
    }
  }

  // Log the GPS Data
  if (writeGPS) {
    digitalWrite(REDLED, HIGH);
    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") || strstr(stringptr, "GGA"))   
      logfile.flush();
    digitalWrite(REDLED, LOW);
    writeGPS = false;
  }
  
  // Log the tiltmeter data if we are ready
  if (writeTilt){
    digitalWrite(GRBLED, HIGH);
    uint8_t stringsize = strlen(buffer);
    if (stringsize != logfile.write((uint8_t *)stringptr, buffer))    //write the string to the SD file
      error(4);
    if (strstr(stringptr, "XDR"))   
      logfile.flush();
    digitalWrite(GRNLED, LOW);
    writeTilt = false;
  }
}

User avatar
adafruit_support_bill
 
Posts: 88086
Joined: Sat Feb 07, 2009 10:11 am

Re: GPS Logging Shield with Extra Serial Sensor

Post by adafruit_support_bill »

Yes. That looks like the right approach.

User avatar
jleeman
 
Posts: 18
Joined: Sat Feb 07, 2015 5:29 pm

Re: GPS Logging Shield with Extra Serial Sensor

Post by jleeman »

Well, I thought I had it. And it's close. I have left the instrument logging for 24 hours and when I look through the data, 90% of it is beautiful as below:
$GPGGA,045954.000,4046.3358,N,07751.4739,W,2,10,0.90,361.5,M,-33.3,M,0000,0000*55
$GPRMC,045954.000,A,4046.3358,N,07751.4739,W,0.01,221.45,080415,,,D*7F
$YXXDR,A,1199.800,M,N,A,-344.505,D,E,C,17.39,C,T-N1052*48
There are a couple of hours though that we stop seeing the RMC sentence, the the transducer data (XDR sentence) starts to muddle into garbage:
$GPGGA,111530.000,4046.3353,N,07751.4729,W,2,07,0.92,358.7,M,-33.3,M,0000,0000*5D
$GPRMC,111530.000,A,4046.3353,N,07751.4729,W,0.02,339.76,080415,,,D*70
$Y9..0,C
$GPGGA,111531.000,4046.3353,N,07751.4729,W,2,07,0.92,359.0,M,-33.3,M,0000,0000*5A
$YR,A,A,,1*4
$GPRMC,111531.000,A,4046.3353,N,07751.4729,W,0.02,352.36,080415,,,D*78
$GPGGA,111532.000,4046.3353,N,07751.4729,W,2,07,0.92,359.2,M,-33.3,M,0000,0000*5B
$GPRMC,111532.000,A,4046.3353,N,07751.4729,W,0.01,273.96,080415,,,D*70
$Y,1137.1
$Y177210,

Occasionally a good sentence will get back in there, but I'm losing a large time chunk of data. Either I'm totally missing something in the sketch (below) or maybe it's a memory problem? The IDE says I've got something like 380 bytes left. Thank you for the help, sorry it's being a pain to solve!
Sketch uses 22,292 bytes (69%) of program storage space. Maximum is 32,256 bytes.
Global variables use 1,669 bytes (81%) of dynamic memory, leaving 379 bytes for local variables. Maximum is 2,048 bytes.

Code: Select all

#include <SPI.h>
#include <Adafruit_GPS.h>
#include <SoftwareSerial.h>
#include <SD.h>
#include <avr/sleep.h>

// Make software serial port for GPS and GPS instance
SoftwareSerial mySerial(8, 7);
Adafruit_GPS GPS(&mySerial);

// Variables and defines
#define chipSelect 10
#define REDLED 3
#define GRNLED 4

int current_hour = 99;
boolean usingInterrupt = false;
boolean writeTilt = false;
boolean writeGPS = false;
void useInterrupt(boolean); // Func prototype keeps Arduino 0023 happy
char buffer[80];
char *stringptr;
uint8_t index = 0;

File logfile;

void error(uint8_t errno) {
 /*
 Blink out error codes based on an integer input.
 First making sure that all status LEDS are off.
 */

 digitalWrite(REDLED, LOW);
 digitalWrite(GRNLED, LOW);

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

void setup() {
 /*
 Do the setup of the GPS, tilt meter, output pins, SD card, etc.
 This is where lots could go wrong if things are not hooked up correctly.
 */

 // Setup the LED pins and the CS pin for the SD card as output
 pinMode(REDLED, OUTPUT);
 pinMode(GRNLED, OUTPUT);
 pinMode(10, OUTPUT);

 // Turn on the LEDs to show that we are in setup
 digitalWrite(REDLED, HIGH);
 digitalWrite(GRNLED, HIGH);

 // see if the card is present and can be initialized:
 if (!SD.begin(chipSelect, 11, 12, 13)) {
   error(2);
 }

 // connect to the GPS at the desired rate
 GPS.begin(9600);
 GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA);
 GPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ);  
 GPS.sendCommand(PGCMD_NOANTENNA);
 useInterrupt(true);

 // Start and setup the tilt meter
 Serial.begin(9600);
 delay(3000);
 int bytesSent = Serial.write("*9900XY-SET-BAUDRATE,9600\n"); // Set baudrate on tilt meter
 delay(1000);
 bytesSent += Serial.write("*9900SO-XDR\n"); // Set to NMEA XDR format
 delay(1000);
 bytesSent += Serial.write("*9900XYC2\n"); // Set to 1 Hz output
 delay(2000);
 // DO ERROR CHECKING

 // Turn off the LEDs to show that we are done, blink them twice
 digitalWrite(REDLED, LOW);
 digitalWrite(GRNLED, LOW);

 for (int i=0; i < 2; i++) {
   delay(1000);
   digitalWrite(REDLED, HIGH);
   digitalWrite(GRNLED, HIGH);
   delay(1000);
   digitalWrite(REDLED, LOW);
   digitalWrite(GRNLED, LOW);
 }
 Serial.flush();
}


// Interrupt is called once a millisecond, looks for any new GPS data, and stores it
SIGNAL(TIMER0_COMPA_vect) {
 char c = GPS.read();
}

void useInterrupt(boolean v) {
 if (v) {
   // Timer0 is already used for millis() - we'll just interrupt somewhere
   // in the middle and call the "Compare A" function above
   OCR0A = 0xAF;
   TIMSK0 |= _BV(OCIE0A);
   usingInterrupt = true;
 } 
 else {
   // do not call the interrupt function COMPA anymore
   TIMSK0 &= ~_BV(OCIE0A);
   usingInterrupt = false;
 }
}

void loop() {

  // Grab any serial from the GPS and store it
  if (GPS.newNMEAreceived()) {
    stringptr = GPS.lastNMEA();
    writeGPS = true;
  }

  // Grab any serial from the Tiltmeter and store it
  while (Serial.available() > 0) {
    if (index < 80) {
      char inChar = Serial.read();
      buffer[index] = inChar;
      index++;
      if (inChar == '\n'){
        writeTilt = true;
        index = 0;
      }
    }
  }
  
  // Process the GPS data if we can
  if (writeGPS) {
    GPS.parse(stringptr);
    // Check and deal with the need for a new logging file
    if (GPS.hour != current_hour){
      logfile.close();
      char name[13];
      getLogFileName(name,GPS.day,GPS.month,GPS.year,GPS.hour);

      // Prevent over-writing by adding to the sub-sequence set
      for (uint8_t i = 0; i < 10; i++) {
        name[7] = '0' + i;
        if (! SD.exists(name)) {
          break;
        }
      }

      // Open the new file and set the current hour to the new hour
      logfile = SD.open(name, FILE_WRITE);
      if( ! logfile ) {
        error(1);
      }
      current_hour = GPS.hour;
    }

    digitalWrite(REDLED, HIGH);
    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") || strstr(stringptr, "GGA"))   
      logfile.flush();
    digitalWrite(REDLED, LOW);
    writeGPS = false;
  }
  
  // Log the tiltmeter data if we are ready
  if (writeTilt){
    digitalWrite(GRNLED, HIGH);
    //uint8_t stringsize = strlen(buffer);
    //if (stringsize != logfile.write((uint8_t *)stringptr, buffer))    //write the string to the SD file
    //  error(4);
    //if (strstr(stringptr, "XDR"))   
    //  logfile.flush();
    logfile.write(buffer);
    logfile.flush();
    for( int i = 0; i < sizeof(buffer);  ++i ) {
      buffer[i] = (char)0;
    }
    digitalWrite(GRNLED, LOW);
    writeTilt = false;
  }
}

char getLogFileName(char *filename, int day, int month, int year, int hour) {
/*
Format is DDDYYHHA.DAT
DDD - Day of year
YY  - Year
NN  - Logger unit number
A - Sub-sequence name, used if more than one file/day
*/
strcpy(filename, "");
strcpy(filename, "00000000.DAT");

// Get day of year and set it
int doy = calculateDayOfYear(day, month, year);

filename[0] = '0' + (doy/100)%100;
filename[1] = '0' + (doy/10)%10;
filename[2] = '0' + doy%10;

// Set the year
filename[3] = '0' + (year/10)%10;
filename[4] = '0' + year%10;

// Set the hour
filename[5] = '0' + (hour/10)%10;
filename[6] = '0' + hour%10;

}

int calculateDayOfYear(int day, int month, int year) {
 /*
 Given a day, month, and year (2 or 4 digit), returns
 the day of year. Errors return 999.
 */
 // List of days in a month for a normal year
 int daysInMonth[] = {31,28,31,30,31,30,31,31,30,31,30,31};

 // Check if it is a leap year
 if (year%4  == 0)
 {
   daysInMonth[1] = 29;
 }

 // Make sure we are on a valid day of the month
 if (day < 1)
 {
   return 999;
 } else if (day > daysInMonth[month-1]) {
   return 999;
 }

 int doy = 0;
 for (int i = 0; i < month - 1; i++) {
   doy += daysInMonth[i];
 }

 doy += day;
 return doy;
}
/* End code */

/*
for( int i = 0; i < sizeof(data);  ++i )
   data[i] = (char)0;
to clear data
*/

User avatar
adafruit_support_mike
 
Posts: 67446
Joined: Thu Feb 11, 2010 2:51 pm

Re: GPS Logging Shield with Extra Serial Sensor

Post by adafruit_support_mike »

It looks like you're losing bytes.

You might be seeing long-term drift between the Arduino's idea of 9600 baud and the GPS module's idea of it. The Arduino's clock isn't nearly as precise as the GPS module's, and even a difference of a few millihertz will add up eventually.

Try stopping and restarting the Serial connection every hour or so and see if that makes any difference.

User avatar
jleeman
 
Posts: 18
Joined: Sat Feb 07, 2015 5:29 pm

Re: GPS Logging Shield with Extra Serial Sensor

Post by jleeman »

Hmm.. I did that and let things run over over weekend. I did get more data recorded (pretty much all of the RMC and XDR sentences), but the GGA sentences still dropped out. After about 24 hours straight, the micro had crashed with nothing logging from either serial device... I'm guessing that I'm too close to the edge on memory? I may have to try tinyGPS or something, but that still doesn't help with the GGA dropouts. Here is the breakdown on hourly file sentence capture:
| File | XDR | GGA | RMC |
| 10115000.DAT| 3600| 3600| 3600|
| 10115200.DAT| 3220| 2518| 3220|
| 10115210.DAT| 3599| 3358| 3600|
| 10115220.DAT| 3601| 3144| 3600|
| 10115230.DAT| 3599| 3583| 3600|
| 10215010.DAT| 3600| 3600| 3600|
| 10215020.DAT| 3600| 3571| 3600|
| 10215030.DAT| 3600| 3550| 3600|
| 10215040.DAT| 3600| 3595| 3600|
| 10215050.DAT| 3600| 3595| 3600|
| 10215060.DAT| 3600| 3408| 3600|
| 10215070.DAT| 3600| 1110| 3600|
| 10215080.DAT| 3599| 2101| 3600|
| 10215090.DAT| 3600| 3538| 3600|
| 10215100.DAT| 3600| 3600| 3600|
| 10215110.DAT| 3600| 3600| 3600|
| 10215120.DAT| 3600| 3600| 3600|
| 10215130.DAT| 3600| 3600| 3600|
| 10215140.DAT| 3600| 3533| 3600|
| 10215150.DAT| 3600| 3581| 3600|
| 10215160.DAT| 3600| 3600| 3600|

User avatar
adafruit_support_bill
 
Posts: 88086
Joined: Sat Feb 07, 2009 10:11 am

Re: GPS Logging Shield with Extra Serial Sensor

Post by adafruit_support_bill »

Those symptoms would be consistent with a memory leak. There is a fair amount of long string processing there and that may contribute to some heap fragmentation. Paul Stoffregan has done a super job on optimizing the strings library, but you may just be hitting some boundary case.

In any case, it is easy to confirm. This page has some tips on measuring memory usage: https://learn.adafruit.com/memories-of- ... ree-memory

User avatar
jleeman
 
Posts: 18
Joined: Sat Feb 07, 2015 5:29 pm

Re: GPS Logging Shield with Extra Serial Sensor

Post by jleeman »

To tie this up in case anyone else hits the same issue, I ended up going with different Arduino boards http://shop.wickeddevice.com to have two hardware serial ports and a watchdog incase things go wrong. Thank for you the support and suggestions!

User avatar
adafruit_support_bill
 
Posts: 88086
Joined: Sat Feb 07, 2009 10:11 am

Re: GPS Logging Shield with Extra Serial Sensor

Post by adafruit_support_bill »

Thanks for the follow-up. Hardware serial certainly cuts down on a lot of the overhead. That can be a big deal on these small microcontrollers.

Locked
Please be positive and constructive with your questions and comments.

Return to “Other Arduino products from Adafruit”