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

main.cpp

Committer:
mprinke
Date:
2013-02-17
Revision:
0:e8ad47e9a9b4

File content as of revision 0:e8ad47e9a9b4:

/* 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)
}