Currently, almost all of us use the GPS module in our smartphone. Whether for navigation, saving traveled path, entertainment such as geocaching or in many other situations. In most cases, smartphone is the best option, but sometimes we may need a dedicated solution to save our routes, e.g. during a several-day trip without access to electricity or simply when the battery in our phone is low.
On the market we can find ready for use GPS loggers but choise is not too wide and atractive probably becous nowdays most of us use a mobile phone. That is why I decided to create a GPS logger that will be inexpensive and will withstand prolonged work time and have easy battery replacement with big memory capacity. Such logger could be used on a very long journey or other expedition.
In order to build a GPS logger, we need:
- Atmeg 328 P-PU microcontroller
- Micro sd card reader
- NEO-6M GPS
- LM7805CV
- 2x 18650 batteries
- Pushbutton
- Switch
- 16 MHz quartz crystal resonator
- Capacitor 2 × 22 nf
- 1-10k resistor
- Diode
The heart of the system is the NEO-6M GPS module responsible for receiving GPS signal. Data downloaded via GPS is processed and saved to the micro SD card. The whole system is powered by two 18650 batteries. Their total load when fully charged is 7.6 V. All modules in operation cannot work with loads higher than 5 V, which is why we use the LM7805CV voltage regulator. To stop reading data from GPS and finish the last recording, push button is instaled. Included diode allows us to easily observe the work of the system. The whole is managed by the Atmega 328 P-PU microcontroller. The electrical diagram and diagram showing how to connect everything to Arduino UNO for test purpose are included below:
Connecting the Neo-6M GPS consists of connecting 5V, grounding and two pins for communication TX (connected to pin 6) and RX (connected to pin 11). The memory card reader allows us to save the current latitude and longitude along with altitudes, as well as provides information about the current date and time. SD reader also requires 5V power supply and grounding. Communication with the microcontroller is available using MOSI pins (connected to pin 17), MISO (connected to pin 18), SCK (connected to pin 19), CS (used to pin 16) using the popular SPI protocol. The SPI protocol allows two devices to communicate with each other. In this protocol, the two components operate in a master-slave relationship. MOSI Is responsible for sending information from the master device to the slave device. MISO sends information from the slave device to the master device. SCK is a clock that allows data synchronization. CS allows us to select a slave device. The toggle button is connected to the 5 Atmegi pin with use of pull-down resistor. When we press this button signal is sent to atmega and microcontroller is calling interrupt function and stops GPS data logging. The diode connected to pin 14 covers us about the operation of the entire system.
The code written for this system is as follows:
#include <BlockDriver.h>
#include <FreeStack.h>
#include <MinimumSerial.h>
#include <SdFat.h>
#include <SdFatConfig.h>
#include <sdios.h>
#include <SysCall.h>
#pragma execution_character_set("utf-8")
#include <TinyGPS++.h>
#include <SoftwareSerial.h>
#include <SPI.h>
int RXPin = 5;
int TXPin = 4;
int GPSBaud = 9600;
TinyGPSPlus gps;
SoftwareSerial gpsSerial(RXPin, TXPin);
int CS_PIN = 10;
SdFile file;
SdFat sd;
char rememberFileName[20];
bool firstStart = true;
volatile bool saveBeforeOff = false;
unsigned long lastToggle;
long times = 1;
void setup()
{
Serial.begin(9600);
gpsSerial.begin(GPSBaud);
pinMode(7, OUTPUT);
initializeSD();
lastToggle = millis();
digitalWrite(7, HIGH);
attachInterrupt(0, saveBeforeOffFunction, RISING);
}
void loop()
{
while (gpsSerial.available() > 0){
if (gps.encode(gpsSerial.read())){
if (millis() - lastToggle > 500) {
writePoint();
}
}
}
if (millis() > 5000 && gps.charsProcessed() < 10)
{
while(true);
}
}
void saveBeforeOffFunction(){
saveBeforeOff = true;
}
void writePoint(){
digitalWrite(7, HIGH);
if (!gps.location.isValid() || !gps.date.isValid() || !gps.time.isValid()){
return;
}
if(firstStart){
char output[16];
itoa(gps.date.year(), output, 10);
rememberFileName[0] = output[0];
rememberFileName[1] = output[1];
rememberFileName[2] = output[2];
rememberFileName[3] = output[3];
itoa(gps.date.month(), output, 10);
if(gps.date.month() < 10){
rememberFileName[4] = '0';
rememberFileName[5] = output[0];
}
else{
rememberFileName[4] = output[0];
rememberFileName[5] = output[1];
}
itoa(gps.date.day(), output, 10);
if(gps.date.day() < 10){
rememberFileName[6] = '0';
rememberFileName[7] = output[0];
}
else{
rememberFileName[6] = output[0];
rememberFileName[7] = output[1];
}
rememberFileName[8] = '_';
itoa(gps.time.hour(), output, 10);
if(gps.time.hour() < 10){
rememberFileName[9] = '0';
rememberFileName[10] = output[0];
}
else{
rememberFileName[9] = output[0];
rememberFileName[10] = output[1];
}
itoa(gps.time.minute(), output, 10);
if(gps.time.minute() < 10){
rememberFileName[11] = '0';
rememberFileName[12] = output[0];
}
else{
rememberFileName[11] = output[0];
rememberFileName[12] = output[1];
}
itoa(gps.time.second(), output, 10);
if(gps.time.second() < 10){
rememberFileName[13] = '0';
rememberFileName[14] = output[0];
}
else{
rememberFileName[13] = output[0];
rememberFileName[14] = output[1];
}
rememberFileName[15] = '.';
rememberFileName[16] = 't';
rememberFileName[17] = 'x';
rememberFileName[18] = 't';
rememberFileName[19] = '\0';
createFile(rememberFileName);
openFile(rememberFileName);
firstStart=false;
}
writeNewLine();
writeToFile("<trkpt lat=\"");
doubleValueWrite(gps.location.lat());
writeToFile("\" lon=\"");
doubleValueWrite(gps.location.lng());
writeToFile("\">");
writeNewLine();
writeToFile("<ele>");
if(gps.altitude.isValid()){
doubleValueWrite(gps.altitude.meters());
}
else{
writeToFile("0");
}
writeToFile("/<ele>");
writeNewLine();
writeToFile("<time>");
intValueWrite(gps.date.year());
writeToFile("-");
if (gps.date.month() < 10)
writeToFile("0");
intValueWrite(gps.date.month());
writeToFile("-");
if (gps.date.day() < 10)
writeToFile("0");
intValueWrite(gps.date.day());
writeToFile("T");
if (gps.time.hour() < 10)
writeToFile("0");
intValueWrite(gps.time.hour());
writeToFile(":");
if (gps.time.minute() < 10)
writeToFile("0");
intValueWrite(gps.time.minute());
writeToFile(":");
if (gps.time.second() < 10)
writeToFile("0");
intValueWrite(gps.time.second());
writeToFile("Z");
writeToFile("</time>");
writeNewLine();
writeToFile("</trkpt>");
times++;
if(times%1000==0){
closeFile();
openFile(rememberFileName);
}
digitalWrite(7, LOW);
lastToggle = millis();
if(saveBeforeOff){
writeNewLine();
writeToFile("</trkseg>");
writeNewLine();
writeToFile("</trk>");
writeNewLine();
writeToFile("</gpx>");
int blinkiLight = 0;
while(blinkiLight<25){
digitalWrite(7, HIGH);
delay(100);
digitalWrite(7, LOW);
delay(100);
blinkiLight++;
}
digitalWrite(7, LOW);
while(true);
}
}
void doubleValueWrite(double input){
char output[10];
dtostrf(input, 0, 8, output);
writeToFile(output);
}
void intValueWrite(int input){
char output[16];
itoa(input, output, 10);
writeToFile(output);
}
void initializeSD()
{
digitalWrite(7, HIGH);
delay(1000);
pinMode(CS_PIN, OUTPUT);
sd.begin();
digitalWrite(7, LOW);
delay(1000);
}
int writeToFile(char text[])
{
file.print(text);
file.flush();
}
int writeNewLine()
{
file.println("");
file.flush();
}
void closeFile()
{
file.close();
}
void openFile(char filename[])
{
file.open(filename, FILE_WRITE);
}
void createFile(char filename[])
{
file.open(filename, FILE_WRITE);
char headerName[] = "header.txt";
SdFile headerFile;
headerFile.open(headerName, FILE_READ);
int data;
while ((data = headerFile.read()) >= 0) {
file.write(data);
}
file.close();
headerFile.close();
}
We will need several libraries for this code to work properly. All the information we receive from the gps module is in the NMEA (National Marine Electronics Association) standard looking for example lke this $ GPGGA, 123519,4807.038, N, 01131.000, E, 1.08,0.9,545.4, M, 46.9, M ,, * 47 . To make it readable for us we use the TinyGps library which can be downloaded from httpss://github.com/mikalhart/TinyGPSPlus/archive/master.zip
Another library we need is SDFat available under httpss://github.com/greiman/SdFat We use it in the project because the default library that works with sd cards only allows us to save files whose name does not exceed 8 characters. In the project, files are saved as the date when the data logging started, which requires more than 8 characters.
At the very beginning, we initialize all objects in the setup () section. Later in the loop() we check the data from GPS. If we are able to download the data from the GPS, at a interval of 0.5 s, we will save the downloaded location.
The writePoint() method at the very beginning lights up the diode, signaling the beginning of writing. We check if we can read all information from GPS. If this is the first time we start the device, we create a text file with the name corresponding to the current date and time. The information is saved in the gpx standard. Therefore, for each newly created file, we copy the following information to it as a header (createFile function):
<?xml version="1.0" encoding="UTF-8"?>
<gpx version="1.1" creator="Sariel" xsi:schemaLocation="https://www.topografix.com/GPX/1/1 https://www.topografix.com/GPX/1/1/gpx.xsd https://www.garmin.com/xmlschemas/GpxExtensions/v3 https://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd https://www.garmin.com/xmlschemas/TrackPointExtension/v1 https://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd" xmlns="https://www.topografix.com/GPX/1/1" xmlns:gpxtpx="https://www.garmin.com/xmlschemas/TrackPointExtension/v1" xmlns:gpxx="https://www.garmin.com/xmlschemas/GpxExtensions/v3" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance">
<metadata>
<author>
<name>Sariel</name>
</author>
<link href="https://binaryalchemist.pl">
<text>Binary Alchemist</text>
</link>
</metadata>
<trk>
<trkseg>
This information is in the header.txt file and this file should be on our sd card for the program to work properly.
After creating the file, we populate it with GPS data, i.e. longitude, latitude, altitude and current date, and save them to the created file on the sd card in the gpx format. A single entry may look like this:
<trkpt lat="58.34874300" lon="21.62502100">
<ele>0/<ele>
<time>2020-01-12T16:29:00Z</time>
</trkpt>
When we want to finish logging we click pushbutton. This immediately calls the saveBeforeOffFunction interrupt method. It changes the value of saveBeforeOff to true. As a result, the last GPS point is saved in the writePoint() function and the xml tag opened at the begining are closed.
</trkseg>
</trk>
</gpx>
The whole is visualized by flashing led several times. After that we can safely turn off the GPS logger.
When we have everything ready we have to transfer our components to prototype board and solder everything togother. In my case it looked like this. Unfortunately, the final dimensions of this device turned out to be relatively large. However, it could certainly be arranged more optimally and if we have more time and desire the best solution would be to prepare a printed circuit board.
The last thing is to place our device in a resistant container. We will probably find many matching plastic food containers or other not used boxes. However, if we have a 3d printer, we can create a container that will fit to the millimeter for our GPS logger.
All files needed to create a GPS Logger: