VFD modular clock firmware

Dependencies:   DipCortex-EEprom RTC flw mbed

gps.cpp

Committer:
Backstr?m
Date:
2015-02-24
Revision:
12:dfb422107412
Parent:
0:f6e68b4ce169

File content as of revision 12:dfb422107412:

/*
 * 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