ESP32 GPS Data Logger EP1: Get GPS Data and Save It to SD Card

In this project, we are building an ESP32-based GPS Data Logger using the NEO-7M GPS module and a microSD card.

The goal is to capture GPS information such as latitude, longitude, speed, date, time, and satellite count, and save the data to an SD card so that it can be analyzed or visualized later.

This is EP1 of the GPS Data Logger series. In this episode, we focus on the core data-logging functionality:

  1. Getting GPS data from the NEO-7M.
  2. Parsing the GPS information using the ESP32.
  3. Connecting and initializing the SD card.
  4. Saving GPS data to files.
  5. Creating date/time-based filenames.
  6. Automatically creating a new file when the configured file-size limit is reached.

In the next episode, we will extend the project with a display and map visualization.

Project Overview

The basic architecture of the project is:

The NEO-7M receives signals from GPS satellites and provides the location information to the ESP32. The ESP32 processes this information and stores the useful data on the SD card. This approach makes the project useful for road trips, vehicle tracking, GPS experiments, and location-data logging.

Components Required

  • ESP32 Dev Board ( I used 38 pin board)
  • Neo-7M GPS module
  • SD Card reader module
  • Breadboard
  • Jumper wires
  • External Antenna (Required if working indoor)

Part 1 – Getting GPS Data

NEO-7M GPS Module

The NEO-7M is a commonly used GPS module that can provide information such as:

  • Latitude
  • Longitude
  • Speed
  • Date
  • Time
  • Number of satellites
  • GPS fix status
  • NMEA sentences

For this project, the NEO-7M communicates with the ESP32 using a serial interface.

GPS Wiring

The GPS module is connected to the ESP32 using a hardware serial port.

The connections used in this project are:

NEO-7M              ESP32 38-pin
--------------------------------
VCC       --------> 5V
GND       --------> GND
TX        --------> GPIO16
RX        --------> GPIO17
PPS       --------> Not connected

The GPS TX line sends data to the ESP32’s RX pin, while the ESP32’s TX line can be connected to the GPS RX pin for communication in the other direction.

Understanding GPS Data

When the GPS module is connected, it continuously sends NMEA sentences.

Examples include:

$GPGSA,...
$GPGSV,...
$GPRMC,...

You will initially receive data in following formats


$GPRMC,…,V,… and

$GPGGA,…,0,00,…

V = No valid position
0   = No GPS fix
00  = 0 satellites currently being used for navigation

Instead of manually parsing each NMEA sentence, the project uses the TinyGPS++ library to extract the information we need.

Waiting for a GPS Fix

When the GPS module is first powered on, it may not immediately have a valid location. The GPS module needs to receive enough satellite information to calculate its position.

Once a valid fix is obtained, the ESP32 will recording the location. The exact time required to obtain a GPS fix depends on factors such as the environment, antenna position, satellite visibility, and whether the module has previously obtained satellite information.

For testing, it is preferable to place the GPS antenna where it has a reasonably clear view of the sky.

Part 1 – Code

Following code is to get test GPS signals. GPS data will be displayed on serial monitor. For this project since we are using TinyGPS++ library, you should install it first.

#include <TinyGPS++.h>

TinyGPSPlus gps;

HardwareSerial gpsSerial(2);

#define GPS_RX 16
#define GPS_TX 17

void setup() {
    delay(2000);

  Serial.begin(115200);

  Serial.println();
  Serial.println("ESP32 GPS Test");
  Serial.println("----------------");

  gpsSerial.begin(
    9600,
    SERIAL_8N1,
    GPS_RX,
    GPS_TX
  );

  Serial.println("GPS Serial Started");
}

