Revised Embedded Artists' MTK3339 library to save all raw GPS messages and enable optional decoding of RMC message.

Fork of MTK3339 by EmbeddedArtists AB

Revision:
1:3057ad6a5d4b
Parent:
0:bd0fe2412980
Child:
2:2391d165df47
--- a/MTK3339.cpp	Thu Nov 07 11:46:53 2013 +0000
+++ b/MTK3339.cpp	Fri Jul 31 21:20:55 2015 +0000
@@ -1,16 +1,33 @@
+// Class to represent serial interface to MTK3339 GPS chip
+// Original author: Embedded Artists
+// Revised by T. Bronez, 2015-05-28
 
 #include "mbed.h"
 #include "MTK3339.h"
+#define min(a,b) a>b?a:b 
 
+DigitalOut led2(LED2);
+Timeout timeout2;
+void led2Off() {
+    led2 = 1;
+}
+void flashLED2() {
+    led2 = 0;
+    timeout2.attach(&led2Off, 0.1);
+}
 
 MTK3339::MTK3339(PinName tx, PinName rx) : _serial(tx, rx) {
     _serial.baud(9600);    
-
     _state = StateStart;
     _sentenceMask = 0;
-    _availDataType = NmeaInvalid;
-    memset(&gga, 0, sizeof(GgaType));
-    memset(&vtg, 0, sizeof(VtgType));
+    _availDataType = NMEA_NONE;
+    memset(&rmc, 0, sizeof(RmcType));
+
+    memset(&ggaMsg, 0, MSG_BUF_SZ);
+    memset(&gsaMsg, 0, MSG_BUF_SZ);
+    memset(&gsvMsg, 0, MSG_BUF_SZ);
+    memset(&rmcMsg, 0, MSG_BUF_SZ);
+    memset(&vtgMsg, 0, MSG_BUF_SZ);
 }
 
 void MTK3339::start(void (*fptr)(void), int mask) {
@@ -18,7 +35,7 @@
         _dataCallback.attach(fptr);
         _sentenceMask = mask;
         _serial.attach(this, &MTK3339::uartIrq, Serial::RxIrq);
-    }        
+    }
 }
 
 void MTK3339::stop() {
@@ -31,156 +48,206 @@
     return _availDataType;
 }
 
