Community project
GPS Status Display
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

Gather all the parts
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"
2. Connections between "page_button_1" and "ESP32"
3. Connections between "rs232_level_1" and "ESP32"
4. Connections between "gps_receiver_1" and "ESP32"
Deploy the firmware
#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.




