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