Breadboard held outdoors with ESP32-C3 SuperMini, NEO-6M GPS module, and OLED display showing GPS coordinates

Beginner

Build a GPS Location Display

SensorsDisplaysIoT

Know exactly where you are, down to a few meters, from a real satellite fix -- no phone, no internet connection required.

6 real parts included

Total for this kit: 1,685.00 ฿

Doesn't include any order discount you may get at checkout.

What you'll need

Project Goal

This project builds a self-contained location display: a controller reads a real satellite fix from a dedicated GPS module and shows your current latitude and longitude on a small screen, entirely offline. No phone, no cell signal, and no internet connection are involved once the code is running -- the fix comes directly from GPS satellites.

What the Controller Does

The ESP32-C3 SuperMini reads a continuous stream of data from the GPS module over a serial (UART) connection, parses out the fields that matter -- latitude, longitude, and altitude -- and redraws the OLED display with the current position on a regular interval.

What the GPS Module Does

The NEO-6M GPS module does the actual satellite work: it tracks GPS satellites, computes a position fix, and streams the result out continuously as standard NMEA sentences over UART. NMEA is a well-established, widely supported text format, which is why mature parsing libraries exist for essentially every microcontroller platform -- you don't need to hand-parse the raw sentences yourself.

What the OLED Display Does

The 0.96" OLED simply renders whatever the controller has most recently parsed from the GPS -- latitude, longitude, and optionally altitude or fix status -- refreshed each cycle so the screen always reflects the latest data the module has reported.

UART Wiring Basics

The GPS module communicates over UART, a simple two-wire serial link: the module's TX (transmit) pin connects to the controller's RX (receive) pin, and vice versa. Getting TX and RX crossed -- connecting TX to TX -- is an extremely common beginner mistake and the first thing to check if nothing is coming through.

Software and Library Concepts

Start from a well-tested NMEA parsing library for your controller platform rather than writing sentence parsing from scratch -- NMEA has a defined checksum and field structure that's easy to get subtly wrong by hand. Once the library reports it's receiving valid sentences, extracting latitude and longitude is typically a couple of function calls.

Testing and Troubleshooting

  • No fix ever appears, even after waiting -- move to a location with a genuinely clear, unobstructed view of the sky. GPS reception does not work indoors or under heavy cover, and this is the single most common reason a GPS module 'doesn't work.'
  • Cold start takes a long time to get a first fix -- this is normal GPS behavior, not a defect. Time to first fix can reasonably take anywhere from about 30 seconds to several minutes on a cold start, depending on satellite visibility.
  • Serial data looks garbled or nothing appears at all on the UART connection -- double-check that TX and RX are not swapped between the module and the controller; this is the most common wiring mistake with any UART peripheral.
  • Display shows 0,0 as the coordinates -- this means no valid fix has been acquired yet, not a bug in your code. Wait longer and/or move somewhere with a clearer sky view before assuming something is broken.

Optional Future Upgrade: Logging a Track Over Time

Once a live fix is displaying reliably, a natural next step is logging a series of fixes over time -- whether to local storage or, combined with the concepts from a wireless data logger project, sent out over a network -- to build a simple track of movement rather than only showing the current position.

Wiring

  • NEO-6M VCC → ESP32-C3 SuperMini 5V (module has an onboard regulator, tolerant of 5V)
  • NEO-6M GND → GND
  • NEO-6M TX → GPIO20 (`GPS_RX_PIN`) — module TX into controller RX
  • NEO-6M RX → GPIO21 (`GPS_TX_PIN`) — module RX from controller TX (standard UART crossover)
  • OLED VCC → 3.3V
  • OLED GND → GND
  • OLED SDA → GPIO8 (`SDA_PIN`)
  • OLED SCL → GPIO9 (`SCL_PIN`)

GPS needs a clear view of the sky — indoors, expect "no fix" and a satellite count of 0 for a long time, or forever; that's normal, not a wiring fault. First fix ("cold start") commonly takes 30 seconds to a few minutes; subsequent fixes after a brief power loss are usually much faster. The ESP32-C3's pin matrix lets `HardwareSerial` be remapped to almost any GPIO, so GPS_RX_PIN/GPS_TX_PIN aren't fixed hardware UART pins — adjust the `#define`s if you wire to different GPIOs.

Wiring Diagram

VCC → 5V GND TX → GPIO20 RX → GPIO21 VCC GND SDA → GPIO8 SCL → GPIO9 ESP32-C3 SuperMini NEO-6M OLED GND 3.3V

Starter Code

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <TinyGPSPlus.h>

#define SDA_PIN 8       // ESP32-C3 default I2C SDA - adjust if your board differs
#define SCL_PIN 9       // ESP32-C3 default I2C SCL - adjust if your board differs

#define GPS_RX_PIN 20   // to GPS module TX - any free GPIO (remapped via pin matrix)
#define GPS_TX_PIN 21   // to GPS module RX - adjust to match your wiring if different
#define GPS_BAUD 9600   // NEO-6M default NMEA output baud rate

#define OLED_ADDRESS 0x3C

Adafruit_SSD1306 display(128, 64, &Wire, -1);
TinyGPSPlus gps;
HardwareSerial gpsSerial(1); // use UART1

unsigned long lastFixTime = 0;

void setup() {
  Serial.begin(115200);
  gpsSerial.begin(GPS_BAUD, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN);

  Wire.begin(SDA_PIN, SCL_PIN);
  if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDRESS)) {
    Serial.println("SSD1306 init failed");
    while (true) delay(1000);
  }

  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);
  display.setTextSize(1);
  display.setCursor(0, 0);
  display.println("Waiting for GPS fix...");
  display.println("(needs clear sky view,");
  display.println("can take a few minutes)");
  display.display();
}

void loop() {
  while (gpsSerial.available() > 0) {
    gps.encode(gpsSerial.read());
  }

  if (gps.location.isUpdated() && gps.location.isValid()) {
    lastFixTime = millis();

    display.clearDisplay();
    display.setTextSize(1);
    display.setCursor(0, 0);
    display.println("GPS Fix");
    display.setCursor(0, 14);
    display.printf("Lat: %.6f\n", gps.location.lat());
    display.printf("Lon: %.6f\n", gps.location.lng());

    if (gps.altitude.isValid()) {
      display.printf("Alt: %.1f m\n", gps.altitude.meters());
    }
    if (gps.satellites.isValid()) {
      display.printf("Sats: %d\n", gps.satellites.value());
    }
    display.display();

    Serial.printf("Lat: %.6f  Lon: %.6f  Sats: %d\n",
                  gps.location.lat(), gps.location.lng(), gps.satellites.value());
  } else if (lastFixTime == 0 && millis() % 2000 < 20) {
    // still searching for first fix - a normal wait indoors or under poor sky view
    Serial.println("Searching for GPS fix...");
  }
}

Not sure about a step? Ask us