ESP32 GPS Tracker mit NEO-6M – Standortverfolgung für Mikrocontroller-Projekte
Mit dem ESP32 und einem NEO-6M GPS-Modul baust du einen vollwertigen GPS-Tracker, der Live-Koordinaten, Geschwindigkeit und Höhe erfasst. Ob auf dem OLED-Display ablesen oder als Wegpunkt-Log auf SD-Karte speichern – in diesem Tutorial zeige ich dir alle drei Code-Stufen, den kompletten Aufbau mit Schaltplan und wie du das Ganze mit einem TP4056-Lademodul akkubetrieben machst.
Warum einen eigenen GPS-Tracker bauen?
GPS-Tracking ist längst kein Hexenwerk mehr. Für unter 25 € Materialkosten bekommst du ein System, das mit kommerziellen Trackern locker mithalten kann – und das bei voller Kontrolle über Hardware und Firmware. Kein Abo, keine Cloud-Abhängigkeit, kein Vendor-Lock-in. Du entscheidest, was mit deinen Positionsdaten passiert.
Der ESP32 ist dafür die perfekte Plattform: Dual-Core mit 240 MHz, WLAN und Bluetooth an Bord, 512 KB RAM und satte 34 GPIO-Pins. Zusammen mit dem NEO-6M von u-blox – einem 50-Kanal-GPS-Empfänger mit 2,5 m CEP-Genauigkeit – hast du ein leistungsfähiges Duo, das sich für alles eignet: Wandertouren aufzeichnen, Fahrzeugbewegungen tracken, Drohnenflüge loggen oder eine portable Wetterstation mit Positionsstempel betreiben.
In diesem Artikel baust du das System in drei Eskalationsstufen auf: Erst liest du rohe NMEA-Daten aus, dann parse ich sie mit der TinyGPS++-Bibliothek und zeige sie auf einem OLED-Display an, und schließlich loggst du komplette Wegpunkte mit Zeitstempel auf eine microSD-Karte.
So funktioniert GPS – die Grundlagen
Bevor wir löten und coden, ein kurzer Deep Dive in die Technik. Denn wer versteht, was da eigentlich passiert, debuggt zehnmal schneller.
Das Satellitennetz
Das Global Positioning System (GPS) besteht aus mindestens 24 aktiven Satelliten, die in rund 20.200 km Höhe die Erde umkreisen – verteilt auf sechs Bahnebenen, jeweils vier Satelliten pro Ebene. Jeder Satellit umrundet die Erde zweimal am Tag und sendet kontinuierlich zwei Informationen: seine exakte Position im Orbit (Ephemeriden) und einen hochpräzisen Zeitstempel, erzeugt von einer onboard verbauten Atomuhr.
Dein NEO-6M-Empfänger am Boden fängt diese Signale ein. Aus der Laufzeitdifferenz – das Funksignal braucht von 20.200 km etwa 67 Millisekunden bis zur Erde – berechnet er die Entfernung zu jedem empfangenen Satelliten. Das Verfahren heißt Trilateration (nicht Triangulation – hier werden Entfernungen gemessen, keine Winkel).
Trilateration vs. Triangulation: Bei der Triangulation werden Winkel zwischen bekannten Punkten gemessen – wie beim Vermessungswesen. GPS hingegen misst Entfernungen über Signallaufzeiten (Lichtgeschwindigkeit × Zeit = Distanz). Mit einem Satelliten weißt du nur, dass du dich irgendwo auf einer Kugeloberfläche befindest. Mit zweien schneiden sich zwei Kugeln zu einem Kreis. Der dritte Satellit grenzt das auf zwei Punkte ein. Der vierte liefert die Höhe und korrigiert den Uhrenfehler deines Empfängers.
Das NMEA-0183-Protokoll
GPS-Module sprechen eine standardisierte Sprache: NMEA 0183 (National Marine Electronics Association). Dabei handelt es sich um ein textbasiertes Protokoll, das der NEO-6M als seriellen Datenstrom über UART ausgibt – standardmäßig mit 9600 Baud, 8 Datenbits, 1 Stopbit, keiner Parität.
Wichtige NMEA-Sentences im Überblick:
| Sentence | Bedeutung | Enthält |
|---|---|---|
$GPGGA |
GPS Fix Data | Position, Höhe, Anzahl Satelliten, Fix-Qualität |
$GPRMC |
Recommended Minimum | Position, Geschwindigkeit (kn), Kurs, Datum+Uhrzeit |
$GPGSA |
DOP & Active Satellites | Fix-Typ (2D/3D), aktive Satelliten-IDs, PDOP/HDOP/VDOP |
$GPGSV |
Satellites in View | Sichtbare Satelliten, Elevation, Azimut, SNR |
$GPVTG |
Track & Ground Speed | Geschwindigkeit in km/h, Kurs über Grund |
Eine typische $GPRMC-Sentence sieht so aus:
$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A
Das bedeutet: Fix um 12:35:19 UTC, Status A=aktiv, Breite 48°07.038'N, Länge 011°31.000'E, Geschwindigkeit 22,4 Knoten, Kurs 84,4°, Datum 23.03.1994, magnetische Deklination 3,1°W, Prüfsumme *6A.
Kaltstart, Warmstart, TTFF
Die Time To First Fix (TTFF) – also die Zeit vom Einschalten bis zur ersten gültigen Position – hängt stark vom Startzustand ab:
- Kaltstart (~27–30 s): Der Empfänger hat keinerlei gespeicherte Daten. Er muss die gesamte Satellitenkonstellation neu erfassen – Almanach, Ephemeriden, Uhrzeit. Das passiert beim allerersten Einschalten oder nach wochenlangem Stillstand.
- Warmstart (~5–10 s): Almanach und ungefähre Position sind durch die Backup-Batterie des NEO-6M erhalten geblieben. Nur die Ephemeriden müssen aktualisiert werden.
- Heißstart (~1 s): Alle Daten sind frisch – z. B. nach kurzer Unterbrechung. Der Fix steht quasi sofort.
Praxis-Tipp: Die Pufferbatterie (CR1220 oder CR2032, je nach Breakout-Variante) auf dem NEO-6M ist keine Spielerei – sie hält den batteriegepufferten RAM (BBR) und den Echtzeituhr-Oszillator am Leben. Ohne sie fällt jedes Einschalten auf einen Kaltstart zurück. Wechsle sie alle 1–2 Jahre, dann hast du konsistent Warmstarts mit unter 10 Sekunden TTFF.
Hardware – das brauchst du
Komponentenliste
ESP32 DevKit – Das Gehirn
Der ESP32 bringt alles mit, was ein GPS-Tracker braucht: genug Rechenleistung für Echtzeit-Parsing, drei UARTs (einer davon fürs GPS), I²C fürs Display und SPI für die SD-Karte. Plus WLAN für optionale Live-Übertragung der Daten. Dank 240 MHz Takt und Dual-Core läuft das GPS-Parsing auf Core 0, während Core 1 Display-Updates erledigt – kein Ruckeln, keine verlorenen Sentences.
NEO-6M GPS-Modul – Die Augen am Himmel
Das u-blox NEO-6M ist ein 50-Kanal-Empfänger mit -161 dBm Empfindlichkeit im Tracking-Modus. Es liefert standardmäßig 1 Hz Update-Rate (ein Fix pro Sekunde), ist aber bis 5 Hz konfigurierbar. Betriebsspannung 3,3–5 V (onboard-Regler auf den meisten Breakouts), Stromaufnahme ~40 mA im Tracking. Die Keramik-Patchantenne reicht für Outdoor-Anwendungen; bei schlechtem Empfang (dichtes Laub, Häuserschluchten) hilft eine externe aktive Antenne über den U.FL/IPEX-Anschluss.
0.96" OLED SSD1306 – Das Display
128×64 Pixel, I²C-Schnittstelle (nur 2 Pins!), gestochen scharfer Kontrast und mit ~20 mA super sparsam. Darauf zeigen wir Koordinaten, Geschwindigkeit, Höhe, Satellitenanzahl und UTC-Zeit an. Im Sonnenlicht gut ablesbar.
TP4056 USB-C Lade-Modul – Die Stromversorgung
Lädt einen 18650-Li-Ion-Akku mit 1 A Ladestrom, integrierte Schutzschaltung (DW01) gegen Tiefentladung, Überladung und Kurzschluss. USB-C-Anschluss zum Nachladen. In Kombination mit einem 3,7-V-auf-5-V-Step-Up-Modul versorgst du ESP32 und Peripherie stundenlang mobil.
Micro SD Card Reader (SPI) – Der Datenspeicher
SPI-Interface, 3,3 V Logik (passt perfekt zum ESP32). Speichert Wegpunkte als CSV-Datei – jede Zeile ein Trackpunkt mit Zeitstempel, Lat, Lon, Höhe, Speed. Eine 8 GB-Karte fasst Millionen von Wegpunkten.
Breadboard 400 + JST-XH Stecker Set
Für den Prototyp-Aufbau. Das Breadboard gibt dir 400 Kontaktpunkte, das JST-XH-Set saubere Steckverbindungen für den Akku – kein loses Gefummel mit Litzen an den Batteriepolen.
Gesamtkosten: Alle Komponenten zusammen liegen bei rund 22–25 €. Dafür bekommst du einen voll funktionsfähigen GPS-Tracker, der in Sachen Flexibilität jedes kommerzielle Gerät in dieser Preisklasse schlägt.
Verkabelung – Schritt für Schritt
Die Verdrahtung ist überschaubar, aber sauberes Arbeiten lohnt sich. Hier die Pin-Belegung im Detail:
| Komponente | Pin | ESP32 Pin | Protokoll |
|---|---|---|---|
| NEO-6M | VCC | 3.3V | – |
| NEO-6M | GND | GND | – |
| NEO-6M | TX | GPIO 16 (RX2) | UART2 RX |
| NEO-6M | RX | GPIO 17 (TX2) | UART2 TX |
| OLED SSD1306 | VCC | 3.3V | – |
| OLED SSD1306 | GND | GND | – |
| OLED SSD1306 | SCL | GPIO 22 | I²C Clock |
| OLED SSD1306 | SDA | GPIO 21 | I²C Data |
| MicroSD Reader | CS | GPIO 5 | SPI Chip Select |
| MicroSD Reader | MOSI | GPIO 23 | SPI MOSI |
| MicroSD Reader | MISO | GPIO 19 | SPI MISO |
| MicroSD Reader | SCK | GPIO 18 | SPI Clock |
| MicroSD Reader | VCC | 3.3V | – |
| MicroSD Reader | GND | GND | – |
Achtung – Spannungspegel: Der ESP32 arbeitet mit 3,3 V Logik. Die meisten NEO-6M-Breakouts und SD-Karten-Reader sind 3,3-V-tolerant oder haben onboard-Regler. Verwende keine 5 V direkt an den GPIOs – das zerstört den ESP32. Die 5V-Schiene des DevKits (VIN-Pin) ist nur über den USB-Port oder den integrierten Spannungsregler zu versorgen.
Stromversorgung für mobilen Betrieb
Für den Akkubetrieb schaltest du einen 18650 Li-Ion-Akku (3,7 V nominal, ~2.500 mAh) an den BAT+/BAT--Anschluss des TP4056-Moduls. Der OUT+/OUT--Anschluss des TP4056 führt zum Eingang eines 3,7V-auf-5V-Step-Up-Moduls (z. B. MT3608). Von dort mit 5 V an den VIN-Pin des ESP32. So läuft der Tracker je nach Akkugröße 6–10 Stunden autark.
Stromspar-Tipp: Der ESP32 hat einen Deep-Sleep-Modus mit nur ~10 µA Stromaufnahme. Wenn du nur alle 30 Sekunden einen Fix brauchst, kannst du den ESP32 dazwischen schlafen legen und per RTC-Timer wecken. Das verlängert die Akkulaufzeit auf mehrere Tage. Die GPS-Positionsdaten bleiben dank der Backup-Batterie des NEO-6M erhalten – nach dem Aufwachen hast du in ~1 Sekunde wieder einen Fix.
Code-Stufe 1: Rohe NMEA-Daten empfangen
Der einfachste Einstieg: Wir verbinden den NEO-6M mit dem ESP32 und geben die Rohdaten im Serial Monitor aus. Das ist der erste Test, ob die Hardware funktioniert – und ein direkter Blick in die „Gedanken" des GPS-Moduls.
1 Bibliotheken & Einrichtung
Keine externe Bibliothek nötig – die HardwareSerial-Klasse ist im ESP32-Arduino-Core enthalten.
/*
* Code-Stufe 1: NMEA-Rohdaten via UART2 empfangen
* NEO-6M TX → ESP32 GPIO16 (RX2)
* NEO-6M RX → ESP32 GPIO17 (TX2) — optional, für Konfiguration
* Öffne den Serial Monitor mit 115200 Baud
*/
#include <HardwareSerial.h>
// UART2 für GPS reservieren
HardwareSerial gpsSerial(2);
void setup() {
Serial.begin(115200);
gpsSerial.begin(9600, SERIAL_8N1, 16, 17); // RX=16, TX=17
Serial.println("NEO-6M NMEA Rohdaten — warte auf Satellitenfix...");
Serial.println("==================================================");
}
void loop() {
// Alle verfügbaren Bytes von GPS nach Serial durchleiten
while (gpsSerial.available() > 0) {
char c = gpsSerial.read();
Serial.print(c);
}
}
Das siehst du im Serial Monitor
Nach dem Hochladen erscheinen Zeilen wie diese (Beispiel von einem klaren Tag mit freier Sicht):
$GPGGA,123519.00,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47
$GPGSA,A,3,04,05,,09,12,,,24,,,,,2.5,0.9,2.3*39
$GPGSV,3,1,12,04,64,043,42,05,36,234,38,09,27,315,45,12,12,155,29*7C
$GPRMC,123519.00,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A
$GPVTG,084.4,T,081.3,M,022.4,N,041.5,K*5F
Woran erkennst du einen gültigen Fix? Im $GPGGA-Satz steht an Position 7 die Fix-Qualität: 0 = kein Fix, 1 = GPS-Fix (SPS), 2 = DGPS-Fix. Im $GPRMC-Satz zeigt Position 3 den Status: A = active/valid, V = void/invalid. Solange du V oder Positionen mit lauter Nullen siehst, hat der Empfänger noch keinen Fix. Geh raus an die frische Luft – GPS-Signale durchdringen keine Stahlbetondecken!
Code-Stufe 2: Parsen mit TinyGPS++ und OLED-Anzeige
Rohe NMEA-Strings sind lehrreich, aber unpraktisch. Jetzt parsen wir die Daten mit der TinyGPS++-Bibliothek und zeigen das Ergebnis aufbereitet auf dem OLED-Display an.
1 Benötigte Bibliotheken
Installiere über den Arduino Library Manager (Sketch → Bibliothek einbinden → Bibliotheken verwalten):
- TinyGPS++ von Mikal Hart – leichtgewichtiger NMEA-Parser, ~12 KB Flash
- Adafruit SSD1306 von Adafruit – OLED-Treiber
- Adafruit GFX Library von Adafruit – Grafik-Primitives (wird von SSD1306 benötigt)
2 Der vollständige Code
/*
* Code-Stufe 2: Geparste GPS-Daten auf OLED-Display
* Zeigt Lat, Lon, Altitude, Speed, Satelliten, UTC-Zeit
* NEO-6M → UART2 (GPIO16/17)
* OLED SSD1306 → I²C (GPIO21=SDA, GPIO22=SCL)
*/
#include <TinyGPS++.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_ADDR 0x3C
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
TinyGPSPlus gps;
HardwareSerial gpsSerial(2);
// Formatierung: Dezimalgrad mit 6 Nachkommastellen (≈ 11 cm Auflösung)
void displayGPS() {
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
if (gps.location.isValid()) {
// Zeile 1: Breitengrad
display.setCursor(0, 0);
display.print("Lat: ");
display.print(gps.location.lat(), 6);
// Zeile 2: Längengrad
display.setCursor(0, 10);
display.print("Lon: ");
display.print(gps.location.lng(), 6);
// Zeile 3: Höhe + Geschwindigkeit
display.setCursor(0, 22);
display.print("Alt: ");
display.print(gps.altitude.meters(), 1);
display.print("m ");
display.print(gps.speed.kmph(), 1);
display.print("km/h");
// Zeile 4: Satelliten
display.setCursor(0, 34);
display.print("Sats: ");
display.print(gps.satellites.value());
// Zeile 5: UTC-Zeit
display.setCursor(0, 46);
display.print("UTC: ");
if (gps.time.hour() < 10) display.print("0");
display.print(gps.time.hour());
display.print(":");
if (gps.time.minute() < 10) display.print("0");
display.print(gps.time.minute());
display.print(":");
if (gps.time.second() < 10) display.print("0");
display.print(gps.time.second());
} else {
display.setCursor(0, 0);
display.print("Kein GPS-Fix");
display.setCursor(0, 16);
display.print("Sats: ");
display.print(gps.satellites.value());
display.setCursor(0, 34);
display.print("Bitte ins Freie gehen");
display.setCursor(0, 46);
display.print("und warten...");
}
display.display();
}
void setup() {
Serial.begin(115200);
gpsSerial.begin(9600, SERIAL_8N1, 16, 17);
// OLED initialisieren
if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
Serial.println("OLED-Fehler!");
while (1);
}
display.clearDisplay();
display.setTextSize(2);
display.setCursor(20, 20);
display.print("GPS v2");
display.display();
delay(2000);
}
void loop() {
// Alle neu verfügbaren NMEA-Sätze einspeisen
while (gpsSerial.available() > 0) {
gps.encode(gpsSerial.read());
}
// Display alle 300 ms aktualisieren (≈ 3 fps)
static unsigned long lastUpdate = 0;
if (millis() - lastUpdate >= 300) {
lastUpdate = millis();
displayGPS();
}
// Zusätzlich: Rohdaten auf Serial ausgeben
while (gpsSerial.available() > 0) {
Serial.write(gpsSerial.read());
}
}
Was der Code macht
TinyGPSPlus::encode() füttert jedes eintreffende Zeichen in den Parser. Sobald eine vollständige NMEA-Sentence erkannt und geparst wurde, stehen die Werte in den Properties gps.location, gps.altitude, gps.speed etc. bereit. isValid() prüft, ob der jeweilige Wert tatsächlich aus einem gültigen Fix stammt – das verhindert Anzeigen von Fantasiezahlen.
Die Display-Aktualisierung läuft nur alle 300 ms, weil OLED-Updates relativ langsam sind und wir den seriellen Puffer nicht blockieren wollen.
Genauigkeit: gps.location.lat() mit 6 Nachkommastellen liefert ~11 cm numerische Auflösung. Die tatsächliche GPS-Genauigkeit des NEO-6M liegt bei 2,5 m CEP (50% der Messpunkte innerhalb eines Kreises von 2,5 m Radius). In der Praxis sind 3–5 m realistisch – mehr als ausreichend für Fahrzeug-Tracking, Wanderaufzeichnung und Drohnen-Failsafe.
Code-Stufe 3: SD-Karten-Logging mit Wegpunkten
Jetzt wird es ernst: Wir schreiben jeden GPS-Fix als strukturierten Datensatz auf eine microSD-Karte – mit Zeitstempel, Koordinaten, Höhe und Geschwindigkeit. Das gibt dir eine vollständige Track-Aufzeichnung, die du später in Google Earth, QGIS oder als CSV in Excel auswerten kannst.
1 Zusätzliche Bibliothek
SD-Karten-Library (im ESP32-Arduino-Core enthalten – keine separate Installation nötig):
- SD (ESP32) – bereits mit dem ESP32-Board-Support-Paket installiert
2 Der vollständige Code
/*
* Code-Stufe 3: GPS-Logging auf SD-Karte + OLED-Live-Anzeige
* Trackpunkte im CSV-Format: timestamp,lat,lon,alt,speed,satellites
* SD-Karte: SPI auf GPIO18(SCK),19(MISO),23(MOSI),5(CS)
*/
#include <TinyGPS++.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <SD.h>
#include <SPI.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_ADDR 0x3C
#define SD_CS 5
#define LOG_INTERVAL 5000 // Alle 5 Sekunden einen Wegpunkt loggen
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
TinyGPSPlus gps;
HardwareSerial gpsSerial(2);
File logFile;
unsigned long lastLog = 0;
unsigned long waypointCount = 0;
bool sdReady = false;
void setupSD() {
SPI.begin(18, 19, 23, SD_CS); // SCK, MISO, MOSI, CS
if (SD.begin(SD_CS)) {
sdReady = true;
// Neue Logdatei mit Zeitstempel im Namen
logFile = SD.open("/gpstrack.csv", FILE_APPEND);
if (logFile) {
// Header schreiben, falls Datei leer
if (logFile.size() == 0) {
logFile.println("timestamp,lat,lon,altitude_m,speed_kmh,satellites");
}
logFile.close();
Serial.println("SD-Karte bereit.");
}
} else {
Serial.println("SD-Karten-Fehler! Logging deaktiviert.");
sdReady = false;
}
}
void logWaypoint() {
if (!sdReady || !gps.location.isValid()) return;
logFile = SD.open("/gpstrack.csv", FILE_APPEND);
if (!logFile) {
sdReady = false;
return;
}
// CSV-Zeile: timestamp,lat,lon,alt,speed,sats
logFile.print(gps.date.year());
logFile.print("-");
if (gps.date.month() < 10) logFile.print("0");
logFile.print(gps.date.month());
logFile.print("-");
if (gps.date.day() < 10) logFile.print("0");
logFile.print(gps.date.day());
logFile.print("T");
if (gps.time.hour() < 10) logFile.print("0");
logFile.print(gps.time.hour());
logFile.print(":");
if (gps.time.minute() < 10) logFile.print("0");
logFile.print(gps.time.minute());
logFile.print(":");
if (gps.time.second() < 10) logFile.print("0");
logFile.print(gps.time.second());
logFile.print("Z,");
logFile.print(gps.location.lat(), 6);
logFile.print(",");
logFile.print(gps.location.lng(), 6);
logFile.print(",");
logFile.print(gps.altitude.meters(), 1);
logFile.print(",");
logFile.print(gps.speed.kmph(), 1);
logFile.print(",");
logFile.println(gps.satellites.value());
logFile.close();
waypointCount++;
// Kurze LED-Bestätigung (built-in LED auf GPIO2)
digitalWrite(2, HIGH);
delay(50);
digitalWrite(2, LOW);
}
void displayStatus() {
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
if (gps.location.isValid()) {
display.print("GPS OK S:");
display.print(gps.satellites.value());
} else {
display.print("GPS: KEIN FIX");
}
display.setCursor(0, 12);
display.print("SD:");
display.print(sdReady ? "OK" : "FEHLER");
display.setCursor(0, 24);
display.print("Logs: ");
display.print(waypointCount);
display.setCursor(0, 36);
display.print("Speed: ");
if (gps.speed.isValid()) {
display.print(gps.speed.kmph(), 1);
display.print(" km/h");
} else {
display.print("--");
}
display.setCursor(0, 48);
display.print("Alt: ");
if (gps.altitude.isValid()) {
display.print(gps.altitude.meters(), 0);
display.print(" m");
} else {
display.print("--");
}
display.display();
}
void setup() {
Serial.begin(115200);
gpsSerial.begin(9600, SERIAL_8N1, 16, 17);
pinMode(2, OUTPUT);
digitalWrite(2, LOW);
if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
Serial.println("OLED-Fehler!");
while (1);
}
display.clearDisplay();
display.setTextSize(2);
display.setCursor(15, 20);
display.print("GPS v3");
display.display();
delay(2000);
setupSD();
Serial.println("GPS Logger gestartet.");
}
void loop() {
while (gpsSerial.available() > 0) {
gps.encode(gpsSerial.read());
}
// Wegpunkt loggen im Intervall
if (millis() - lastLog >= LOG_INTERVAL) {
lastLog = millis();
logWaypoint();
}
// Display alle 500 ms aktualisieren
static unsigned long lastDisplay = 0;
if (millis() - lastDisplay >= 500) {
lastDisplay = millis();
displayStatus();
}
}
Ausgabeformat auf der SD-Karte: Die CSV-Datei verwendet ISO-8601-Zeitstempel (UTC) und enthält alle relevanten Daten in einem Format, das direkt in Google Earth importiert werden kann. Für Google Earth konvertierst du die CSV mit einem kurzen Python-Script oder Onlinetool in KML. Alternativ öffnest du sie in QGIS (Layer → Layer hinzufügen → Getrennte Textdatei) und hast sofort eine Kartenansicht deines Tracks.
Log-Intervall optimieren: Bei LOG_INTERVAL 5000 (5 s) erzeugst du 720 Wegpunkte pro Stunde. Auf einer 8 GB-Karte sind das Millionen von Stunden. Für Wanderungen reichen 10–30 s völlig. Für Fahrzeug-Tracking mit Kurvenerfassung sind 1–5 s besser. Bedenke: Jeder SD-Schreibzugriff zieht kurz ~30–50 mA – bei Akkubetrieb lieber seltener loggen.
Erweiterungen: WLAN, MQTT und Bluetooth
Der ESP32 kann weit mehr als nur Daten lokal speichern. Seine eingebaute Konnektivität macht ihn zur perfekten Brücke zwischen GPS-Rohdaten und der vernetzten Welt. Hier drei Erweiterungsideen für Fortgeschrittene:
WiFi Live-Tracking via MQTT
Statt nur auf SD-Karte zu loggen, sendest du jeden Wegpunkt per MQTT an einen Broker (z. B. Mosquitto auf einem Raspberry Pi oder einen Cloud-Dienst). Der Vorteil: Du siehst die Position live auf einer Karte, ohne die SD-Karte auslesen zu müssen. Das funktioniert natürlich nur im WLAN-Empfangsbereich – perfekt für Werksgelände, Campus oder den eigenen Garten. Für unterwegs bräuchtest du ein GSM-Modul (SIM800L) oder LoRa für große Reichweiten.
Ein minimaler MQTT-Publish sieht so aus:
// Im setup() nach WiFi-Verbindung:
client.setServer("192.168.1.100", 1883);
// Im loop() nach gültigem Fix:
char payload[128];
snprintf(payload, sizeof(payload),
"{\"lat\":%.6f,\"lon\":%.6f,\"alt\":%.1f}",
gps.location.lat(), gps.location.lng(), gps.altitude.meters());
client.publish("gps/tracker/location", payload);
Bluetooth-Gateway zum Smartphone
Der ESP32 hat Bluetooth Classic und BLE an Bord. Du kannst den Tracker so konfigurieren, dass er sich als Bluetooth-Serial-Device meldet und die GPS-Daten an eine Smartphone-App streamt – ganz ohne WLAN. Mit Serial-Bluetooth reichen 5 Zeilen Code, um eine virtuelle COM-Schnittstelle aufzubauen, die jede Android-App mit Bluetooth-Serial-Unterstützung lesen kann.
GPS-Disziplinierten NTP-Server bauen
GPS-Satelliten senden nicht nur Positionen, sondern auch eine hochpräzise Zeitreferenz – die GPS-Zeit weicht nur wenige Nanosekunden von der koordinierten Weltzeit ab. Mit dem PPS-Signal (Pulse Per Second) des NEO-6M kannst du den ESP32 zu einem Stratum-1-NTP-Server machen, der dein gesamtes Heimnetzwerk mit mikrosekundengenauer Zeit versorgt. Der NEO-6M gibt auf vielen Breakouts einen 1PPS-Pin aus – ein Rechtecksignal mit steigender Flanke genau zu jeder vollen GPS-Sekunde.
Praxisanwendungen – wofür du den Tracker einsetzt
🏔️ Outdoor & Wandern
Zeichne deine Wanderroute mit Höhenprofil auf, finde zurück zum Ausgangspunkt und teile den Track als GPX-Datei mit anderen. Dank SD-Karte gehen auch bei stundenlangen Touren keine Daten verloren. Mit einer Powerbank oder einem 2.500 mAh-Akku läuft das System 8+ Stunden.
🚗 Fahrzeug-Tracking
Versteckt im Handschuhfach (mit externer GPS-Antenne auf dem Armaturenbrett) loggt der Tracker jede Fahrt. Später wertest du Routen, Geschwindigkeiten und Stopps aus – ideal für Flottenmanagement im Kleinformat oder als Diebstahl-Prävention. Über WLAN könntest du die Daten sogar live an einen MQTT-Broker streamen.
🛸 Drohnen & RC-Modelle
Als Backup-Tracker an der Drohne befestigt, findest du dein Modell auch nach einem Fly-Away wieder. Das geringe Gewicht (~35 g für ESP32 + NEO-6M + Akku) belastet die meisten Copter kaum. Bei 5 Hz Update-Rate bekommst du selbst bei schnellen Flugmanövern eine dichte Track-Aufzeichnung.
📡 IoT & Smart City
Kombiniere den Tracker mit LoRa (z. B. SX1278 am ESP32) und du hast einen stromsparenden GPS-Sender mit Kilometern Reichweite – ohne Mobilfunk. Ideal für Nutztier-Tracking, Wetterballons oder Flottenmanagement in abgelegenen Gebieten. Der ESP32 schläft 99% der Zeit und wacht nur für GPS-Fix + LoRa-Transmission auf.
Typische Probleme und Lösungen
„Kein GPS-Fix" – das häufigste Problem. GPS-Signale sind Mikrowellen bei 1,57542 GHz (L1-Band) mit einer Leistung von nur ~-130 dBm an der Erdoberfläche. Sie durchdringen keine Gebäude, keine Metalldächer, oft nicht mal dichtes Blätterdach. Erste Regel: Geh raus ins Freie mit freier Sicht zum Himmel. Zweite Regel: Gib dem Modul Zeit – ein Kaltstart dauert bis zu zwei Minuten.
OLED bleibt schwarz? Prüfe die I²C-Adresse deines Displays mit einem I²C-Scanner-Sketch. Manche SSD1306-Module nutzen 0x3D statt 0x3C. Der Display-Treiber sollte außerdem mit SSD1306_SWITCHCAPVCC initialisiert werden – das ist der interne Ladungspumpen-Modus, der bei 3,3 V korrekt arbeitet.
SD-Karte wird nicht erkannt? Viele günstige Micro-SD-Adapter haben eigenwillige Pinbelegungen. Prüfe mit einem Multimeter, ob VCC und GND tatsächlich am SD-Kartenslot ankommen. Formatiere die Karte als FAT32 (nicht exFAT!). Dateinamen auf 8.3-Format beschränken (z. B. gpstrack.csv). Und: Nicht alle SD-Karten arbeiten zuverlässig mit SPI – Class-4-Karten von Sandisk oder Kingston sind die beste Wahl.
GPS-Daten springen? Ein gewisses Rauschen ist normal. Der NEO-6M hat 2,5 m CEP unter optimalen Bedingungen – in der Stadt mit Mehrwege-Empfang (Multipath) können es auch mal 10 m sein. Für smoothere Tracks kannst du in der Firmware einen gleitenden Mittelwert oder einen Kalman-Filter implementieren. TinyGPS++ bietet dafür keine eingebaute Lösung – aber genau das ist der Vorteil eigener Hardware: Du hast den vollen Durchgriff auf die Rohdaten.
Jetzt deinen GPS-Tracker bauen
Alle Komponenten für dieses Projekt findest du bei makeroo – inklusive des ESP32 DevKit, des NEO-6M GPS-Moduls, des 0.96" OLED-Displays und des TP4056-Lademoduls für den Akkubetrieb. Dazu das passende SD-Karten-Modul und alles an Kleinmaterial, was du für den Aufbau brauchst.
Fragen zum Aufbau? Schreib uns – wir helfen gern weiter.
Fazit: GPS-Tracking für Maker – offen, günstig, leistungsfähig
Mit ESP32 und NEO-6M hast du ein GPS-Tracking-System, das du komplett selbst in der Hand hast. Die drei Code-Stufen führen dich von den rohen NMEA-Daten über das Live-Display bis zum vollständigen SD-Karten-Logger – jede Stufe ist für sich funktionsfähig und du entscheidest, wie tief du einsteigst.
Die Komponentenkosten liegen bei unter 25 €, der Stromverbrauch lässt sich per Deep-Sleep auf Tage strecken, und mit WLAN/Bluetooth stehen dir alle Türen für IoT-Erweiterungen offen: MQTT-Live-Tracking, Bluetooth-Gateway zum Smartphone oder direkte HTTP-POSTs an deinen eigenen Server.
Der größte Vorteil gegenüber kommerziellen Trackern: Deine Daten gehören dir. Kein Abo, keine fremde Cloud, kein Hersteller, der nach zwei Jahren den Dienst einstellt. Du hast den Quellcode, du hast die Hardware – und du hast die Freiheit, das System genau so weiterzuentwickeln, wie du es brauchst.
Viel Spaß beim Bauen und guten Satellitenempfang! 🛰️