2021 mbed Grove GPS

Dependents:   er4_2022

Revision:
4:0ee729b4a211
Parent:
3:037ec2b52d31
Child:
5:8a5414710483
Child:
6:dffd55375288
--- a/GroveGPS.h	Thu Jun 06 20:17:47 2019 -0500
+++ b/GroveGPS.h	Thu Jun 06 21:26:39 2019 -0500
@@ -9,20 +9,13 @@
 
 public:
 
-	GroveGPS(PinName tx=D1, PinName rx=D0) : _last_line(""), gps_serial(tx, rx, 9600) {}
-
-	void readCharacter(char newCharacter) {
-		if (newCharacter == '\n') {
-			parseLine();
-			_last_line = "";
-		} else {
-			_last_line += newCharacter;
-		}
-
-		if (_last_line.length() > max_line_length) {
-		    error("GPS error - line too long");
-		    _last_line = "";
-		}
+	GroveGPS(PinName tx=D1, PinName rx=D0) : gps_serial(tx, rx, 9600) {
+	    memset(_isr_line_bufs, 0, sizeof(_isr_line_bufs));
+	    _first_line_in_use = true;
+	    _isr_line_buf_pos = 0;
+	    memset(_last_line, 0, sizeof(_last_line));
+	    _last_line_updated = false;
+	    gps_serial.attach(callback(this, &GroveGPS::read_serial), SerialBase::RxIrq);
 	}
 
 	struct GGA {
@@ -40,17 +33,20 @@
 		char    geoid_separation_units;
 		long    age_of_diff;
 		long    diff_ref_station_id;
-		std::string  checksum;
 	} gps_gga;
 
 	void getTimestamp(char* buffer) {
         m.lock();
+        parseLine();
+
 		sprintf(buffer, "%f", gps_gga.utc_time);
         m.unlock();
 	}
 
 	void getLatitude(char* buffer) {
         m.lock();
+        parseLine();
+
 		double coordinate = convertGPSToDecimal(gps_gga.latitude);
 		if (gps_gga.position_fix==0)
 			sprintf(buffer, "N/A");
@@ -61,6 +57,8 @@
 
 	void getLongitude(char* buffer) {
         m.lock();
+        parseLine();
+
 		double coordinate = convertGPSToDecimal(gps_gga.longitude);
 		if (gps_gga.position_fix==0)
 			sprintf(buffer, "N/A");
@@ -68,22 +66,43 @@
 		sprintf(buffer, "%c%f", (gps_gga.ew_indicator == 'E') ? '0' : '-', coordinate);
         m.unlock();
 	}
-    
-    void start() {
-        serial_thread.start(callback(this, &GroveGPS::read_serial));
-    }
-        
 
 private:
-    static const size_t max_line_length = 1024;
-	std::string _last_line;
-    Thread serial_thread;
-    Serial gps_serial;
+    static const size_t max_line_length = 256;
+    char _isr_line_bufs[2][max_line_length];
+    bool _first_line_in_use;
+    size_t _isr_line_buf_pos;
+    char _last_line[max_line_length];
+    bool _last_line_updated;
+
+    RawSerial gps_serial;
     Mutex m;
+
     void read_serial() {
-        while (true) {
-            if (gps_serial.readable()) {
-                 readCharacter(gps_serial.getc());
+        while (gps_serial.readable()) {
+
+            // Check for overflow
+            if (_isr_line_buf_pos > max_line_length -1 ) {
+                error("GPS error - line too long");
+                _isr_line_buf_pos = 0;
+            }
+
+            // Add a character to the active buffer
+            char *buf = _isr_line_bufs[_first_line_in_use ? 0 : 1];
+            char value = gps_serial.getc();
+            buf[_isr_line_buf_pos] = value;
+            _isr_line_buf_pos++;
+
+            // Check for end of line
+            if (value == '\n') {
+                buf[_isr_line_buf_pos] = 0;
+                _isr_line_buf_pos = 0;
+
+                // Save off this line if it is valid
+                if (memcmp("$GPGGA", buf, 6) == 0) {
+                    _first_line_in_use = !_first_line_in_use;
+                    _last_line_updated = true;
+                }
             }
         }
     }
@@ -97,32 +116,45 @@
 	}
 
 	void parseLine() {
-		if (_last_line.find("GPGGA") != std::string::npos) {
-            m.lock();
-			parseGGA();
-            m.unlock();
-		}
+	    bool parse_gga = false;
+
+	    // Atomically copy the line buffer since the ISR can change it at any time
+	    core_util_critical_section_enter();
+	    if (_last_line_updated) {
+	        char *buf_saved = _isr_line_bufs[_first_line_in_use ? 1 : 0];
+	        strcpy(_last_line, buf_saved);
+	        parse_gga = true;
+            _last_line_updated = false;
+	    }
+	    core_util_critical_section_exit();
+
+	    if (parse_gga) {
+	        parseGGA();
+	    }
 	}
 
 	void parseGGA() {
-  		char* pEnd;
+  		char *line_pos = _last_line;
 		for (int i=0; i<14; i++) {
-			std::string current_item = _last_line.substr(0,_last_line.find(","));
-			_last_line = _last_line.substr(_last_line.find(",")+1);
 			if (i==0) { 		// NMEA Tag
 			} else if (i==1) { 	// UTC time
-				gps_gga.utc_time = strtod(current_item.c_str(), &pEnd);
+				gps_gga.utc_time = strtod(line_pos, 0);
 			} else if (i==2) { 	// Latitude
-				gps_gga.latitude = strtod(current_item.c_str(), &pEnd);
+				gps_gga.latitude = strtod(line_pos, 0);
 			} else if (i==3) { 	// Latitude North/South indicator
-				gps_gga.ns_indicator = current_item[0];
+				gps_gga.ns_indicator = line_pos[0];
 			} else if (i==4) { 	// Longitude
-				gps_gga.longitude = strtod(current_item.c_str(), &pEnd);
+				gps_gga.longitude = strtod(line_pos, 0);
 			} else if (i==5) { 	// Longitude indicator
-				gps_gga.ew_indicator = current_item[0];
+				gps_gga.ew_indicator = line_pos[0];
 			} else if (i==6) {
-				gps_gga.position_fix = strtod(current_item.c_str(), &pEnd);
+				gps_gga.position_fix = strtod(line_pos, 0);
 			}
+			line_pos = strchr(line_pos, ',');
+			if (line_pos == NULL) {
+			    break;
+			}
+			line_pos += 1;
 		}
 	}
 };