-double MTK3339::getLatitudeAsDegrees() {
-    if(gga.fix == 0 || gga.nsIndicator == 0) return 0;
-    
-    double l = gga.latitude;
-    char ns = gga.nsIndicator;
-    
-    // convert from ddmm.mmmm to degrees only
-    // 60 minutes is 1 degree
-    
-    int deg = (int)(l / 100);
-    l = (l - deg*100.0) / 60.0;
-    l = deg + l;
-    if (ns == 'S') l = -l;
+void MTK3339::decodeRMC() {
+    // See http://aprs.gids.nl/nmea/#rmc for RMC sentence definition
+    const double sixtieth = 1.0/60.0;
+    int n = 0;      // scratch int for conversions
+    double d = 0;   // scratch double for conversions
+    char* q = NULL; // scratch pointer for conversions
+
+    memset(&rmc, 0, sizeof(RmcType));
+    char* p = rmcMsg;
+
+    /** 
+     * Find and decode field 0, UTC time, as hhmmss.sss
+     */
+    p = strchr(p, ',');                 // First comma
+    if (p == NULL || *p == 0) return;   // Unexpected
+    p++;                                // Start of field
+
+    q = p;
+    // Decode time hours
+    n  = ((*q++) - 48)*10;  // tens
+    n += ((*q++) - 48);     // ones
+    rmc.gps_tm.tm_hour = n;
+    // Decode time minutes
+    n  = ((*q++) - 48)*10;  // tens
+    n += ((*q++) - 48);     // ones
+    rmc.gps_tm.tm_min = n;
+    // Decode time seconds
+    n  = ((*q++) - 48)*10;  // tens
+    n += ((*q++) - 48);     // ones
+    rmc.gps_tm.tm_sec = n;
+    // Decode time milliseconds
+    q++;                    // decimal point
+    n  = ((*q++) - 48)*100; // hundreds of msec = tenths of second
+    n += ((*q++) - 48)*10;  // tens of msec = hundredths of second
+    n += ((*q++) - 48);     // ones of msec = thousandths of second
+    rmc.msec = n;
+
+    /** 
+     * Find and decode field 1, GPS status, as single
+     * character 'A' for valid or 'V' for invalid
+     */
+    p = strchr(p, ',');                 // Next comma
+    if (p == NULL || *p == 0) return;   // Unexpected
+    p++;                                // Start of field
+
+    if (*p == 'A' || *p == 'V')
+        rmc.status = *p;
+    else
+        rmc.status = ' ';
+
+    /**
+     * Find and decode field 2, latitude, formatted as ddmm.mmmm
+     */
+    p = strchr(p, ',');                 // Next comma
+    if (p == NULL || *p == 0) return;   // Unexpected
+    p++;                                // Start of field
+
+    q = p;
+    d  = ((*q++) - 48)*10;      // tens
+    d += ((*q++) - 48);         // ones
+    rmc.lat_dd = d;             // whole degrees
     
-    return l;
-}
+    d  = ((*q++) - 48)*10;      // tens
+    d += ((*q++) - 48);         // ones
+    q++;                        // decimal point
+    d += ((*q++) - 48)*0.1000;  // tenths
+    d += ((*q++) - 48)*0.0100;  // hundredths
+    d += ((*q++) - 48)*0.0010;  // thousandths
+    d += ((*q++) - 48)*0.0001;  // ten-thousandths
+    if (*q == ',')
+        rmc.lat_dd += sixtieth*d;   // decimal degrees
+    else
+        rmc.lat_dd = NAN;           // Unexpected
+        
+    /**
+     * Find and decode field 3, latitude sense, as single
+     * character 'N' for north or 'S' for south
+     */
+    p = strchr(p, ',');                 // Next comma
+    if (p == NULL || *p == 0) return;   // Unexpected
+    p++;                                // Start of field
 
-double MTK3339::getLongitudeAsDegrees() {
-    if(gga.fix == 0 || gga.ewIndicator == 0) return 0;
-    
-    double l = gga.longitude;
-    char ew = gga.ewIndicator;
+    if (*p == 'S') rmc.lat_dd = -rmc.lat_dd;
+
+    /**
+     * Find and decode field 4, longitude, formatted as dddmm.mmmm
+     */
+    p = strchr(p, ',');                 // Next comma
+    if (p == NULL || *p == 0) return;   // Unexpected
+    p++;                                // Start of field
+
+    q = p;
+    d  = ((*q++) - 48)*100;     // hundreds
+    d += ((*q++) - 48)*10;      // tens
+    d += ((*q++) - 48);         // ones
+    rmc.lon_dd = d;             // whole degrees
     
-    // convert from ddmm.mmmm to degrees only
-    // 60 minutes is 1 degree
+    d  = ((*q++) - 48)*10;      // tens
+    d += ((*q++) - 48);         // ones
+    q++;                        // decimal point
+    d += ((*q++) - 48)*0.1000;  // tenths
+    d += ((*q++) - 48)*0.0100;  // hundredths
+    d += ((*q++) - 48)*0.0010;  // thousandths
+    d += ((*q++) - 48)*0.0001;  // ten-thousandths
+    if (*q == ',')
+        rmc.lon_dd += sixtieth*d;   // decimal degrees
+    else
+        rmc.lon_dd = NAN;           // Unexpected
+
+    /**
+     * Find and decode field 5, longitude sense, as single
+     * character 'E' for east or 'W' for west
+     */
+    p = strchr(p, ',');                 // Next comma
+    if (p == NULL || *p == 0) return;   // Unexpected
+    p++;                                // Start of field
+
+    if (*p == 'W') rmc.lon_dd = -rmc.lon_dd;
     
-    int deg = (int)(l / 100);
-    l = (l - deg*100) / 60;
-    l = deg + l;
-    if (ew == 'W') l = -l;
-    
-    return l;
+    /**
+     * Find and skip field 6, speed in knots
+     */
+    p = strchr(p, ',');                 // Next comma
+    if (p == NULL || *p == 0) return;   // Unexpected
+    p++;                                // Start of field
+
+    /**
+     * Find and skip field 7, course in degrees
+     */
+    p = strchr(p, ',');                 // Next comma
+    if (p == NULL || *p == 0) return;   // Unexpected
+    p++;                                // Start of field
+
+    /**
+     * Find and decode field 8, UTC date, formatted as ddmmyy
+     * where 1<=dd<=31, 1<=mm<=12, and yy is offset from 2000
+     */
+    p = strchr(p, ',');                 // Next comma
+    if (p == NULL || *p == 0) return;   // Unexpected
+    p++;                                // Start of field
+
+    q = p;
+    // Decode date day
+    n  = ((*q++) - 48)*10;  // tens
+    n += ((*q++) - 48);     // ones
+    rmc.gps_tm.tm_mday = n;
+    // Decode date month
+    n  = ((*q++) - 48)*10;      // tens
+    n += ((*q++) - 48);         // ones
+    rmc.gps_tm.tm_mon = n-1;  // tm_mon runs 0-11, not 1-12
+    // Decode date year
+    n  = ((*q++) - 48)*10;          // tens
+    n += ((*q++) - 48);             // ones
+    rmc.gps_tm.tm_year = n+100;   // tm_year is offset from year 1900, not 2000
+
+    /**
+     * Find and skip field 9, magnetic variation
+     * (not implemented in GPS firmware)
+     */
+    p = strchr(p, ',');                 // Next comma
+    if (p == NULL || *p == 0) return;   // Unexpected
+    p++;                                // Start of field
+
+    /**
+     * Find and skip field 10, magnetic variation direction
+     * (not implemented in GPS firmware)
+     */
+    p = strchr(p, ',');                 // Next comma
+    if (p == NULL || *p == 0) return;   // Unexpected
+    p++;                                // Start of field
 }
 
-void MTK3339::parseGGA(char* data, int dataLen) {
-    //http://aprs.gids.nl/nmea/#gga
-    
-    double tm = 0;
-    
-    memset(&gga, 0, sizeof(GgaType));
-    
-    char* p = data;
-    int pos = 0;    
-    
-    p = strchr(p, ',');
-    while (p != NULL && *p != 0) {
-        p++;
-        
-        switch(pos) {
-            case 0: // time: hhmmss.sss
-                tm = strtod(p, NULL);
-                gga.hours = (int)(tm / 10000);
-                gga.minutes = ((int)tm % 10000) / 100;
-                gga.seconds = ((int)tm % 100);
-                gga.milliseconds = (int)(tm * 1000) % 1000;
-                break;
-            case 1: // latitude: ddmm.mmmm
-                gga.latitude = strtod(p, NULL);
-                break;
-            case 2: // N/S indicator (north or south)
-                if (*p == 'N' || *p == 'S') {
-                    gga.nsIndicator = *p;
-                }
-                break;
-            case 3: // longitude: dddmm.mmmm
-                gga.longitude = strtod(p, NULL);
-                break;                
-            case 4: // E/W indicator (east or west)
-                if (*p == 'E' || *p == 'W') {
-                    gga.ewIndicator = *p;
-                }
-                break;                                
-            case 5: // position indicator (1=no fix, 2=GPS fix, 3=Differential)
-                gga.fix = strtol(p, NULL, 10);
-                break;                                             
-            case 6: // num satellites
-                gga.satellites = strtol(p, NULL, 10);
-                break;                                                             
-            case 7: // hdop
-                gga.hdop = strtod(p, NULL);
-                break;                               
-            case 8: // altitude
-                gga.altitude = strtod(p, NULL);
-                break;                     
-            case 9: // units
-                // ignore units
-                break;                         
-            case 10: // geoidal separation
-                gga.geoidal = strtod(p, NULL);
-                break;                      
-        }
-        pos++;
-        
-        p = strchr(p, ',');            
-    }
-    
+void MTK3339::saveGGA(char* data, int dataLen) {
+    // See http://aprs.gids.nl/nmea/#gga for GGA sentence definition
+    memcpy (ggaMsg, data, min(dataLen, MSG_BUF_SZ));
+}
+
+void MTK3339::saveGSA(char* data, int dataLen) {
+    // See http://aprs.gids.nl/nmea/#gsa for GSA sentence definition
+    memcpy (gsaMsg, data, min(dataLen, MSG_BUF_SZ));
 }
 
-void MTK3339::parseVTG(char* data, int dataLen) {
-     
-    
-    char* p = data;
-    int pos = 0;    
-    
-    memset(&vtg, 0, sizeof(VtgType));
-    
-    p = strchr(p, ',');
-    while (p != NULL && *p != 0) {
-        p++;
-        
-        switch(pos) {
-            case 0: // course in degrees
-                vtg.course = strtod(p, NULL);
-                break;
-            case 1: // Reference (T)                
-                break;
-            case 2: // course magnetic (need customization)
-                break;
-            case 3: // reference (M)
-                break;                
-            case 4: // speed in knots
-                vtg.speedKnots = strtod(p, NULL);
-                break;                                
-            case 5: // units (N)
-                break;                                             
-            case 6: // speed in Km/h
-                vtg.speedKmHour = strtod(p, NULL);
-                break;                                                             
-            case 7: // units (K)                
-                break;                               
-            case 8: // mode
-                if (*p == 'A' || *p == 'D' || *p == 'E') {
-                    vtg.mode = *p;
-                }
-                                
-                break;                     
-                
-        }
-
-        pos++;
-        
-        p = strchr(p, ',');            
-    }
+void MTK3339::saveGSV(char* data, int dataLen) {
+    // See http://aprs.gids.nl/nmea/#gsv for GSV sentence definition
+    memcpy (gsvMsg, data, min(dataLen, MSG_BUF_SZ));
 }
 
+void MTK3339::saveRMC(char* data, int dataLen) {
+    // See http://aprs.gids.nl/nmea/#rmc for RMC sentence definition
+    memcpy (rmcMsg, data, min(dataLen, MSG_BUF_SZ));
+}
 
-void MTK3339::parseData(char* data, int len) {
+void MTK3339::saveVTG(char* data, int dataLen) {
+    // See http://aprs.gids.nl/nmea/#vtg for VTG sentence definition
+    memcpy (vtgMsg, data, min(dataLen, MSG_BUF_SZ));
+}
+
+void MTK3339::saveData(char* data, int len) {
     do {
-    
-        // verify checksum 
+        // Verify checksum 
         if (len < 3 || (len > 3 && data[len-3] != '*')) {
             // invalid data
             break;
@@ -192,32 +259,47 @@
         if (sum != 0) {
             // invalid checksum
             break;
-        }  
-        
-        
-        if (strncmp("$GPGGA", data, 6) == 0 && (_sentenceMask & NmeaGga) != 0) {            
-            parseGGA(data, len);
-            _availDataType = NmeaGga;
+        }
+
+        // Save appropriate sentence for later decoding   
+        if (strncmp("$GPRMC", data, 6) == 0 && (_sentenceMask & NMEA_RMC) != 0) {
+            flashLED2();
+            saveRMC(data, len);
+            _availDataType = NMEA_RMC;
             _dataCallback.call();
-            _availDataType = NmeaInvalid;
+            _availDataType = NMEA_NONE;
+        }
+        else if (strncmp("$GPGGA", data, 6) == 0 && (_sentenceMask & NMEA_GGA) != 0) {
+            saveGGA(data, len);
+            _availDataType = NMEA_GGA;
+            _dataCallback.call();
+            _availDataType = NMEA_NONE;
         }          
-        else if (strncmp("$GPVTG", data, 6) == 0 && (_sentenceMask & NmeaVtg) != 0) {            
-            parseVTG(data, len);
-            _availDataType = NmeaVtg;
+        else if (strncmp("$GPGSA", data, 6) == 0 && (_sentenceMask & NMEA_GSA) != 0) {
+            saveGSA(data, len);
+            _availDataType = NMEA_GSA;
             _dataCallback.call();
-            _availDataType = NmeaInvalid;
-        }     
-
-    
+            _availDataType = NMEA_NONE;
+        }          
+        else if (strncmp("$GPGSV", data, 6) == 0 && (_sentenceMask & NMEA_GSV) != 0) {
+            saveGSV(data, len);
+            _availDataType = NMEA_GSV;
+            _dataCallback.call();
+            _availDataType = NMEA_NONE;
+        }          
+        else if (strncmp("$GPVTG", data, 6) == 0 && (_sentenceMask & NMEA_VTG) != 0) {
+            saveVTG(data, len);
+            _availDataType = NMEA_VTG;
+            _dataCallback.call();
+            _availDataType = NMEA_NONE;
+        }          
     } while(0);
 }
 
 void MTK3339::uartIrq() {
     char d = 0;
-    
     while(_serial.readable()) {
         d = _serial.getc();
-        
         switch(_state) {
         case StateStart:
             if (d == '$') {                
@@ -232,18 +314,15 @@
                 _state = StateStart;
             }
             else if (d == '\r') {
-                
                 _buf[_bufPos] = 0;
-                
-                parseData(_buf, _bufPos);
-                
+                saveData(_buf, _bufPos);
                 _state = StateStart;
             }
             else {
                 _buf[_bufPos++] = d;
             }
-            
             break;
         }
     }
 }
+