GPS device with text LCD display and track logging to GPX file on SD Card

Dependencies:   MODGPS SDFileSystem TextLCD mbed

mbed Pin #mbed Pin FunctionPeripheral Pin
p5SPI MOSI / SDFileSystemSD Card Data In
p6SPI MISO / SDFileSystemSD Card Data Out
p7SPI SCK / SDFileSystemSD Card Clk
p8SPI CS / SDFileSystemSD Card Data CS
p12GPIO / TextLCDLCD RS
p14GPIO / TextLCDLCD EN
p21GPIO / InterruptInButton switch to GND (start/stop logging)
p22GPIO / TextLCDLCD D4
p23GPIO / TextLCDLCD D5
p24GPIO / TextLCDLCD D6
p25GPIO / TextLCDLCD D7
p27UART RX / GPSGPS TX
Revision:
0:e8ad47e9a9b4
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.cpp	Sun Feb 17 17:37:39 2013 +0000
@@ -0,0 +1,248 @@
+/* mbed GPS Logger using SD Card and Text LCD display
+ * 
+ * Copyright (c) 2013 m.prinke, MIT License
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software 
+ * and associated documentation files (the "Software"), to deal in the Software without restriction, 
+ * including without limitation the rights to use, copy, modify, merge, publish, distribute, 
+ * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or 
+ * substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 
+ * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * @file          main.cpp 
+ * @purpose       GPS Logger using SD Card and Text LCD display
+ * @version       0.1
+ * @date          Feb 2013
+ * @author        M. Prinke 
+ */
+/**   
+ * Time and position data from GPS receiver is displayed on a text LCD.
+ * GPS tracks can be written to a logfile (GPX format) on SD Card.
+ * Logging is started/stopped using a toggle button.
+ * Everytime logging is started, a new file with the filename pattern /sd/gpslog<nnnn>.gpx
+ * is created, where <nnnn> is decimal number in the range 0000 to 9999 which is incremented.
+ * Searching for a new unique filename is started from zero. 
+ * 
+ * LED1: GPS status (blinking: fix invalid; on: fix valid)
+ * LED2: Logging status (off: stopped; on: logging)
+ *
+ * To Do:
+ * - Adjustable logging cycle time (read from config file on SD Card?)
+ * - Start new track segment after loss of GPS fix
+ * - Add support for GSA message (2D/3D fix, dilution of precision)
+ */
+
+#include "mbed.h"
+#include "GPS.h"
+#include "TextLCD.h"
+#include "SDFileSystem.h"
+
+#define GPS_BAUDRATE        4800
+#define LOGGING_CYCLE_SEC   5
+
+DigitalOut led_fix(LED1);   // GPS fix (blinking - no data/no fix; on: fix) 
+DigitalOut led_log(LED2);   // logging active
+InterruptIn button(p21);    // button connects pin to GND
+Serial pc(USBTX, USBRX);    // tx, rx
+GPS gps(NC, p27);           // tx, rx
+TextLCD lcd(p12, p14, p22, p23, p24, p25, TextLCD::LCD40x2); // rs, e, d4-d7
+SDFileSystem sd(p5, p6, p7, p8, "sd"); // mosi, miso, sclk, cs
+
+bool g_logging = false;
+bool g_changed = false;
+uint32_t g_trkpt_no = 0;
+
+/**
+ * Function to create a unique filename
+ *
+ * @param fn pointer to a filename buffer
+ * @param number part of filename (returned by reference)
+ * @return 0 on success, -1 if no unused filename could be created.
+ */
+int get_filename(char *fn, uint16_t *number)
+{
+    FILE *fp;
+    
+    for (int n=0; n < 9999; n++) {
+        sprintf(fn, "/sd/gpslog%04d.gpx", n);
+        if ((fp = fopen(fn, "r")) == NULL) {
+            *number = n;
+            // file does not exist yet
+            return 0;
+        } else {
+            fclose(fp);
+        }
+    }
+    
+    // filename pattern exhausted
+    return -1;
+}
+
+/**
+ * Callback function for button interrupt
+ *
+ * Debounce button and set change flag.
+ */
+void change_logging(void)
+{
+        wait_ms(50);        // button debounce
+        while (!button);    // wait until button released
+        g_changed = true;
+}
+
+/**
+ * Write GPX file header (including start of track segment)
+ *
+ * @param fp file pointer
+ * @param t time structure
+ * @param fileno file number
+ * @return result of last file write access
+ */
+int write_gpx_header(FILE *fp, GPS_Time t, uint16_t fileno)
+{
+    g_trkpt_no = 0; //< reset trackpoint number
+    
+    fprintf(fp, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+    fprintf(fp, "<gpx\n");
+    fprintf(fp, "  version=\"1.0\"\n");
+    fprintf(fp, "  creator=\"mbed GPS Logger\"\n");
+    fprintf(fp, "  xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n");
+    fprintf(fp, "  xmlns=\"http://www.topografix.com/GPX/1/0\"\n");
+    fprintf(fp, "  xsi:schemaLocation=\"http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd\">\n");
+    fprintf(fp, "<time>%04d-%02d-%02dT%02d:%02d:%02dZ</time>\n", t.year, t.month, t.day, t.hour, t.minute, t.second);
+    fprintf(fp, "<trk>\n");
+    fprintf(fp, "  <name>track%04d</name>\n", fileno);
+    return fprintf(fp, "<trkseg>\n");
+}
+
+/**
+ * Write GPX trackpoint - add your geo-referenced data here
+ *
+ * @param fp file pointer
+ * @param t time structure
+ * @return result of last file write access
+ */
+int write_gpx_trkpt(FILE *fp, GPS_Time t)
+{   
+    fprintf(fp, "  <trkpt lat=\"%.4f\" lon=\"%.4f\">\n", gps.latitude(), gps.longitude());
+    fprintf(fp, "    <ele>%.1f</ele>\n", gps.altitude() * 1000.0);
+    fprintf(fp, "    <time>%04d-%02d-%02dT%02d:%02d:%02dZ</time>\n", t.year, t.month, t.day, t.hour, t.minute, t.second);
+    fprintf(fp, "    <name>TP%06d</name>\n", g_trkpt_no++);
+    return fprintf(fp, "  </trkpt>\n");
+}
+
+/**
+ * Write GPX file footer (including end of track segment)
+ *
+ * @param fp file pointer
+ * @return result of last file write access
+ */
+int write_gpx_footer(FILE *fp)
+{
+    fprintf(fp, "</trkseg>\n");
+    fprintf(fp, "</trk>\n");
+    return fprintf(fp, "</gpx>\n");
+}
+
+
+int main() {
+    GPS_Time t;
+    char filename[30];
+    uint16_t fileno;
+    FILE *fp = NULL;
+    uint16_t log_wait = 0;
+    
+    button.mode(PullUp);        
+    button.fall(&change_logging);
+    gps.baud(GPS_BAUDRATE);
+    lcd.cls();
+
+    while (1) {
+        // Wait for the GPS NMEA data to become valid.
+        // (Status flag in RMC message)
+        while (!gps.isTimeValid()) {
+            led_fix = !led_fix;
+            lcd.locate(21, 0);
+            lcd.printf("[no fix]");
+            wait(1);
+        }
+
+        gps.timeNow(&t);
+#if defined(DEBUG)
+        pc.printf("The time/date is %02d:%02d:%02d %02d/%02d/%04d\r\n",
+            t.hour, t.minute, t.second, t.day, t.month, t.year);
+#endif
+        lcd.locate(0, 0);
+        lcd.printf("%02d:%02d:%02d %02d/%02d/%04d %9s %10s",
+            t.hour, t.minute, t.second, t.day, t.month, t.year, 
+            (gps.getGPSquality() > 0 ? "[fix]   " : "[no fix]"),
+            (g_logging ? "[logging]" : "[stopped]"));
+        
+        // Check if at least four satellites produce a position fix and a valid quality.
+        if (gps.numOfSats() < 4 && gps.getGPSquality() != 0) {
+            // No fix or poor quality
+            led_fix = !led_fix;
+        } else {
+            // Fix valid and good quality
+            led_fix = 1;
+#if defined(DEBUG)
+            pc.printf("Lat = %.4f Lon = %.4f Alt = %.1fkm\r\n", 
+                gps.latitude(), gps.longitude(), gps.altitude());
+#endif
+            lcd.locate(0, 1);
+            lcd.printf("Lat: %07.4f Lon: %08.4f Alt: %.0fm   ",
+                gps.latitude(), gps.longitude(), gps.altitude() * 1000.0);
+                
+            if (g_logging) {
+                if (log_wait-- == 0) {
+                    log_wait = LOGGING_CYCLE_SEC - 1;
+                    if (write_gpx_trkpt(fp, t) < 0) {
+                        // write error
+                        fclose(fp);
+                        g_logging = false;
+                        led_log = 0;
+                    }
+                }
+            }
+        }
+        wait(1);
+        
+        // Logging state changed
+        if (g_changed) {
+            if (!g_logging) {
+                // Start logging
+                if (get_filename(filename, &fileno) != 0) {
+                    error("Could not create file with unique name\n");
+                } else {
+                    // open file for writing
+                    fp = fopen(filename, "w");
+                    if (fp == NULL) {
+                        error("Could not open file for write\n");
+                    } else {
+                        if (write_gpx_header(fp, t, fileno)) {
+                            g_logging = true;
+                            led_log = 1;
+                        }
+                    }
+                }
+            } else {
+                // Stop logging 
+                write_gpx_footer(fp);
+                // close file
+                fclose(fp);
+                g_logging = false;
+                led_log = 0;
+            }
+            g_changed = false;
+        } // if (changed)
+        
+    } // while (1)
+}
\ No newline at end of file