Revised Embedded Artists' MTK3339 library to save all raw GPS messages and enable optional decoding of RMC message.
Fork of MTK3339 by
Diff: MTK3339.cpp
- 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; } } } +