GPS Logger

After I was able to use FatFs with SD cards, I made a GPS logger. For this project you can use either the ATmega32L or the ATmega644, or you can figure out a way to port the code over. I suggest you stick with the ATmega644, or 644P, it makes this entire project compatible with the “Sanguino”.

GPS Logger Source Code

GPS Logger Road Trip Data

Relevent Links:

Hardware:

The GPS Module:

The GPS I used module is an EM-406A module, you can substitute this with your own module as long as:

The EM-406A is really supposed to run at 4.5-5.5V, but supplying it 3.3V works by some sort of black magic.

There's a baud rate setting in “gps.h” you can change to suit your own module (make sure the UART can use the baud rate while running on a 8 MHz clock, if not, use your own clock source). The UBBR register is set automatically by my code according to what frequency and baud rate is specified. If the frequency is not a good match for the baud rate, the code will not run at all.

Power Supply:

This is a GPS logger, it's not meant to have an external power supply, it's meant to run on batteries. Go buy a lithium ion battery and charger, each one is 3.7V, which is fine. Make sure your GPS module can run on that voltage, if it can't, you'll need to either use two batteries in series, or step-up the voltage using a DC-DC converter. Make sure you don't exceed limits.

Circuitry:

The circuit is very simple. Just connect as labeled. Remember that the GPS connector in the diagram represents a EM-406A module, you may need to connect your own differently. Also remember what I said about the programmer voltage before.


Circuit Diagram

Software:

GPS Logger Source Code

Compiling the Code:

You must have WinAVR and AVR Studio installed. Compile the project in AVR Studio, make sure to specify your AVR chip in the configuration, and make sure the clock speed is 8000000 Hz. Also make sure optimization is set at -Os.

Look inside “gpslogger.h” and check that everything matches. Finally, look inside “config.h” and change the settings as desired if you are using an ATmega644. Remember that if you use the ATmega32L, you cannot have SerialDebug or USE_KML enabled.

Overview of Program Flow:

First comes the initialization stuff, enabling the watchdog timer, enabling interrupts, setting up the timer, setting up the serial port, etc. Then the disk is initialized, if the disk isn't there, it keeps retrying since there's nothing better to do without a working and formatted SD card.

After the disk is found and the filesystem is opened, it tried to find the configuration file, if the file is not there, then the file is created with default values. It then reads the configuration file to know how often to make a log, and what time zone you are in.

Once it reaches the end of the configuration file. It sends the GPS module some commands (discussed later in detail), and waits for a valid location fix. Once the fix has been obtained, it then looks for the log files, or create them if they are not there. Skipping to the end of the files, it will make a note that a new recording has begun on what day and at what time. Then the GPS module is read and log entries are entered repeatedly once every interval as specified in the configuration file (the delay utilizes the fact that two NMEA sentences are sent per second). After every entry, the file is synchronized and then truncated, meaning the file is actually written and ends after the most recent entry.

If the SD card is somehow corrupted, the program will freeze and the watchdog will reset the logger, thus reinitializing the card.

There are several features planned for later, such as a switch that pauses disk activity that allows the user to safely remove the SD card. You can see that such a feature is already written, but the hardware IO is not.

Actually Using FatFs:

My SD card + FAT Tutorial (must read before reading this)

Call the disk initialization and open the file system first.

Call f_open to open a file, giving the file a handle struct, specifying its file path, specify that you want to read or write or both (and other things such as create if missing).

f_printf in FatFs works the same as fprintf from stdio.h, you use it to write to your file.

f_read return a number of characters into an array (a number which you specify), and also return how many characters were actually read (so if it doesn't match, you know you've reached the end of the file).

More details are on the FatFs website.

Reading the Configuration File:

When reading the configuration file, one word is read at a time, each word is separated by a space or a new line. These words are stored as a struct called StringStruct by a function called f_readWord. f_readWord records into the StringStruct one character at a time from a file until it reaches a space or a new line or the end of the file.

StringStruct is a struct which is used as a fixed length string in my code, containing a fixed length array of bytes and the length of the actual string, it also contains a end-of-file flag. I made this because strings in C, with the pointers and stuff, is annoying as hell for me to work with since I've been around higher level languages before.

The first word indicated what the next word contains, so if the first word is “delay” then the next word is a number indicating the delay in seconds between each log entry. the 2nd word is converted from ASCII to a integer and stored as a configuration. This goes on until the entire file is read (or until the keyword “end”).

