Community project

GPS Status Display

ESP32
Photo of GPS Status Display
Generated with AI

Eren YAZICI

Published October 15, 2026

This project builds a real-time GPS status display using an ESP32 microcontroller connected to an NMEA RS232 GPS receiver. The MAX3232 converter bridges the RS232 signal to TTL levels, while the SH1106 OLED screen shows position, satellite count, and signal quality. A push button lets users cycle through multiple display pages.

The guide includes a complete wiring diagram, parts list, and step-by-step assembly instructions. Firmware is provided with multi-page display logic, NMEA sentence parsing via TinyGPSPlus, and debounced button handling. Builders will learn how to integrate serial GPS receivers with modern microcontrollers and create responsive user interfaces on small displays.

Wiring diagram

Wiring diagram for GPS Status Display

Gather all the parts

QtyComponent
1

SH1106 OLED

1.3 inch 128x64 OLED display with I2C interface using the SH1106 controller

1

Push Button

Momentary push button switch

1

MAX3232 RS232-to-TTL converter module

A small converter board that makes the GPS receiver's RS232 signal safe for the ESP32's 3.3 V input.

1

NMEA RS232 GPS receiver

The external GPS receiver that supplies position, time, and navigation sentences over an RS232 serial output.

Assemble it in 5 steps

1. Önce ekranı bağlayın

OLED ekranın VCC ucunu ESP32 üzerindeki 3V3 ucuna, GND ucunu GND ucuna bağlayın. Ekranın SDA ucundan GPIO21'e, SCL ucundan GPIO22'ye iki ayrı kablo çekin; bunlar ekrana yazı gönderir.

  • OLED kartında pin adları bazen VDD ve GND olarak yazabilir; VDD, VCC ile aynı güç ucudur.
  • VCC ve GND yerlerini ters bağlamayın; ters güç bağlantısı ekranı bozabilir.

2. Sayfa düğmesini takın

Butonun bir bacağını ESP32 GND ucuna, karşı taraftaki bacağını GPIO27'ye bağlayın. Kod düğmeyi içeriden güvenli şekilde yüksek tutar; bastığınızda bu iki uç birleşir ve sonraki sayfa açılır.

  • Dört bacaklı küçük butonda aynı taraftaki iki bacak zaten birbirine bağlıdır; karşılıklı iki taraftan birer bacak kullanın.

3. RS232 çeviriciyi besleyin

MAX3232 kartının VCC ucunu ESP32 3V3 ucuna ve GND ucunu ESP32 GND ucuna bağlayın. Bu kart, GPS alıcısının RS232 sinyalini ESP32'nin anlayacağı güvenli 3,3 V sinyale çevirir.

  • GPS alıcısının RS232 kablosunu doğrudan ESP32 GPIO16 ucuna bağlamayın; RS232 gerilimi ESP32 girişine zarar verebilir.

4. GPS verisini çeviriciden geçirin

GPS alıcısının RS232 TX ucunu MAX3232 kartındaki R1IN ucuna bağlayın. MAX3232 kartındaki R1OUT ucundan ESP32 GPIO16 ucuna kablo çekin. GPS alıcısının GND ucunu da ESP32 GND ucuna bağlayın; bu ortak dönüş kablosu verinin doğru okunmasını sağlar.

  • MAX3232 kartınız R1IN/R1OUT yerine RXD/TXD yazıyorsa, kartın baskısındaki RS232 tarafı girişini GPS TX'e, TTL tarafı çıkışını GPIO16'ya bağlayın.
  • GPS cihazınızın TX ve GND isimlerini kullanım kılavuzundan kontrol edin; yanlış pin seçimi ekranda veri görünmemesine neden olur.

5. USB ile çalıştırın

Tüm GND kablolarının ortak olduğundan ve açıkta birbirine değen tel olmadığından emin olun. ESP32'yi USB kablosuyla bilgisayara bağlayın; bu proje normal USB gücüyle çalışır.

  • GPS alıcısının kendi güç gereksinimini üreticisinin önerdiği beslemeyle karşılayın; bu tasarım yalnızca onun RS232 veri ve GND bağlantısını kullanır.