void loop() {

  while (gpsSerial.available()) {

    char c = gpsSerial.read();
    Serial.write(c);
    gps.encode(c);
    
  }
  if (gps.location.isUpdated()) {

    Serial.println("----------------");

    Serial.print("Latitude: ");
    Serial.println(gps.location.lat(), 6);

    Serial.print("Longitude: ");
    Serial.println(gps.location.lng(), 6);

    Serial.print("Speed (km/h): ");
    Serial.println(gps.speed.kmph());

    Serial.print("Altitude (m): ");
    Serial.println(gps.altitude.meters());

    Serial.print("Satellites: ");
    Serial.println(gps.satellites.value());

    Serial.print("HDOP: ");
    Serial.println(gps.hdop.hdop());

    if (gps.time.isValid()) {

      Serial.print("UTC Time: ");

      if (gps.time.hour() < 10)
        Serial.print("0");

      Serial.print(gps.time.hour());
      Serial.print(":");

      if (gps.time.minute() < 10)
        Serial.print("0");

      Serial.print(gps.time.minute());
      Serial.print(":");

      if (gps.time.second() < 10)
        Serial.print("0");

      Serial.println(gps.time.second());
    }
  }
}

Part 2 – Saving GPS Data to an SD Card

Once the ESP32 can successfully read GPS information, the next step is to store it.

A microSD card provides a convenient way to record GPS data during a journey without requiring a computer or network connection.

The SD card uses the ESP32’s SPI interface.

SD Card Connections

SD CardESP32
CSGPIO 5
MOSIGPIO 23
MISOGPIO 19
SCKGPIO 18
VCC5V* ( Some module may require 3.3v)
GNDGND

GPS Log Format

Instead of saving the complete raw NMEA stream, the project stores the useful GPS information in a structured format. We will log in below format:

Date,Time,Latitude,Longitude,Speed,Altitude,Satellites,HDOP
29/06/2026,11:20:31,17.385044,78.486671,0.00,542.30,8,1.20
29/06/2026,11:20:32,17.385050,78.486690,2.10,542.40,8,1.18

Date and Time Based File Names

Instead of creating files with simple names the logger uses the GPS date and time to create meaningful filenames.

For example:

GPS_20260906_214215.csv

This makes it much easier to identify when a particular recording was created.

The filename is basically based on:

GPS_YYYYMMDD_HHMMSS.csv

Automatic File Rotation

A GPS logger can potentially generate a large amount of data during a long journey. Instead of allowing one file to grow indefinitely, the project uses a configured maximum file size. When the current file reaches that limit, the logger automatically creates a new file.

This makes the logger more manageable and prevents a single log file from becoming unnecessarily large.

Flushing Data to the SD Card

The project also periodically flushes the file data to the SD card. This is important because data written by the program may initially be held in a buffer. Periodically calling flush() ensures that the buffered data is written to the card.

For example, the logger can periodically display:

[SD] GPS data flushed to card.

This provides an additional level of protection against losing recently recorded data if power is interrupted.

During testing, the Serial Monitor will show information such as:

GPS: 17.469238, 78.311814 | Speed: 0.54 km/h | Satellites: 5

GPS: 17.469239, 78.311817 | Speed: 0.62 km/h | Satellites: 5

GPS: 17.469241, 78.311820 | Speed: 0.71 km/h | Satellites: 5

[SD] GPS data flushed to card.

At the same time, the information is being stored on the SD card.

Part 2 – GPS Logger Code

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

// ============================================================
// GPS Configuration
// ============================================================

#define GPS_RX_PIN 16
#define GPS_TX_PIN 17
#define GPS_BAUD   9600

HardwareSerial GPS(1);
TinyGPSPlus gps;


// ============================================================
// SD Card Configuration
// ============================================================

#define SD_CS_PIN 5

// Maximum file size
// 1 MB
#define MAX_FILE_SIZE (1024UL * 1024UL)

File logFile;


// ============================================================
// Logging Configuration
// ============================================================

#define LOG_INTERVAL   1000    // Log every 1 second
#define FLUSH_INTERVAL 5000    // Flush every 5 seconds

unsigned long lastLogTime = 0;
unsigned long lastFlushTime = 0;


// ============================================================
// Create New GPS Log File
// ============================================================

