Skip to content

GPS Tracker and Data Logger Tutorial

This project combines our GPS TinyShield with our Flash Memory TinyShield to create a tiny GPS tracking and data logging device. This tutorial is for any skill level - no coding, programming, or soldering required! Just follow the steps below and you can have your device working in minutes.

If you would prefer to use an Micro SD Card TinyShield to store NMEA string data, the GPS TinyShield getting started tutorial program offers the option to do so.


Materials

Hardware materials

Hardware

Software


Hardware Assembly

Start with the processor of choice on the bottom of the stack. Add the Flash Memory TinyShield. The GPS TinyShield goes on top. (If you are using a TinyDuino, add the USB TinyShield in the middle of the stack so the GPS can still be on top)

Assembled TinyDuino stack

With your Mounting Kit, add spacers between the boards on the side opposite the connector. This will make sure your stack stays rigid and prevent your connectors from coming apart if pressure is placed on that side. If you're having difficulty placing them with bare fingers, I recommend a pair of tweezers to make the job easier.

Three spacers per side, with two screws and bolts.

Drop the screw through the holes and placed spacers and screw in the bolt on the other side to seal the deal. Finger tightening will be just fine to hold everything in place.

Plug in your Lithium Battery and your assembly is finished!


Software setup

To install the most recent version of the SPIFlash library, open the Arduino IDE. Then, open your Library Manager (under the 'Sketch' tab) and search for 'SPIMemory' by Prajwal Bhattaram. Click 'Install' to install the library to your IDE, as shown below.


Upload Program

Opening the .zip file downloaded from GitHub.

Download the .zip file containing the sketch above under "Software". Save the 'GPS_Tracker_v2' sketch folder as pictured above to any destination you like. Double click on 'GPS_Tracker_v2.ino' to open the IDE. You can also just copy the code below:

Tiny GPS Tracker and Data Logger Arduino Sketch
//-------------------------------------------------------------------------------
//  TinyCircuits GPS Tracker Tutorial Program
//  Last updated 1 July 2020 (2.00)
//  
//  Using the GPS TinyShield, the Flash Memory TinyShield, and the TinyDuino/TinyZero,
//  this program turns the stack into a miniature GPS tracker and data logger.
//  The code detects which sentence is being read and formats the string accordingly.
//  In order to reduce the number of writes, we write one NMEA sentence per 10 seconds, 
//  which can be modified.
//
//  With the Telit SE868 V2 module with Glonass support, some messages come through
//  as GN** sentences instead of GP**. These are changed back to GP** before logging
//  so that they don't cause problems with programs like Google Earth.
//  Some GPS modules have been shipped with 4800 baud instead of 9600 (in 2017)- try 
//  this if you see bad data.
//
//  Written by Ben Rose & Lilith Freed for TinyCircuits, http://TinyCircuits.com
//  Updated July 1, 2020 to add SAMD21 support by Laveréna Wienclaw
//
//-------------------------------------------------------------------------------

//This may need to be set to 4800 baud
const int GPSBaud = 9600;

#include <SPIFlash.h>

#if defined (ARDUINO_ARCH_AVR)
#define SerialMonitorInterface Serial
#include <SoftwareSerial.h>
#elif defined(ARDUINO_ARCH_SAMD)
#define SerialMonitorInterface SerialUSB
#include "SoftwareSerialZero.h"
#endif

// The chip/slave select pin is pin 5 for the Flash Memory TinyShield
const uint8_t flashCS = 5; 
unsigned long address = 0;

// The SPIFlash object for the chip. Passed the chip select pin in the constructor.
SPIFlash flash(flashCS); 

// The Arduino pins used by the GPS module
const uint8_t GPS_ONOFFPin = A3;
const uint8_t GPS_SYSONPin = A2;
const uint8_t GPS_RXPin = A1;
const uint8_t GPS_TXPin = A0;
const uint8_t chipSelect = 10;

// The GPS connection is attached with a software serial port
SoftwareSerial Gps_Serial(GPS_RXPin, GPS_TXPin);

// Set which sentences should be enabled on the GPS module
// GPGGA - 
char nmea[] = {'1'/*GPGGA*/, '0'/*GNGLL*/, '0'/*GNGSA*/, '0'/*GPGSV/GLGSV*/, '1'/*GNRMC*/, '0'/*GNVTG*/, '0'/*not supported*/, '0'/*GNGNS*/};

