Receiving GPS data is a job just like receiving any other serial communication. But the data received will also has to be parsed, so it can be used for control and display purposes. The procedure for receiving the GPS data is:
- Start the relevant Serial connection, e.g. Serial1.begin(9600), also called SerialGPS in the RFzero and is already initialized via the RFzero.init() function
- Collect the incoming GPS data, e.g. in an ISR function (see below)
- Parse the GPS data (see below)
You can also see the GPS NMEA data being used in the RFzero Manager GPS view.
GPS receiver ISR handler
Receiving the GPS data in the RFzero is taken care of by an Interrupt Serial Routine (ISR) that is triggered every time a character is received.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
void SERCOM4_Handler(void) { Serial1.IrqHandler(); static int gpsBufCount = 0; static char gpsBuf[100]; while (SerialGPS.available()) // Collect incoming chars { char ch = (char) SerialGPS.read(); if (gpsBufCount < sizeof(gpsBuf) - 1) { // End of line found so parse the buffer if (ch == '\n') { // End of line found so parse the buffer gps.ParseNMEAFrame(gpsBuf); gpsBufCount = 0; // Clear buffer gpsBuf[0] = 0; } else { // Pad buffer with latest char gpsBuf[gpsBufCount] = ch; gpsBufCount++; gpsBuf[gpsBufCount] = 0; } } else { // Overflow so reset buffer gpsBufCount = 0; gpsBuf[0] = 0; } } } |
GPS data parser
Parsing, or splitting, the GPS data string into the relevant pieces can be done in many ways. Below is one example of splitting the NMEA $GPGGA frame to pieces that are used in the RFzero. But the same principle can be applied to any kind of data in a string separated by a delimiter, in his case a comma (,). The pieces of data parsed by ParseNMEAFrame(char *gpsNMEA) are written to global variables, but they could also be returned by the function if relevant. The return value from the parser is either an integer combining the UTC minutes and seconds (100 x minutes + seconds) or -1 if the GPS data was invalid.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
int ParseNMEAFrame(char *gpsNMEA) { char *gPtr; int mmss = -1; // $GPGGA,205019.00,5449.69634,N,01156.28487,E,1,12,0.98,29.3,M,39.7,M,,*6B if (0 == strncmp("$GPGGA,", gpsNMEA, 7)) { // Time gPtr = strchr(gpsNMEA, ',') + 1; if (',' != *gPtr) { utcHours = 10 * (gPtr[0] - 48) + gPtr[1] - 48; utcMinutes = 10 * (gPtr[2] - 48) + gPtr[3] - 48; utcSeconds = 10 * (gPtr[4] - 48) + gPtr[5] - 48; mmss = 100 * utcMinutes + utcSeconds; gPtr += 6; } // Lat and long if (utcSeconds % 30) // Only look for lat and long every 30 s { for (int i = 0; i < 4; i++) gPtr = strchr(gPtr, ',') + 1; // Step four more places } else { gPtr = strchr(gPtr, ',') + 1; latitude = NMEA2Deg(gPtr); for (int i = 0; i < 2; i++) gPtr = strchr(gPtr, ',') + 1; // Step two more places longitude = NMEA2Deg(gPtr); gPtr = strchr(gPtr, ',') + 1; } // Fix quality / valid/invalid gPtr = strchr(gPtr, ',') + 1; if (',' != *gPtr) valid = *gPtr - 48; else { valid = 0; mmss = -1; } // Update VALID_LED if (valid) digitalWrite(VALID_LED, LOW) ; else digitalWrite(VALID_LED, HIGH) ; // Number of satellites gPtr = strchr(gPtr, ',') + 1; if (',' != *gPtr) satellites = 10 * (gPtr[0] - 48) + gPtr[1] - 48; // Horizontal dilution gPtr = strchr(gPtr, ',') + 1; if (',' != *gPtr) { char hdopStr[5]; hdopStr[0] = gPtr[0]; hdopStr[1] = gPtr[1]; hdopStr[2] = gPtr[2]; if (gPtr[3] == ',') hdopStr[3] = 0; // Handle situations HDOP 3 or 4 chars long else hdopStr[3] = gPtr[3]; hdop = atof(hdopStr); } } return mmss; } |
Parsing the string by a delimiter is a safer way for strings where each chunk may vary in length from time to time.
If you need to parse another NMEA frame, e.g. $GPRMC, all you have to do is either modify the existing parser or add it to the parser through another if (0 == strncmp(“$GPRMC,”, gpsNMEA, 7)) check.