bool createNewLogFile()
{
    if (!gps.date.isValid() || !gps.time.isValid())
    {
        Serial.println("[GPS] Date/time not valid. Waiting...");
        return false;
    }

    char filename[50];

    snprintf(
        filename,
        sizeof(filename),
        "/GPS_%04d%02d%02d_%02d%02d%02d.csv",
        gps.date.year(),
        gps.date.month(),
        gps.date.day(),
        gps.time.hour(),
        gps.time.minute(),
        gps.time.second()
    );

    Serial.println();
    Serial.print("[SD] Creating new file: ");
    Serial.println(filename);


    // --------------------------------------------------------
    // Check for filename collision
    // --------------------------------------------------------

    if (SD.exists(filename))
    {
        char uniqueFilename[50];

        for (int i = 1; i <= 99; i++)
        {
            snprintf(
                uniqueFilename,
                sizeof(uniqueFilename),
                "/GPS_%04d%02d%02d_%02d%02d%02d_%d.csv",
                gps.date.year(),
                gps.date.month(),
                gps.date.day(),
                gps.time.hour(),
                gps.time.minute(),
                gps.time.second(),
                i
            );

            if (!SD.exists(uniqueFilename))
            {
                strcpy(filename, uniqueFilename);
                break;
            }
        }
    }


    // --------------------------------------------------------
    // Open file
    // --------------------------------------------------------

    logFile = SD.open(filename, FILE_WRITE);

    if (!logFile)
    {
        Serial.println("[ERROR] Failed to create GPS log file.");
        return false;
    }


    // --------------------------------------------------------
    // CSV Header
    // --------------------------------------------------------

    logFile.println(
        "Date,Time,Latitude,Longitude,Altitude_m,Speed_kmh,Satellites,HDOP"
    );

    logFile.flush();


    Serial.println("[SD] New GPS log file created.");
    Serial.println("[SD] CSV header written.");

    lastFlushTime = millis();

    return true;
}


// ============================================================
// Close Current File
// ============================================================

void closeCurrentFile()
{
    if (logFile)
    {
        logFile.flush();
        logFile.close();

        Serial.println("[SD] Current GPS file closed.");
    }
}


// ============================================================
// Setup
// ============================================================

void setup()
{
    Serial.begin(115200);

    delay(1000);

    Serial.println();
    Serial.println("======================================");
    Serial.println(" ESP32 GPS Data Logger - Part 2");
    Serial.println(" CSV Logger + File Rotation");
    Serial.println("======================================");


    // ========================================================
    // Start GPS
    // ========================================================

    GPS.begin(
        GPS_BAUD,
        SERIAL_8N1,
        GPS_RX_PIN,
        GPS_TX_PIN
    );

    Serial.println("[GPS] GPS serial started.");

    Serial.print("[GPS] RX: GPIO ");
    Serial.println(GPS_RX_PIN);

    Serial.print("[GPS] TX: GPIO ");
    Serial.println(GPS_TX_PIN);


    // ========================================================
    // Start SD Card
    // ========================================================

    Serial.println();
    Serial.println("[SD] Initializing SD card...");

    if (!SD.begin(SD_CS_PIN))
    {
        Serial.println("[ERROR] SD card initialization failed!");
        return;
    }

    Serial.println("[SD] SD card initialized successfully.");


    // ========================================================
    // Display SD Card Information
    // ========================================================

    uint8_t cardType = SD.cardType();

    Serial.print("[SD] Card type: ");

    if (cardType == CARD_MMC)
    {
        Serial.println("MMC");
    }
    else if (cardType == CARD_SD)
    {
        Serial.println("SDSC");
    }
    else if (cardType == CARD_SDHC)
    {
        Serial.println("SDHC");
    }
    else
    {
        Serial.println("UNKNOWN");
    }


    Serial.print("[SD] Card size: ");

    Serial.print(
        SD.cardSize() / (1024 * 1024)
    );

    Serial.println(" MB");


    // ========================================================
    // Wait for GPS
    // ========================================================

    Serial.println();
    Serial.println("[GPS] Waiting for valid GPS date/time...");
    Serial.println("[GPS] Waiting for GPS fix...");
}


// ============================================================
// Main Loop
// ============================================================