void setup()
{
  Gps_Serial.begin(GPSBaud);
  SerialMonitorInterface.begin(115200);
  if(!SerialMonitorInterface) { delay(5000);}; // Pauses 5 seconds if Serial Monitor is not opened

  SerialMonitorInterface.println("Initializing Flash Memory...");
  SerialMonitorInterface.println();
  pinMode(flashCS, OUTPUT); // Ensure chip select pin is an output.
  flash.begin(); // Boots the flash memory

  SerialMonitorInterface.println("Determining write/read start point...");
  uint8_t flashBuffer[256];
  uint8_t foundStart = false;
  while (!foundStart) {
    flash.readByteArray(address, flashBuffer, 256);
    int i;
    for (i = 0; !foundStart && i < 256; i++) {
      if (flashBuffer[i] == 0xFF) // checks for the first logical 1
        foundStart = true;
    }
    address += i;
  }
  address--;
  SerialMonitorInterface.println("Done.");
  SerialMonitorInterface.println();

  unsigned long timer = millis();
  SerialMonitorInterface.println("Send 'y' to start read mode. Write mode will begin in 10 seconds...");
  SerialMonitorInterface.println();
  while(millis() < timer + 10000) {
    if(SerialMonitorInterface.available()) {
      if(SerialMonitorInterface.read() == 'y') {
        readFlash(address);
      }
    }
  }

  SerialMonitorInterface.println("Now initiating write mode.");
  SerialMonitorInterface.println();

  // Init the GPS Module to wake mode
  pinMode(GPS_SYSONPin, INPUT);
  digitalWrite(GPS_ONOFFPin, LOW);
  pinMode(GPS_ONOFFPin, OUTPUT);
  delay(100);
  SerialMonitorInterface.print("Attempting to wake GPS module.. ");
  while (digitalRead( GPS_SYSONPin ) == LOW )
  {
    // Need to wake the module
    digitalWrite( GPS_ONOFFPin, HIGH );
    delay(5);
    digitalWrite( GPS_ONOFFPin, LOW );
    delay(100);
  }
  SerialMonitorInterface.println("done.");
  delay(100);

  char command[] = "$PSRF103,00,00,00,01*xx\r\n";
  for (int i = 0; i < 8; i++) {
    command[10] = i + '0';
    command[16] = nmea[i];
    int c = 1;
    byte checksum = command[c++];
    while (command[c] != '*')
      checksum ^= command[c++];
    command[c + 1] = (checksum >> 4) + (((checksum >> 4) < 10) ? '0' : ('A' - 10));
    command[c + 2] = (checksum & 0xF) + (((checksum & 0xF) < 10) ? '0' : ('A' - 10));
    Gps_Serial.print(command);
    delay(20);
  }

  SerialMonitorInterface.println();

}

void loop() {
  unsigned long startTime = millis();
  while (Gps_Serial.read() != '$') {
    //do other stuff here
  }
  while (Gps_Serial.available() < 5);
  Gps_Serial.read(); 
  Gps_Serial.read(); //skip two characters
  char c = Gps_Serial.read();
  //determine senetence type
  if (c == 'R' || c == 'G') {
    c = Gps_Serial.read();
    if (c == 'M') {
      logNMEA(1);
    } else if (c == 'G') {
      logNMEA(2);
    }
  }

  // Waits 10 seconds before reading next NMEA string
  while (millis() - startTime < 10000) {
    Gps_Serial.read(); // clears GPS serial buffer
  }
}

void logNMEA(uint8_t type) {
  uint8_t buffer[82];
  // Initializes buffer to null terminators to ensure proper writing to flash memory
  for(int i = 0; i < 82; ++i) {
    buffer[i] = '\0';
  }

    // Writes NMEA string to buffer
  buffer[0] = '$';
  int counter = 1;
  char c = 0;
  while (!Gps_Serial.available());
  c = Gps_Serial.read();
  while (c != '*') {
    buffer[counter++] = c;
    while (!Gps_Serial.available());
    c = Gps_Serial.read();
  }
  buffer[counter++] = c;
  while (!Gps_Serial.available());
  c = Gps_Serial.read();
  buffer[counter++] = c;
  while (!Gps_Serial.available());
  c = Gps_Serial.read();
  buffer[counter++] = c;
  buffer[counter++] = '\r';
  buffer[counter++] = '\n';

  buffer[2] = 'P'; // Changes GNRMC to GPRMC

  c = 1;
  byte checksum = buffer[c++];
  while (buffer[c] != '*')
    checksum ^= buffer[c++];
  buffer[c + 1] = (checksum >> 4) + (((checksum >> 4) < 10) ? '0' : ('A' - 10));
  buffer[c + 2] = (checksum & 0xF) + (((checksum & 0xF) < 10) ? '0' : ('A' - 10));

  // Writes buffer array to flash memory. Write length is variable to maximize memory.
  flash.writeCharArray(address, (char *)buffer, strlen((char *)buffer));
  address += strlen((char *)buffer); // Sets address ahead for length of the nmea string
}