Review all connections

1. Connections between "oled_1" and "ESP32"

Functionoled_1ESP32
powerVCC3V3
groundGNDGND
i2cSDAGPIO 21
i2cSCLGPIO 22

2. Connections between "page_button_1" and "ESP32"

Functionpage_button_1ESP32
groundGNDGND
digitalSIGNALGPIO 27

3. Connections between "rs232_level_1" and "ESP32"

Functionrs232_level_1ESP32
powerVCC3V3
groundGNDGND
uartR1OUTGPIO 16

4. Connections between "gps_receiver_1" and "ESP32"

Functiongps_receiver_1ESP32
dataTX → MAX3232 RS232-to-TTL converter module R1INEXT
groundGNDGND

Deploy the firmware

NAVIMAR_NMEA_RS232_Servis.inoOpen in Schematik
#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SH110X.h>
#include <TinyGPSPlus.h>

constexpr int OLED_SDA = 21;
constexpr int OLED_SCL = 22;
constexpr int GPS_RX_PIN = 16;
constexpr int PAGE_BUTTON_PIN = 27;
constexpr uint32_t GPS_BAUD = 4800;       // Standard NMEA RS232 speed
constexpr uint32_t STALE_AFTER_MS = 5000; // Show stale after 5 seconds without a sentence
constexpr uint32_t BUTTON_DEBOUNCE_MS = 35;

Adafruit_SH1106G display(128, 64, &Wire, -1);
TinyGPSPlus gps;
HardwareSerial gpsSerial(2);

uint8_t page = 0;
uint32_t lastSentenceMs = 0;
uint32_t lastButtonEdgeMs = 0;
bool lastButtonReading = HIGH;
bool stableButtonState = HIGH;
String lastFrame;

bool nmeaIsStale() {
  return lastSentenceMs == 0 || (millis() - lastSentenceMs > STALE_AFTER_MS);
}

bool hasFreshFix() {
  return !nmeaIsStale() && gps.location.isValid() && gps.location.age() < STALE_AFTER_MS;
}

void printStatusLine() {
  display.setTextSize(1);
  display.setCursor(0, 55);
  if (nmeaIsStale()) {
    display.print(F("NMEA ESKI / BAGLANTI YOK"));
  } else if (!hasFreshFix()) {
    display.print(F("VERI CANLI - FIX YOK"));
  } else {
    display.print(F("VERI GUNCEL"));
  }
}