void loop()
{
    // ========================================================
    // Read GPS Data
    // ========================================================

    while (GPS.available())
    {
        gps.encode(GPS.read());
    }


    // ========================================================
    // Create First File
    // ========================================================

    if (!logFile)
    {
        if (gps.date.isValid() &&
            gps.time.isValid())
        {
            createNewLogFile();
        }
    }


    // ========================================================
    // Log GPS Data Every 1 Second
    // ========================================================

    if (millis() - lastLogTime >= LOG_INTERVAL)
    {
        lastLogTime = millis();


        // ----------------------------------------------------
        // Check GPS Position
        // ----------------------------------------------------

        if (gps.location.isValid())
        {
            // Make sure file exists
            if (!logFile)
            {
                if (!createNewLogFile())
                {
                    return;
                }
            }


            // =================================================
            // Date
            // =================================================

            char dateBuffer[20];

            snprintf(
                dateBuffer,
                sizeof(dateBuffer),
                "%04d-%02d-%02d",
                gps.date.year(),
                gps.date.month(),
                gps.date.day()
            );


            // =================================================
            // Time
            // =================================================

            char timeBuffer[20];

            snprintf(
                timeBuffer,
                sizeof(timeBuffer),
                "%02d:%02d:%02d",
                gps.time.hour(),
                gps.time.minute(),
                gps.time.second()
            );


            // =================================================
            // Write CSV Record
            // =================================================

            logFile.print(dateBuffer);
            logFile.print(",");

            logFile.print(timeBuffer);
            logFile.print(",");

            // Latitude
            logFile.print(
                gps.location.lat(),
                6
            );

            logFile.print(",");

            // Longitude
            logFile.print(
                gps.location.lng(),
                6
            );

            logFile.print(",");

            // Altitude in meters
            logFile.print(
                gps.altitude.meters(),
                2
            );

            logFile.print(",");

            // Speed in km/h
            logFile.print(
                gps.speed.kmph(),
                2
            );

            logFile.print(",");

            // Satellites
            logFile.print(
                gps.satellites.value()
            );

            logFile.print(",");

            // HDOP
            logFile.println(
                gps.hdop.hdop(),
                2
            );


            // =================================================
            // Serial Monitor Output
            // =================================================

            Serial.print("GPS: ");

            Serial.print(
                gps.location.lat(),
                6
            );

            Serial.print(", ");

            Serial.print(
                gps.location.lng(),
                6
            );

            Serial.print(" | Alt: ");

            Serial.print(
                gps.altitude.meters(),
                2
            );

            Serial.print(" m | Speed: ");

            Serial.print(
                gps.speed.kmph(),
                2
            );

            Serial.print(" km/h | Satellites: ");

            Serial.print(
                gps.satellites.value()
            );

            Serial.print(" | HDOP: ");

            Serial.println(
                gps.hdop.hdop(),
                2
            );


            // =================================================
            // Check File Size
            // =================================================

            if (logFile.position() >= MAX_FILE_SIZE)
            {
                Serial.println();
                Serial.println(
                    "[SD] Maximum file size reached."
                );

                Serial.println(
                    "[SD] Rotating to a new file..."
                );

                closeCurrentFile();

                // New file will be created on the
                // next valid GPS record.
            }
        }
        else
        {
            Serial.println(
                "[GPS] Waiting for valid GPS fix..."
            );
        }
    }


    // ========================================================
    // Flush Data Every 5 Seconds
    // ========================================================

    if (logFile &&
        millis() - lastFlushTime >= FLUSH_INTERVAL)
    {
        logFile.flush();

        lastFlushTime = millis();

        Serial.println(
            "[SD] GPS data flushed to card."
        );
    }
}

Testing the Logger

After allowing the GPS to obtain a valid fix, the logger can be tested by moving the GPS module around or taking it outside. Or attach an external GPS antenna like I did and place it in your balcony/Window.

Once you start receiving GPS Fix and see data in you serial monitor GPS logger will start writing the information in a CSV file and store it in SD card. After some interval, disconnect the setup from power and remove the SD card and verify the CSV file Format. The data should look as shown below.

What’s Next?

With EP1 complete, we now have the basic GPS data logger working:

NEO-7M → ESP32 → SD Card

In EP2, we’ll make the project more useful by adding a display and working with the recorded GPS data.

The next stage will include:

  • Adding an TFT display
  • Showing live GPS information
  • Displaying latitude and longitude
  • Showing speed and satellite count
  • Integrating the display with the existing GPS logger
  • Taking the recorded GPS data from the SD card
  • Visualizing the recorded journey on a map

The ultimate goal is to turn the project into a practical GPS road-trip logger that can record a journey and allow the route to be viewed later.

Step by Step YouTube Video

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.