void readFlash(unsigned long address) {
  char command;
  do {
    // Clear Serial write buffer to prepare for read
    while(SerialMonitorInterface.available()) {
      SerialMonitorInterface.read();
    }
    // Menu
    SerialMonitorInterface.println("Read Mode | Please select a command (1, 2, 3):");
    SerialMonitorInterface.println("1. Read to serial monitor.");
    SerialMonitorInterface.println("2. Erase all data.");
    SerialMonitorInterface.println("3. Exit read mode.");
    while(!SerialMonitorInterface.available());
    command = SerialMonitorInterface.read();
    // Command select
    switch(command) {
      case '1':
        // Handles empty flash
        if(address == 0) {
          SerialMonitorInterface.println("No available data.");
          SerialMonitorInterface.println();
        } else {
          // Reads all available data to the Serial monitor
          for(unsigned long i = 0; i < address; ++i) {
            SerialMonitorInterface.print((char)flash.readChar(i));
          }
          SerialMonitorInterface.println();
        }
        break;
      case '2':
        // Erases data up until the first available byte.
        eraseData(address);
        address = 0; // resets the first available byte address to 0
        SerialMonitorInterface.println("All data erased.");
        SerialMonitorInterface.println();
        break;
      case '3':
        // Exits read mode.
        return;
      default:
        // Passes by invalid input.
        SerialMonitorInterface.println("That is not a recognized command.");
        SerialMonitorInterface.println();
        break;  
    }
  } while(command != 3);
}

// Erases the flash memory in 4KB sectors.
// Minimizes excess erase "writes".
void eraseData(unsigned long address) {
  unsigned long index = 0;
  while(index < address) {
    flash.eraseSector(index);
    index += 4096;
  }
}

Ensure that the connection to your TinyDuino is configured properly, turn on your TinyDuino, and hit upload. Open the Serial Monitor to ensure that your device is outputting properly.

What the Serial Monitor should display after upload is complete.


Transporting Your Device

For optimal GPS data readings, the sensor at the end of the antenna on top of the stack should be parallel with the ground. (Note that the coiling or bending of the antenna wire will not affect your readings.) This is best achieved by carrying the stack upright in some sort of containment. Pictured above is a potential setup that I took for a test drive - I have the stack upright in an anti-static plastic bag, which could be pinned to a backpack strap or the shoulder of your coat. You can accomplish this same effect in a number of creative ways. Maybe there's a tiny box that can accompany you on your journey!


Device Operation

When you power on your device, it will take ten seconds for the GPS module to wake and begin configuring. It usually takes a few minutes for the module to precisely determine your position, so it is recommended that you stay still with the device for a minute or two to obtain the best accuracy. Note that factors such as cloud cover, large buildings, and large land or rock masses in close proximity can affect GPS readings.

While the Summit Metroparks Gorge Trail is beautiful, the large rock structures did have an effect on my GPS data.

The sketch for the device currently specifies that a data point is taken every ten seconds. If you wish, you can change the code as shown below to adjust that.

The value I am adjusting is the number of seconds in milliseconds.

To check if your device still has power, watch to see if the LED labeled 'P13' on your TinyDuino is blinking every ten seconds (or whatever delay you set it for). This signals that the device is writing to your Flash Memory TinyShield. If you're concerned about your device losing power on long trips, you can use a standard 1 Amp charging block or battery with a micro-USB cable and connect it to the USB TinyShield to recharge the lithium battery or power the device altogether.

Before powering down your device, make sure you've left enough time for your TinyDuino to write your last data point to memory!


Reading the Data

When you first opened the Serial Monitor to verify the operation of your TinyDuino, you saw the following dialog pop up:

The dialog that appears upon turning your device on.

We will now interact with this dialog to retrieve the data from your Flash Memory TinyShield. Send 'y' as shown below to begin read mode:

You can also hit the "enter" key to accomplish this.

We will read the data from our device by sending '1'.

The data displayed here is for example only - your data will look much different.

We can copy and paste the block of strings into Notepad or another plain text editor like so:

Use the shortcut CTRL + C or CMD + C to copy this data.

Save that file as a .txt file. It's ready for the next step! As for your device, if you wish to erase that trip's data from its memory, send '2' to clear all data:

Make sure you've saved all your data before taking this action!

Now we get to the best part - visualizing your GPS data!


Converting to Google Maps

For this task, we will be using the software on GPS-Visualizer.com. Huge thanks to their team for keeping this software free of charge, and for making the mapping process simple. (If you want to help keep the software free forever, you can donate here.)

The NMEA data you pasted into your .txt file needs to correctly be formatted before using the GPSVisualizer software to Map your location. To do this, you just need to include a header line at the top of the .txt file that tells the website how to read the data. Here's an example of what your header should look like:

GPS position,timestamp,latitude,north,longitude,west,quality,satellites,HDOP,altitude,meters,geoidal separation,meters,correction station ID&checksum

You should be able to insert the above header directly into your .txt file as the first line in order to get a map for your GPS data.

Now that the file is correctly formatted, go to the home page of GPSVisualizer, use the 'Choose File' button in the 'Get started now!' box to select the .txt file that you saved earlier. You can select any output format from the drop-down menu, but I recommend Google Maps, as it is the most dynamic and informative format. Click "Map it" and watch the magic happen!

Yes, it is that easy. There are some advanced settings that you can use to smooth out your data points that they cover in a tutorial here on their site. Otherwise, you can save your results for later, and even share them on social media!


Contact Us

If you have any questions or feedback, feel free to email us or make a post on our forum. Show us what you make by tagging @TinyCircuits on Instagram, Twitter, or Facebook so we can feature it.

Thanks for making with us!