void drawPage() {
  display.clearDisplay();
  display.setTextColor(SH110X_WHITE);
  display.setTextSize(1);

  if (page == 0) {
    display.setCursor(0, 0);
    display.print(F("GPS DURUM  1/5"));
    display.drawFastHLine(0, 10, 128, SH110X_WHITE);
    display.setTextSize(2);
    display.setCursor(0, 16);
    display.print(hasFreshFix() ? F("FIX OK") : F("FIX YOK"));
    display.setTextSize(1);
    display.setCursor(0, 39);
    display.print(F("Uydu: "));
    if (gps.satellites.isValid()) display.print(gps.satellites.value());
    else display.print(F("--"));
    display.setCursor(70, 39);
    display.print(F("HDOP: "));
    if (gps.hdop.isValid()) display.print(gps.hdop.hdop(), 1);
    else display.print(F("--"));
  } else if (page == 1) {
    display.setCursor(0, 0);
    display.print(F("KONUM  2/5"));
    display.drawFastHLine(0, 10, 128, SH110X_WHITE);
    display.setCursor(0, 18);
    display.print(F("EN: "));
    if (hasFreshFix()) display.print(gps.location.lat(), 6);
    else display.print(F("GECERSIZ"));
    display.setCursor(0, 35);
    display.print(F("BOY: "));
    if (hasFreshFix()) display.print(gps.location.lng(), 6);
    else display.print(F("GECERSIZ"));
  } else if (page == 2) {
    display.setCursor(0, 0);
    display.print(F("UTC ZAMAN  3/5"));
    display.drawFastHLine(0, 10, 128, SH110X_WHITE);
    display.setCursor(0, 18);
    display.print(F("Tarih: "));
    if (!nmeaIsStale() && gps.date.isValid()) {
      char dateText[16];
      snprintf(dateText, sizeof(dateText), "%02d.%02d.%04d", gps.date.day(), gps.date.month(), gps.date.year());
      display.print(dateText);
    } else display.print(F("GECERSIZ"));
    display.setCursor(0, 35);
    display.print(F("UTC:   "));
    if (!nmeaIsStale() && gps.time.isValid()) {
      char timeText[12];
      snprintf(timeText, sizeof(timeText), "%02d:%02d:%02d", gps.time.hour(), gps.time.minute(), gps.time.second());
      display.print(timeText);
    } else display.print(F("GECERSIZ"));
  } else if (page == 3) {
    display.setCursor(0, 0);
    display.print(F("HAREKET  4/5"));
    display.drawFastHLine(0, 10, 128, SH110X_WHITE);
    display.setCursor(0, 18);
    display.print(F("Hiz: "));
    if (!nmeaIsStale() && gps.speed.isValid()) {
      display.print(gps.speed.kmph(), 1);
      display.print(F(" km/sa"));
    } else display.print(F("GECERSIZ"));
    display.setCursor(0, 35);
    display.print(F("Yon: "));
    if (!nmeaIsStale() && gps.course.isValid()) {
      display.print(gps.course.deg(), 1);
      display.print(F(" deg"));
    } else display.print(F("GECERSIZ"));
    printStatusLine();
  } else {
    display.drawRect(0, 0, 128, 64, SH110X_WHITE);
    display.setTextSize(1);
    display.setCursor(24, 10);
    display.print(F("5/5"));
    display.drawFastHLine(10, 22, 108, SH110X_WHITE);
    display.setTextSize(2);
    display.setCursor(18, 30);
    display.print(F("NAVIMAR"));
    display.setTextSize(1);
    display.setCursor(25, 50);
    display.print(F("ELECTRONICS"));
  }

  if (page != 3 && page != 4) printStatusLine();
  display.display();
}

String screenSignature() {
  String s;
  s.reserve(100);
  s += page;
  s += '|';
  s += nmeaIsStale();
  s += '|';
  s += hasFreshFix();
  s += '|';
  s += gps.satellites.value();
  s += '|';
  s += gps.hdop.value();
  s += '|';
  s += gps.location.lat();
  s += '|';
  s += gps.location.lng();
  s += '|';
  s += gps.date.value();
  s += '|';
  s += gps.time.value();
  s += '|';
  s += gps.speed.value();
  s += '|';
  s += gps.course.value();
  return s;
}

void updateButton() {
  bool reading = digitalRead(PAGE_BUTTON_PIN);
  if (reading != lastButtonReading) lastButtonEdgeMs = millis();

  if (millis() - lastButtonEdgeMs > BUTTON_DEBOUNCE_MS && reading != stableButtonState) {
    stableButtonState = reading;
    if (stableButtonState == LOW) page = (page + 1) % 5;
  }
  lastButtonReading = reading;
}

void setup() {
  pinMode(PAGE_BUTTON_PIN, INPUT_PULLUP);
  Wire.begin(OLED_SDA, OLED_SCL);
  display.begin(0x3C, true);
  display.clearDisplay();
  display.setTextColor(SH110X_WHITE);
  display.setTextSize(1);
  display.setCursor(0, 25);
  display.print(F("NMEA servis cihazi"));
  display.display();
  gpsSerial.begin(GPS_BAUD, SERIAL_8N1, GPS_RX_PIN, -1);
}

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

  updateButton();

  String newFrame = screenSignature();
  if (newFrame != lastFrame) {
    lastFrame = newFrame;
    drawPage();
  }
}

Remix this project

Make it yours in one click

Open a full copy of this project in your own Schematik workspace — diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.

Open in Schematik