Built-in project · Arduino Mega 2560 R3

GPS logger on an Arduino Mega

A NEO-6M on the Mega’s second serial port and a microSD card: every position goes into TRACK.CSV, which you can watch grow and download. The whole build, an Arduino Mega and 2 more parts, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.

AdvancedRuns in your browser. Free, and no account needed.

GPS logger · Arduino Mega 2560 R3live0.000 s 0.00x
Hold the button
The circuit itself, running here on the simulator. Press what can be pressed; click anything else to open it in the editor.

The code

The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.

sketch.ino · Arduino Mega 2560 R3
// GPS logger: where the module is, once a second, written to a microSD card.
//
// The NEO-6M sends NMEA sentences on Serial1 (its TX to the Mega's RX1, pin
// 19). TinyGPS++ reads them a character at a time; each time a new position
// comes in, one line "time,latitude,longitude,km/h" is added to TRACK.CSV.
//
// The module is moving: its speed and course properties walk the position
// along a heading once a second. Change them and watch the numbers change.
// Click the GPS to take its fix away: the sentences keep coming, but empty,
// the position stops updating and its age() climbs, and nothing is logged
// until the fix is back.
//
// The track grows in the list beside the card. Click TRACK.CSV to download it;
// it opens in a spreadsheet or a mapping tool.
//
// The card is slow and the GPS does not wait. While the sketch is busy writing
// a sector, the bytes arriving on Serial1 pile up in a 64-byte buffer and the
// rest of the sentence is lost. So the lines are kept in memory and written
// to the card five at a time: four seconds in five nothing is missed.

const int LINES_PER_WRITE = 5;
const int LINE_LENGTH = 48;
char pending[LINES_PER_WRITE][LINE_LENGTH];
int pendingCount = 0;

#include <SPI.h>
#include <SD.h>
#include <TinyGPS++.h>

TinyGPSPlus gps;
const int CARD_CS = 53;

bool cardReady = false;
unsigned long lastWarning = 0;
uint32_t lastStamp = 0xFFFFFFFF;

void setup()
{
    Serial.begin(9600);
    Serial1.begin(9600);
    cardReady = SD.begin(CARD_CS);
    Serial.println(cardReady ? "Card ready." : "No card: positions go to the serial monitor only.");
    if (cardReady) {
        File file = SD.open("track.csv", FILE_WRITE);
        if (file) {
            if (file.size() == 0) {
                file.println("time,lat,lng,kmh");
            }
            file.close();
        }
    }
}

// A Print that writes into one of the pending lines.
class LineBuffer : public Print {
  public:
    explicit LineBuffer(char *line) : line_(line) { line_[0] = 0; }
    size_t write(uint8_t c) override
    {
        if (length_ + 1 >= LINE_LENGTH) {
            return 0;
        }
        line_[length_++] = (char)c;
        line_[length_] = 0;
        return 1;
    }

  private:
    char *line_;
    int length_ = 0;
};

void printTwo(Print &out, int value)
{
    if (value < 10) {
        out.print('0');
    }
    out.print(value);
}

void writeLine(Print &out)
{
    printTwo(out, gps.time.hour());
    out.print(':');
    printTwo(out, gps.time.minute());
    out.print(':');
    printTwo(out, gps.time.second());
    out.print(',');
    out.print(gps.location.lat(), 6);
    out.print(',');
    out.print(gps.location.lng(), 6);
    out.print(',');
    out.println(gps.speed.kmph(), 1);
}

void writePending()
{
    File file = SD.open("track.csv", FILE_WRITE);
    if (file) {
        for (int i = 0; i < pendingCount; i++) {
            file.print(pending[i]);
        }
        file.close();
    }
    pendingCount = 0;
}

void loop()
{
    while (Serial1.available() > 0) {
        gps.encode(Serial1.read());
    }
    if (!gps.location.isUpdated()) {
        // No new position. After three seconds of that, say so, once every
        // three seconds.
        if (gps.location.age() > 3000 && millis() - lastWarning > 3000) {
            Serial.println("No fix yet.");
            lastWarning = millis();
        }
        return;
    }
    // RMC and GGA both carry the position, so it is updated twice a second;
    // one line a second is enough, so the second one is skipped by its time.
    uint32_t stamp = gps.time.value();
    if (stamp == lastStamp) {
        gps.location.lat(); // read, so isUpdated() waits for the next one
        return;
    }
    lastStamp = stamp;
    LineBuffer line(pending[pendingCount]);
    writeLine(line);
    Serial.print(pending[pendingCount]);
    pendingCount++;
    if (pendingCount == LINES_PER_WRITE) {
        if (cardReady) {
            writePending();
        }
        pendingCount = 0;
    }
}

Parts list

4 parts, plus the jumper wires. Every one is in the editor's parts bin.

How it is wired

7 connections, pin by pin, read from the circuit itself. Each line is one set of pins joined together, by a jumper wire or a breadboard strip.

  • Arduino Mega 2560 R3 pin 19; GPS module, NEO-6M pin TX
  • Arduino Mega 2560 R3 pin 50; microSD card module pin MISO
  • Arduino Mega 2560 R3 pin 51; microSD card module pin MOSI
  • Arduino Mega 2560 R3 pin 52; microSD card module pin SCK
  • Arduino Mega 2560 R3 pin 53; microSD card module pin CS
  • Arduino Mega 2560 R3 pin 5V; GPS module, NEO-6M pin VCC; microSD card module pin VCC
  • Ground: Arduino Mega 2560 R3 pin GND; GPS module, NEO-6M pin GND; microSD card module pin GND

Change it and keep it

Open it in the editor, change the circuit or the code, and keep your version in a free account.