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
Committer:
mprinke
Date:
Sun Feb 17 17:42:16 2013 +0000
Revision:
1:1b62ee4c7e05
Parent:
0:e8ad47e9a9b4
initial version

Who changed what in which revision?

UserRevisionLine numberNew contents of line
mprinke 0:e8ad47e9a9b4 1 /* mbed GPS Logger using SD Card and Text LCD display
mprinke 0:e8ad47e9a9b4 2 *
mprinke 0:e8ad47e9a9b4 3 * Copyright (c) 2013 m.prinke, MIT License
mprinke 0:e8ad47e9a9b4 4 *
mprinke 0:e8ad47e9a9b4 5 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
mprinke 0:e8ad47e9a9b4 6 * and associated documentation files (the "Software"), to deal in the Software without restriction,
mprinke 0:e8ad47e9a9b4 7 * including without limitation the rights to use, copy, modify, merge, publish, distribute,
mprinke 0:e8ad47e9a9b4 8 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
mprinke 0:e8ad47e9a9b4 9 * furnished to do so, subject to the following conditions:
mprinke 0:e8ad47e9a9b4 10 *
mprinke 0:e8ad47e9a9b4 11 * The above copyright notice and this permission notice shall be included in all copies or
mprinke 0:e8ad47e9a9b4 12 * substantial portions of the Software.
mprinke 0:e8ad47e9a9b4 13 *
mprinke 0:e8ad47e9a9b4 14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
mprinke 0:e8ad47e9a9b4 15 * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
mprinke 0:e8ad47e9a9b4 16 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
mprinke 0:e8ad47e9a9b4 17 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
mprinke 0:e8ad47e9a9b4 18 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
mprinke 0:e8ad47e9a9b4 19 *
mprinke 0:e8ad47e9a9b4 20 * @file main.cpp
mprinke 0:e8ad47e9a9b4 21 * @purpose GPS Logger using SD Card and Text LCD display
mprinke 0:e8ad47e9a9b4 22 * @version 0.1
mprinke 0:e8ad47e9a9b4 23 * @date Feb 2013
mprinke 0:e8ad47e9a9b4 24 * @author M. Prinke
mprinke 0:e8ad47e9a9b4 25 */
mprinke 0:e8ad47e9a9b4 26 /**
mprinke 0:e8ad47e9a9b4 27 * Time and position data from GPS receiver is displayed on a text LCD.
mprinke 0:e8ad47e9a9b4 28 * GPS tracks can be written to a logfile (GPX format) on SD Card.
mprinke 0:e8ad47e9a9b4 29 * Logging is started/stopped using a toggle button.
mprinke 0:e8ad47e9a9b4 30 * Everytime logging is started, a new file with the filename pattern /sd/gpslog<nnnn>.gpx
mprinke 0:e8ad47e9a9b4 31 * is created, where <nnnn> is decimal number in the range 0000 to 9999 which is incremented.
mprinke 0:e8ad47e9a9b4 32 * Searching for a new unique filename is started from zero.
mprinke 0:e8ad47e9a9b4 33 *
mprinke 0:e8ad47e9a9b4 34 * LED1: GPS status (blinking: fix invalid; on: fix valid)
mprinke 0:e8ad47e9a9b4 35 * LED2: Logging status (off: stopped; on: logging)
mprinke 0:e8ad47e9a9b4 36 *
mprinke 0:e8ad47e9a9b4 37 * To Do:
mprinke 0:e8ad47e9a9b4 38 * - Adjustable logging cycle time (read from config file on SD Card?)
mprinke 0:e8ad47e9a9b4 39 * - Start new track segment after loss of GPS fix
mprinke 0:e8ad47e9a9b4 40 * - Add support for GSA message (2D/3D fix, dilution of precision)
mprinke 0:e8ad47e9a9b4 41 */
mprinke 0:e8ad47e9a9b4 42
mprinke 0:e8ad47e9a9b4 43 #include "mbed.h"
mprinke 0:e8ad47e9a9b4 44 #include "GPS.h"
mprinke 0:e8ad47e9a9b4 45 #include "TextLCD.h"
mprinke 0:e8ad47e9a9b4 46 #include "SDFileSystem.h"
mprinke 0:e8ad47e9a9b4 47
mprinke 0:e8ad47e9a9b4 48 #define GPS_BAUDRATE 4800
mprinke 0:e8ad47e9a9b4 49 #define LOGGING_CYCLE_SEC 5
mprinke 0:e8ad47e9a9b4 50
mprinke 0:e8ad47e9a9b4 51 DigitalOut led_fix(LED1); // GPS fix (blinking - no data/no fix; on: fix)
mprinke 0:e8ad47e9a9b4 52 DigitalOut led_log(LED2); // logging active
mprinke 0:e8ad47e9a9b4 53 InterruptIn button(p21); // button connects pin to GND
mprinke 0:e8ad47e9a9b4 54 Serial pc(USBTX, USBRX); // tx, rx
mprinke 0:e8ad47e9a9b4 55 GPS gps(NC, p27); // tx, rx
mprinke 0:e8ad47e9a9b4 56 TextLCD lcd(p12, p14, p22, p23, p24, p25, TextLCD::LCD40x2); // rs, e, d4-d7
mprinke 0:e8ad47e9a9b4 57 SDFileSystem sd(p5, p6, p7, p8, "sd"); // mosi, miso, sclk, cs
mprinke 0:e8ad47e9a9b4 58
mprinke 0:e8ad47e9a9b4 59 bool g_logging = false;
mprinke 0:e8ad47e9a9b4 60 bool g_changed = false;
mprinke 0:e8ad47e9a9b4 61 uint32_t g_trkpt_no = 0;
mprinke 0:e8ad47e9a9b4 62
mprinke 0:e8ad47e9a9b4 63 /**
mprinke 0:e8ad47e9a9b4 64 * Function to create a unique filename
mprinke 0:e8ad47e9a9b4 65 *
mprinke 0:e8ad47e9a9b4 66 * @param fn pointer to a filename buffer
mprinke 0:e8ad47e9a9b4 67 * @param number part of filename (returned by reference)
mprinke 0:e8ad47e9a9b4 68 * @return 0 on success, -1 if no unused filename could be created.
mprinke 0:e8ad47e9a9b4 69 */
mprinke 0:e8ad47e9a9b4 70 int get_filename(char *fn, uint16_t *number)
mprinke 0:e8ad47e9a9b4 71 {
mprinke 0:e8ad47e9a9b4 72 FILE *fp;
mprinke 0:e8ad47e9a9b4 73
mprinke 0:e8ad47e9a9b4 74 for (int n=0; n < 9999; n++) {
mprinke 0:e8ad47e9a9b4 75 sprintf(fn, "/sd/gpslog%04d.gpx", n);
mprinke 0:e8ad47e9a9b4 76 if ((fp = fopen(fn, "r")) == NULL) {
mprinke 0:e8ad47e9a9b4 77 *number = n;
mprinke 0:e8ad47e9a9b4 78 // file does not exist yet
mprinke 0:e8ad47e9a9b4 79 return 0;
mprinke 0:e8ad47e9a9b4 80 } else {
mprinke 0:e8ad47e9a9b4 81 fclose(fp);
mprinke 0:e8ad47e9a9b4 82 }
mprinke 0:e8ad47e9a9b4 83 }
mprinke 0:e8ad47e9a9b4 84
mprinke 0:e8ad47e9a9b4 85 // filename pattern exhausted
mprinke 0:e8ad47e9a9b4 86 return -1;
mprinke 0:e8ad47e9a9b4 87 }
mprinke 0:e8ad47e9a9b4 88
mprinke 0:e8ad47e9a9b4 89 /**
mprinke 0:e8ad47e9a9b4 90 * Callback function for button interrupt
mprinke 0:e8ad47e9a9b4 91 *
mprinke 0:e8ad47e9a9b4 92 * Debounce button and set change flag.
mprinke 0:e8ad47e9a9b4 93 */
mprinke 0:e8ad47e9a9b4 94 void change_logging(void)
mprinke 0:e8ad47e9a9b4 95 {
mprinke 0:e8ad47e9a9b4 96 wait_ms(50); // button debounce
mprinke 0:e8ad47e9a9b4 97 while (!button); // wait until button released
mprinke 0:e8ad47e9a9b4 98 g_changed = true;
mprinke 0:e8ad47e9a9b4 99 }
mprinke 0:e8ad47e9a9b4 100
mprinke 0:e8ad47e9a9b4 101 /**
mprinke 0:e8ad47e9a9b4 102 * Write GPX file header (including start of track segment)
mprinke 0:e8ad47e9a9b4 103 *
mprinke 0:e8ad47e9a9b4 104 * @param fp file pointer
mprinke 0:e8ad47e9a9b4 105 * @param t time structure
mprinke 0:e8ad47e9a9b4 106 * @param fileno file number
mprinke 0:e8ad47e9a9b4 107 * @return result of last file write access
mprinke 0:e8ad47e9a9b4 108 */
mprinke 0:e8ad47e9a9b4 109 int write_gpx_header(FILE *fp, GPS_Time t, uint16_t fileno)
mprinke 0:e8ad47e9a9b4 110 {
mprinke 0:e8ad47e9a9b4 111 g_trkpt_no = 0; //< reset trackpoint number
mprinke 0:e8ad47e9a9b4 112
mprinke 0:e8ad47e9a9b4 113 fprintf(fp, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
mprinke 0:e8ad47e9a9b4 114 fprintf(fp, "<gpx\n");
mprinke 0:e8ad47e9a9b4 115 fprintf(fp, " version=\"1.0\"\n");
mprinke 0:e8ad47e9a9b4 116 fprintf(fp, " creator=\"mbed GPS Logger\"\n");
mprinke 0:e8ad47e9a9b4 117 fprintf(fp, " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n");
mprinke 0:e8ad47e9a9b4 118 fprintf(fp, " xmlns=\"http://www.topografix.com/GPX/1/0\"\n");
mprinke 0:e8ad47e9a9b4 119 fprintf(fp, " xsi:schemaLocation=\"http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd\">\n");
mprinke 0:e8ad47e9a9b4 120 fprintf(fp, "<time>%04d-%02d-%02dT%02d:%02d:%02dZ</time>\n", t.year, t.month, t.day, t.hour, t.minute, t.second);
mprinke 0:e8ad47e9a9b4 121 fprintf(fp, "<trk>\n");
mprinke 0:e8ad47e9a9b4 122 fprintf(fp, " <name>track%04d</name>\n", fileno);
mprinke 0:e8ad47e9a9b4 123 return fprintf(fp, "<trkseg>\n");
mprinke 0:e8ad47e9a9b4 124 }
mprinke 0:e8ad47e9a9b4 125
mprinke 0:e8ad47e9a9b4 126 /**
mprinke 0:e8ad47e9a9b4 127 * Write GPX trackpoint - add your geo-referenced data here
mprinke 0:e8ad47e9a9b4 128 *
mprinke 0:e8ad47e9a9b4 129 * @param fp file pointer
mprinke 0:e8ad47e9a9b4 130 * @param t time structure
mprinke 0:e8ad47e9a9b4 131 * @return result of last file write access
mprinke 0:e8ad47e9a9b4 132 */
mprinke 0:e8ad47e9a9b4 133 int write_gpx_trkpt(FILE *fp, GPS_Time t)
mprinke 0:e8ad47e9a9b4 134 {
mprinke 0:e8ad47e9a9b4 135 fprintf(fp, " <trkpt lat=\"%.4f\" lon=\"%.4f\">\n", gps.latitude(), gps.longitude());
mprinke 0:e8ad47e9a9b4 136 fprintf(fp, " <ele>%.1f</ele>\n", gps.altitude() * 1000.0);
mprinke 0:e8ad47e9a9b4 137 fprintf(fp, " <time>%04d-%02d-%02dT%02d:%02d:%02dZ</time>\n", t.year, t.month, t.day, t.hour, t.minute, t.second);
mprinke 0:e8ad47e9a9b4 138 fprintf(fp, " <name>TP%06d</name>\n", g_trkpt_no++);
mprinke 0:e8ad47e9a9b4 139 return fprintf(fp, " </trkpt>\n");
mprinke 0:e8ad47e9a9b4 140 }
mprinke 0:e8ad47e9a9b4 141
mprinke 0:e8ad47e9a9b4 142 /**
mprinke 0:e8ad47e9a9b4 143 * Write GPX file footer (including end of track segment)
mprinke 0:e8ad47e9a9b4 144 *
mprinke 0:e8ad47e9a9b4 145 * @param fp file pointer
mprinke 0:e8ad47e9a9b4 146 * @return result of last file write access
mprinke 0:e8ad47e9a9b4 147 */
mprinke 0:e8ad47e9a9b4 148 int write_gpx_footer(FILE *fp)
mprinke 0:e8ad47e9a9b4 149 {
mprinke 0:e8ad47e9a9b4 150 fprintf(fp, "</trkseg>\n");
mprinke 0:e8ad47e9a9b4 151 fprintf(fp, "</trk>\n");
mprinke 0:e8ad47e9a9b4 152 return fprintf(fp, "</gpx>\n");
mprinke 0:e8ad47e9a9b4 153 }
mprinke 0:e8ad47e9a9b4 154
mprinke 0:e8ad47e9a9b4 155
mprinke 0:e8ad47e9a9b4 156 int main() {
mprinke 0:e8ad47e9a9b4 157 GPS_Time t;
mprinke 0:e8ad47e9a9b4 158 char filename[30];
mprinke 0:e8ad47e9a9b4 159 uint16_t fileno;
mprinke 0:e8ad47e9a9b4 160 FILE *fp = NULL;
mprinke 0:e8ad47e9a9b4 161 uint16_t log_wait = 0;
mprinke 0:e8ad47e9a9b4 162
mprinke 0:e8ad47e9a9b4 163 button.mode(PullUp);
mprinke 0:e8ad47e9a9b4 164 button.fall(&change_logging);
mprinke 0:e8ad47e9a9b4 165 gps.baud(GPS_BAUDRATE);
mprinke 0:e8ad47e9a9b4 166 lcd.cls();
mprinke 0:e8ad47e9a9b4 167
mprinke 0:e8ad47e9a9b4 168 while (1) {
mprinke 0:e8ad47e9a9b4 169 // Wait for the GPS NMEA data to become valid.
mprinke 0:e8ad47e9a9b4 170 // (Status flag in RMC message)
mprinke 0:e8ad47e9a9b4 171 while (!gps.isTimeValid()) {
mprinke 0:e8ad47e9a9b4 172 led_fix = !led_fix;
mprinke 0:e8ad47e9a9b4 173 lcd.locate(21, 0);
mprinke 0:e8ad47e9a9b4 174 lcd.printf("[no fix]");
mprinke 0:e8ad47e9a9b4 175 wait(1);
mprinke 0:e8ad47e9a9b4 176 }
mprinke 0:e8ad47e9a9b4 177
mprinke 0:e8ad47e9a9b4 178 gps.timeNow(&t);
mprinke 0:e8ad47e9a9b4 179 #if defined(DEBUG)
mprinke 0:e8ad47e9a9b4 180 pc.printf("The time/date is %02d:%02d:%02d %02d/%02d/%04d\r\n",
mprinke 0:e8ad47e9a9b4 181 t.hour, t.minute, t.second, t.day, t.month, t.year);
mprinke 0:e8ad47e9a9b4 182 #endif
mprinke 0:e8ad47e9a9b4 183 lcd.locate(0, 0);
mprinke 0:e8ad47e9a9b4 184 lcd.printf("%02d:%02d:%02d %02d/%02d/%04d %9s %10s",
mprinke 0:e8ad47e9a9b4 185 t.hour, t.minute, t.second, t.day, t.month, t.year,
mprinke 0:e8ad47e9a9b4 186 (gps.getGPSquality() > 0 ? "[fix] " : "[no fix]"),
mprinke 0:e8ad47e9a9b4 187 (g_logging ? "[logging]" : "[stopped]"));
mprinke 0:e8ad47e9a9b4 188
mprinke 0:e8ad47e9a9b4 189 // Check if at least four satellites produce a position fix and a valid quality.
mprinke 0:e8ad47e9a9b4 190 if (gps.numOfSats() < 4 && gps.getGPSquality() != 0) {
mprinke 0:e8ad47e9a9b4 191 // No fix or poor quality
mprinke 0:e8ad47e9a9b4 192 led_fix = !led_fix;
mprinke 0:e8ad47e9a9b4 193 } else {
mprinke 0:e8ad47e9a9b4 194 // Fix valid and good quality
mprinke 0:e8ad47e9a9b4 195 led_fix = 1;
mprinke 0:e8ad47e9a9b4 196 #if defined(DEBUG)
mprinke 0:e8ad47e9a9b4 197 pc.printf("Lat = %.4f Lon = %.4f Alt = %.1fkm\r\n",
mprinke 0:e8ad47e9a9b4 198 gps.latitude(), gps.longitude(), gps.altitude());
mprinke 0:e8ad47e9a9b4 199 #endif
mprinke 0:e8ad47e9a9b4 200 lcd.locate(0, 1);
mprinke 0:e8ad47e9a9b4 201 lcd.printf("Lat: %07.4f Lon: %08.4f Alt: %.0fm ",
mprinke 0:e8ad47e9a9b4 202 gps.latitude(), gps.longitude(), gps.altitude() * 1000.0);
mprinke 0:e8ad47e9a9b4 203
mprinke 0:e8ad47e9a9b4 204 if (g_logging) {
mprinke 0:e8ad47e9a9b4 205 if (log_wait-- == 0) {
mprinke 0:e8ad47e9a9b4 206 log_wait = LOGGING_CYCLE_SEC - 1;
mprinke 0:e8ad47e9a9b4 207 if (write_gpx_trkpt(fp, t) < 0) {
mprinke 0:e8ad47e9a9b4 208 // write error
mprinke 0:e8ad47e9a9b4 209 fclose(fp);
mprinke 0:e8ad47e9a9b4 210 g_logging = false;
mprinke 0:e8ad47e9a9b4 211 led_log = 0;
mprinke 0:e8ad47e9a9b4 212 }
mprinke 0:e8ad47e9a9b4 213 }
mprinke 0:e8ad47e9a9b4 214 }
mprinke 0:e8ad47e9a9b4 215 }
mprinke 0:e8ad47e9a9b4 216 wait(1);
mprinke 0:e8ad47e9a9b4 217
mprinke 0:e8ad47e9a9b4 218 // Logging state changed
mprinke 0:e8ad47e9a9b4 219 if (g_changed) {
mprinke 0:e8ad47e9a9b4 220 if (!g_logging) {
mprinke 0:e8ad47e9a9b4 221 // Start logging
mprinke 0:e8ad47e9a9b4 222 if (get_filename(filename, &fileno) != 0) {
mprinke 0:e8ad47e9a9b4 223 error("Could not create file with unique name\n");
mprinke 0:e8ad47e9a9b4 224 } else {
mprinke 0:e8ad47e9a9b4 225 // open file for writing
mprinke 0:e8ad47e9a9b4 226 fp = fopen(filename, "w");
mprinke 0:e8ad47e9a9b4 227 if (fp == NULL) {
mprinke 0:e8ad47e9a9b4 228 error("Could not open file for write\n");
mprinke 0:e8ad47e9a9b4 229 } else {
mprinke 0:e8ad47e9a9b4 230 if (write_gpx_header(fp, t, fileno)) {
mprinke 0:e8ad47e9a9b4 231 g_logging = true;
mprinke 0:e8ad47e9a9b4 232 led_log = 1;
mprinke 0:e8ad47e9a9b4 233 }
mprinke 0:e8ad47e9a9b4 234 }
mprinke 0:e8ad47e9a9b4 235 }
mprinke 0:e8ad47e9a9b4 236 } else {
mprinke 0:e8ad47e9a9b4 237 // Stop logging
mprinke 0:e8ad47e9a9b4 238 write_gpx_footer(fp);
mprinke 0:e8ad47e9a9b4 239 // close file
mprinke 0:e8ad47e9a9b4 240 fclose(fp);
mprinke 0:e8ad47e9a9b4 241 g_logging = false;
mprinke 0:e8ad47e9a9b4 242 led_log = 0;
mprinke 0:e8ad47e9a9b4 243 }
mprinke 0:e8ad47e9a9b4 244 g_changed = false;
mprinke 0:e8ad47e9a9b4 245 } // if (changed)
mprinke 0:e8ad47e9a9b4 246
mprinke 0:e8ad47e9a9b4 247 } // while (1)
mprinke 0:e8ad47e9a9b4 248 }