VFD modular clock firmware
Dependencies: DipCortex-EEprom RTC flw mbed
Diff: gps.cpp
- Revision:
- 0:f6e68b4ce169
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gps.cpp Mon Feb 09 13:40:46 2015 +0000 @@ -0,0 +1,261 @@ +/* + * GPS support for VFD Modular Clock + * (C) 2012 William B Phelps + * + * mbed Port (C) 2014 Akafugu Corporation + * + * This program is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free Software + * Foundation; either version 2 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + */ + +#include "global.h" +#include "gps.h" + +#ifdef HAVE_GPS + +//Serial gps(P1_13, P1_14); +Serial* gps; + +bool g_gps_updating = false; +int g_gps_cks_errors = 0; +int g_gps_parse_errors = 0; +int g_gps_time_errors = 0; + +unsigned long tGPSupdate; + +// we double buffer: read into one line and leave one for the main program +//volatile char gpsBuffer1[GPSBUFFERSIZE]; +//volatile char gpsBuffer2[GPSBUFFERSIZE]; +volatile char* gpsBuffer1; +volatile char* gpsBuffer2; + +// our index into filling the current line +volatile uint8_t gpsBufferPtr; +// pointers to the double buffers +volatile char *gpsNextBuffer; +volatile char *gpsLastBuffer; +volatile uint8_t gpsDataReady_; + +time_t tLast = 0; // for checking GPS messages + +void GPSread() +{ + char c = gps->getc(); + + if (c == '$') { + gpsNextBuffer[gpsBufferPtr] = 0; + gpsBufferPtr = 0; + } + if (c == '\n') { // newline marks end of sentence + gpsNextBuffer[gpsBufferPtr] = 0; // terminate string + if (gpsNextBuffer == gpsBuffer1) { // switch buffers + gpsNextBuffer = gpsBuffer2; + gpsLastBuffer = gpsBuffer1; + } else { + gpsNextBuffer = gpsBuffer1; + gpsLastBuffer = gpsBuffer2; + } + gpsBufferPtr = 0; + gpsDataReady_ = true; // signal data ready + } + + gpsNextBuffer[gpsBufferPtr++] = c; // add char to current buffer, then increment index + if (gpsBufferPtr >= GPSBUFFERSIZE) // if buffer full + gpsBufferPtr = GPSBUFFERSIZE-1; // decrement index to make room (overrun) +} + +uint8_t gpsDataReady(void) { + return gpsDataReady_; +} + +char *gpsNMEA(void) { + gpsDataReady_ = false; + return (char *)gpsLastBuffer; +} + +uint32_t parsedecimal(char *str, uint8_t len) { + uint32_t d = 0; + for (uint8_t i=0; i<len; i++) { + if ((str[i] > '9') || (str[0] < '0')) + return d; // no more digits + d = (d*10) + (str[i] - '0'); + } + return d; +} + +int32_t _abs(int32_t a) { + if (a < 0) return -a; + return a; +} + +const char hex[17] = "0123456789ABCDEF"; + +uint8_t atoh(char x) { + return (strchr(hex, x) - hex); +} + +uint32_t hex2i(char *str, uint8_t len) { + uint32_t d = 0; + for (uint8_t i=0; i<len; i++) { + d = (d*10) + (strchr(hex, str[i]) - hex); + } + return d; +} + +// find next token in GPS string - find next comma, then point to following char +char * ntok ( char *ptr ) { + ptr = strchr(ptr, ','); // Find the next comma + if (ptr == NULL) return NULL; + ptr++; // point at next char after comma + return ptr; +} + +// 225446 Time of fix 22:54:46 UTC +// A Navigation receiver warning A = OK, V = warning +// 4916.45,N Latitude 49 deg. 16.45 min North +// 12311.12,W Longitude 123 deg. 11.12 min West +// 000.5 Speed over ground, Knots +// 054.7 Course Made Good, True +// 191194 Date of fix 19 November 1994 +// 020.3,E Magnetic variation 20.3 deg East +// *68 mandatory checksum + +//$GPRMC,225446.000,A,4916.45,N,12311.12,W,000.5,054.7,191194,020.3,E*68\r\n +// 0 1 2 3 4 5 6 7 +// 0123456789012345678901234567890123456789012345678901234567890123456789012 +// 0 1 2 3 4 5 6 7 8 9 10 11 12 +time_t parseGPSdata(char *gpsBuffer, bool& error, bool& fix, int8_t tzh, uint8_t tzm) { + time_t tNow; + struct tm tm; + uint8_t gpsCheck1, gpsCheck2; // checksums + + char gpsFixStat; // fix status +// char gpsLat[7]; // ddmm.ff (with decimal point) +// char gpsLatH; // hemisphere +// char gpsLong[8]; // dddmm.ff (with decimal point) +// char gpsLongH; // hemisphere +// char gpsSpeed[5]; // speed over ground +// char gpsCourse[5]; // Course +// char gpsDate[6]; // Date +// char gpsMagV[5]; // Magnetic variation +// char gpsMagD; // Mag var E/W +// char gpsCKS[2]; // Checksum without asterisk + + error = false; + fix = false; + + char *ptr; + uint32_t tmp; + if ( strncmp( gpsBuffer, "$GPRMC,", 7 ) == 0 ) { + //Calculate checksum from the received data + ptr = &gpsBuffer[1]; // start at the "G" + gpsCheck1 = 0; // init collector + + // Loop through entire string, XORing each character to the next + while (*ptr != '*') // count all the bytes up to the asterisk + { + gpsCheck1 ^= *ptr; + ptr++; + if (ptr>(gpsBuffer+GPSBUFFERSIZE)) goto GPSerrorP; // extra sanity check, can't hurt... + } + // now get the checksum from the string itself, which is in hex + gpsCheck2 = atoh(*(ptr+1)) * 16 + atoh(*(ptr+2)); + + if (gpsCheck1 == gpsCheck2) { // if checksums match, process the data + ptr = &gpsBuffer[1]; // start at beginning of buffer + ptr = ntok(ptr); // Find the time string + if (ptr == NULL) goto GPSerrorP; + char *p2 = strchr(ptr, ','); // find comma after Time + if (p2 == NULL) goto GPSerrorP; + if (p2 < (ptr+6)) goto GPSerrorP; // Time must be at least 6 chars + tmp = parsedecimal(ptr, 6); // parse integer portion + tm.tm_hour = tmp / 10000; + tm.tm_min = (tmp / 100) % 100; + tm.tm_sec = tmp % 100; + ptr = ntok(ptr); // Find the next token - Status + if (ptr == NULL) goto GPSerrorP; + gpsFixStat = ptr[0]; + if (gpsFixStat == 'A') { // if data valid, parse time & date + fix = true; + + for (uint8_t n=0; n<7; n++) { // skip 6 tokend, find date + ptr = ntok(ptr); // Find the next token + if (ptr == NULL) goto GPSerrorP; // error if not found + } + p2 = strchr(ptr, ','); // find comma after Date + if (p2 == NULL) goto GPSerrorP; + if (p2 != (ptr+6)) goto GPSerrorP; // check date length + tmp = parsedecimal(ptr, 6); + tm.tm_mday = tmp / 10000; + tm.tm_mon = ((tmp / 100) % 100) - 1; // zero offset for month + tm.tm_year = tmp % 100; + + ptr = strchr(ptr, '*'); // Find Checksum + if (ptr == NULL) goto GPSerrorP; + + tm.tm_year = y2kYearToTm(tm.tm_year); // convert yy year to (yyyy-1900) (add 100) + tNow = mktime(&tm); // convert to time_t - seconds since 0/0/1970 + + // If time jumps by more than a minute, complain about it. Either poor GPS signal or an error in the data + if ( (tLast>0) && (_abs(tNow - tLast)>60) ) // Beep if over 60 seconds since last GPRMC? + { + goto GPSerrorT; // it's probably an error + } + else { + tLast = tNow; + tNow = tNow + (long)(tzh) * SECS_PER_HOUR + (long)tzm; + + } + } // if fix status is A + } // if checksums match + else // checksums do not match + goto GPSerrorC; + + return tNow; + +GPSerrorC: + g_gps_cks_errors++; // increment error count + goto GPSerror2a; +GPSerrorP: + g_gps_parse_errors++; // increment error count + goto GPSerror2a; +GPSerrorT: +#ifdef HAVE_SERIAL_DEBUG + tDelta = tNow - tGPSupdate; + Serial.print("tNow="); Serial.print(tNow); Serial.print(", tLast="); Serial.print(tLast); Serial.print(", diff="); Serial.print(tNow-tLast); + Serial.print(", tDelta="); Serial.print(tDelta); + Serial.println(" "); +#endif + g_gps_time_errors++; // increment error count + tLast = tNow; // save new time + +GPSerror2a: + strcpy(gpsBuffer, ""); // wipe GPS buffer + } // if "$GPRMC" + + error = true; + return 0; +} + +void gps_init() { + gps = new Serial(P1_13, P1_14); + gps->attach(&GPSread); + + gpsBuffer1 = new char[GPSBUFFERSIZE]; + gpsBuffer2 = new char[GPSBUFFERSIZE]; + + tGPSupdate = 0; // reset GPS last update time + gpsDataReady_ = false; + gpsBufferPtr = 0; + gpsNextBuffer = gpsBuffer1; + gpsLastBuffer = gpsBuffer2; +} + +#endif // HAVE_GPS