Okay, this code parses the data for a proper message, then gets only the lines starting with that message. This was relatively messy, especially in C/C++. Note that the function filterForMessage doesn't pass a value really, but instead just modifies a global variable. I tried messing with pointers, but I don't think Arduino's code can handle them.
#define rxPin 7
#define txPin 8
#define ledPin 13
//definitions for 4800 baud communication
#define bit4800Delay 188
#define halfBit4800Delay 94
//The type of message we want. Edit this for other forms of GPS data.
char wantedMessage[7] = "$GPRMC";
// thisMessage is a global variable -- I didn't want to deal with returning
// strings, as properly object-oriented it would be.
char thisMessage[100];
boolean ledPinOn;
void setup() {
// define pin modes for tx, rx, led pins:
pinMode(rxPin, INPUT);
pinMode(txPin, OUTPUT);
pinMode(ledPin, OUTPUT);
digitalWrite(txPin, HIGH); //set transmit pin to high
digitalWrite(ledPin, HIGH); //turn on debug led
ledPinOn = true;
// set the data rate for the SoftwareSerial port
Serial.begin(4800); //4800 is the baud rate for EM306
}
int SWread()
{
byte val = 0;
while (digitalRead(rxPin));
//wait for start bit
if (digitalRead(rxPin) == LOW) {
//okay, so now that it's low
delayMicroseconds(halfBit4800Delay);
//wait for a tiny bit, then get data
for (int offset = 0; offset < 8; offset++) {
//iterate through bit mask.
//Or, in other words, each bit is sent after a bit4800delay,
//from MSB to LSB. get the first most significant bit, shift it left, etc.
delayMicroseconds(bit4800Delay);
val |= digitalRead(rxPin) << offset;
}
//wait for stop bit + extra
delayMicroseconds(bit4800Delay);
delayMicroseconds(bit4800Delay);
return val;
}
}
boolean filterForMessage()
{
int tempVal;
int counter;
char tempString[100]; //max string length is 100.
tempVal = SWread(); //convert byte from GPS to character
if(tempVal == '$') //if byte is a start of a new line
{
counter = 1;
tempString[0] = '$'; //add the dollar sign
//while we're reading input, and the input isn't a newline (10 is ascii newline)
while((tempVal = SWread()) && (tempVal != 10)) {
tempString[counter++] = tempVal; //add current character to temporary string
}
tempString[counter] = '\0'; //end string with null char
//now, do we want this line? let's compare the first 6 chars with wantedMessage.
if(strncmp(wantedMessage, tempString, 6) == 0) {
//if we do want this line, then copy the message into the global variable
//so we can 'pass' it to the callee of this function.
strcpy(thisMessage, tempString);
return true;
}
else
return false;
}
}
void loop() {
//if our message matches our wanted message, print it out!
if(filterForMessage() == true) {
Serial.println(thisMessage);
// toggle an LED so that we can see that it's working
// the LED will go on with every other proper message match
if(ledPinOn == true) ledPinOn = false;
else ledPinOn = true;
digitalWrite(ledPin, ledPinOn);
}
}
So, let's look at the data. An example line:
$GPRMC,083739.000,A,3922.17xx,N,07640.02xx,W,0.38,32.39,210507,,*26
Clearly I'm not here right now or anything.
The reference manual for the NMEA standard details the RMC format in page 16. This page has it as well.
A bit of data parsing in Processing led to this code. String manipulation in Java is more awkward than I remembered -- I can't seem to be able to use 'split' well.
boolean parseRMCData(String raw) {
Date tempDate;
String [] values = raw.split(","); //split values around comma
if(values[2].equals("V")) return false; //status is invalid!
//convert values into date. GAH! there should be a better way,
//but split wasn't working properly.
int h; int m; int s; int y; int mon; int d;
h = parseInt(values[1].substring(0, 2));
m = parseInt(values[1].substring(2, 4));
s = parseInt(values[1].substring(4, 6));
d = parseInt(values[9].substring(0, 2));
mon = parseInt(values[9].substring(2, 4));
y = parseInt(values[9].substring(4, 6)) + 100;
timeDate = new Date(y, mon, d, h, m, s);
//100 + y is done because y is two-digits, and the Date class
//automatically adds 1900 to the year value. Adding 100 corrects this all.
//// CONFUSING TIMEZONE CONVERSION COMING RIGHT UP
// configure a SimpleDateFormat to recognize our certain format
SimpleDateFormat sdf = new SimpleDateFormat("y M d H m s");
// configure a time zone that is EST and with dst built in.
// Base GMT offset: -5:00
// DST starts: at 2:00am in standard time
// on the second Sunday in March
// DST ends: at 2:00am in daylight time
// on the first Sunday in November
// Save: 1 hour
SimpleTimeZone stz = new SimpleTimeZone(-18000000,
"EST",
Calendar.MARCH, 2, -Calendar.SUNDAY,
7200000,
Calendar.NOVEMBER, 1, -Calendar.SUNDAY,
7200000,
3600000);
// configure our SimpleDateFormat to recieve GMT input
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
// make our SimpleDateFormat parse our date as GMT
try {
tempDate = sdf.parse(y + " " + mon + " " + d + " " + h + " " + m + " " + s);
}
catch (Exception e) {
tempDate = new Date();
}
// convert SimpleDateFormat to EST with EDT
sdf.setTimeZone(stz);
// change display pattern of our format
sdf.applyPattern("E M/W/y H:m:s z");
// convert the date to EST with EDT, and in our certain pattern
print(sdf.format(tempDate));
print(" | Lat : " + values[3] + values[4] + " | Long : " + values[5] + values[6]);
float vel = Float.parseFloat(values[7]) * 1.852;
println(" | Velocity : " + vel + " km/h in the direction " + values[8] + " degrees from north");
return true; //status is valid, of course
}