GPS and NMEA:

Most GPS modules output and take in NMEA sentences. The output sentences are automatically sent every one second by default. These sentences have a specific structure and they also use ASCII. Each sentence begins with a “$” symbol, then a 5 letter word indicating what the sentence will contain (in the format GPXXX where XXX is the identifier), then a comma. After that, data is sent separated by commas except for the last data item which ends with a “*”. Following the “*” are two characters representing the checksum value of the sentence is hexadecimal (letters are capitalized). Then a \r\n sequence is sent which ends the sentence. The checksum is equal to 0 XOR-ed with the binary value of every character sent in the sentence excluding the “$”, “*”, the checksum itself, and the \r\n sequence.

The data we are interested in are:

All the GPS data we need is stored in a struct called GPSData.

There are several types of sentences the EM-406A can send, but since we only need those data items, we only need two types of sentences, GGA (which isn't an acronym for anything, but is called GPS fixed data) and RMC (recommended minimum specific GNSS data). The other sentences are disabled by sending NMEA set rate commands, setting their output rate to 0. This is done so we don't need a massive serial port buffer to store useless data.

Take a look at “gps.c”, you can see that the GPSInit calls NMEASetRate to disable the sentences which are not wanted. NMEASetRate uses fprintf to print out the formatted string out to the gpsStdout stream. The stream uses gps_putchar to output characters from the AVR to the GPS module via UART. Look at gps_putchar and you can see that checksum calculation is reset when a “$” symbol is sent, and the hexadecimal conversion happens when a “*” symbol is sent, the \r\n sequence is also automatically sent.

To understand what NMEASetRate is doing, take a look at the NMEA Reference Manual (page 23).

Now take a look at the GPSRead function. First thing it does is wait for the “$GP” sequence since all sentences starts with it. Then the message type identifier is read into a struct called a NMEAWord by a function called NMEAReadWord. If the sentence is identified as a GGA or RMC sentence, then data is extracted by reading more NMEAWords, parsed accordingly and stored in GPSData.

The NMEAWord struct is basically a fixed length string as an array of chars, with a length and end-of-sentence attribute contained with the string. The NMEAWord is stored by NMEAReadWord, which reads characters from the serial port one by one, and stopping at commas which indicate the end of a word., or “*” which would indicate the end of a sentence.

The NMEAParse_(fill in the blank)_ functions uses simple techniques to turn the ASCII data into integers or double precision numbers, they are pretty self-explanatory (ASCII to binary conversion by subtracting ASCII “0”, then multiply by power of 10, or use the stdlib's string to double function), when you look at them. Read the NMEA Reference Manual about the GGA and RMC sentences, also note that the length of the time, date, longitude and latitude data have a constant width, while speed, altitude, and heading do not.

GPSTimeProc simply changes the time read from the module to your local time based on your time zone (which is read from a configuration file on the SD card), it then accounts for whether it needs to increment or decrement the day, month, and year. It also knows how to handle leap years.

KML and CSV format:

KML format is a XML format used by Google Earth, although the logger doesn't make the KML file completely, it records in a format that you can copy and paste into an existing KML file.

The path.txt file contains the data you can paste into a KML path file. Each entry is simply the longitude, latitude, and altitude separated by commas. Each new entry is separated by a space and/or new line. This appears as a path in Google Earth,.

If KML is used, the point.txt and path.txt file contains all the GPS data inside a XML structure, each log entry appears as an icon in Google Earth, which you can click to see when that entry was taken, and the data associated with that entry.

The data is also saved in CSV format, the data is simply separated by commas, and new entries are separated by a new line. Note that you must actually copy and paste the data from the .txt file into another .csv file without the new log indicator so you can view the data in a spreadsheet program. You can also process this file yourself into other formats if you know how.

Other Stuff Not Related to GPS Logger

Serial Port:

“ser.c” and “ser.h” handles serial port functions. Received data is placed into a FIFO circular buffer array. This means data is not shifted in the array as they are written or read, but two pointers which indicated where the next read and write locations are changed. Transmitting data is simple, wait for the previous transmission to finish, then start the next one. The comments in the file explains how everything works.

“print.h” links the serStdio stream to the serial port, this makes it possible to use the functions found in stdio.h to output formatted strings using the serial port. If you send a \n character, a \r is automatically added before the \n, making valid \r\n sequences.