GPS-трекер на NEO-6M: координати, швидкість і точка на карті
Модуль NEO-6M ловить супутники, бібліотека TinyGPSPlus розбирає NMEA, а ESP32 показує координати, швидкість, висоту й пройдену відстань на веб-сторінці з посиланням на OpenStreetMap.
Що отримаємо
GPS-модуль щосекунди надсилає текстові рядки у форматі NMEA ($GPRMC,..., $GPGGA,...). Бібліотека TinyGPSPlus перетворює їх на зрозумілі числа: широту, довготу, висоту, швидкість, курс, кількість супутників і точний час UTC. Ми виведемо все це на сторінку, додамо лічильник пройденої відстані й кнопку «Відкрити на карті».
Компоненти
- ESP32 DevKit
- Модуль u-blox NEO-6M (GY-GPS6MV2) з керамічною антеною
- Для першого запуску — місце біля вікна або надворі
Підключення
Модуль працює через UART. У ESP32 три апаратні UART — використаємо UART2.
| NEO-6M | ESP32 |
|---|---|
| VCC | 3V3 (або 5V — на більшості модулів є стабілізатор) |
| GND | GND |
| TX | GPIO 16 (RX2) |
| RX | GPIO 17 (TX2) |
TX модуля йде на RX плати, і навпаки.
Бібліотека
TinyGPSPlus (Mikal Hart).
Код
#include <WiFi.h>
#include <WebServer.h>
#include <TinyGPSPlus.h>
const char* WIFI_SSID = "YOUR_SSID";
const char* WIFI_PASS = "YOUR_PASSWORD";
const int GPS_RX = 16; // до TX модуля
const int GPS_TX = 17; // до RX модуля
HardwareSerial gpsSerial(2);
TinyGPSPlus gps;
WebServer server(80);
double lastLat = 0, lastLng = 0;
bool haveLast = false;
double distanceM = 0;
unsigned long lastTrack = 0;
const char PAGE[] = R"rawliteral(
<!doctype html><html><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>GPS</title>
<style>body{font-family:system-ui;max-width:480px;margin:30px auto;padding:0 16px}
td{padding:6px 10px}td:first-child{color:#667}a{font-size:1.1rem}</style>
</head><body><h2>GPS tracker</h2>
<table id="t"></table>
<p><a id="map" target="_blank">Open on map</a></p>
<script>
async function update() {
const d = await (await fetch('/data')).json();
const rows = [
['Fix', d.fix ? 'yes' : 'searching…'], ['Satellites', d.sats],
['Latitude', d.lat], ['Longitude', d.lng], ['Altitude', d.alt + ' m'],
['Speed', d.kmph + ' km/h'], ['Distance', (d.dist / 1000).toFixed(2) + ' km'],
['UTC', d.time]
];
document.getElementById('t').innerHTML = rows.map(r => `<tr><td>${r[0]}</td><td>${r[1]}</td></tr>`).join('');
const a = document.getElementById('map');
a.href = d.fix ? `https://www.openstreetmap.org/?mlat=${d.lat}&mlon=${d.lng}#map=17/${d.lat}/${d.lng}` : '#';
}
update(); setInterval(update, 2000);
</script></body></html>
)rawliteral";
void handleData() {
bool fix = gps.location.isValid() && gps.location.age() < 5000;
char json[256];
snprintf(json, sizeof(json),
"{\"fix\":%s,\"sats\":%u,\"lat\":%.6f,\"lng\":%.6f,\"alt\":%.0f,"
"\"kmph\":%.1f,\"dist\":%.0f,\"time\":\"%02d:%02d:%02d\"}",
fix ? "true" : "false", (unsigned)gps.satellites.value(),
gps.location.lat(), gps.location.lng(), gps.altitude.meters(),
gps.speed.kmph(), distanceM,
gps.time.hour(), gps.time.minute(), gps.time.second());
server.send(200, "application/json", json);
}
void setup() {
Serial.begin(115200);
gpsSerial.begin(9600, SERIAL_8N1, GPS_RX, GPS_TX);
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) delay(500);
Serial.println(WiFi.localIP());
server.on("/", [] { server.send(200, "text/html", PAGE); });
server.on("/data", handleData);
server.begin();
}
void loop() {
// Згодовуємо бібліотеці всі байти, що прийшли від модуля
while (gpsSerial.available()) gps.encode(gpsSerial.read());
server.handleClient();
// Раз на 5 с додаємо відрізок шляху; дрібні «стрибки» на місці ігноруємо
if (millis() - lastTrack > 5000 && gps.location.isValid() && gps.hdop.hdop() < 3.0) {
lastTrack = millis();
double lat = gps.location.lat(), lng = gps.location.lng();
if (haveLast) {
double d = TinyGPSPlus::distanceBetween(lastLat, lastLng, lat, lng);
if (d > 5) {
distanceM += d;
lastLat = lat;
lastLng = lng;
}
} else {
lastLat = lat;
lastLng = lng;
haveLast = true;
}
}
if (millis() > 10000 && gps.charsProcessed() < 10) {
Serial.println("No data from GPS — check wiring (TX↔RX)");
delay(2000);
}
}
Перший запуск
- Холодний старт триває від 30 с до кількох хвилин — модулю потрібно завантажити альманах із супутників. Під відкритим небом швидше, у приміщенні може не впіймати зовсім.
- Коли з'явиться фіксація, на модулі почне блимати світлодіод (раз на секунду).
- Резервна батарейка на модулі зберігає альманах — наступні старти займатимуть секунди.
Точність
- Типова похибка NEO-6M — 2.5–5 м на відкритому просторі.
hdop(геометричний фактор точності) < 2 — відмінно, 2–5 — нормально, > 5 — координатам краще не довіряти. Ми ігноруємо точки з HDOP ≥ 3, щоб «блукання» на місці не накручувало відстань.
Ідеї
- Автономний трекер: GPS + SD-карта, запис GPX-треку кожні 10 с, потім імпорт у Google Earth чи Strava.
- Геозона: сповіщення в Telegram, коли велосипед покинув двір (див. проєкт «Telegram-бот»).
- Точний годинник без інтернету — час GPS точний до мікросекунд.
- Для роботи без Wi-Fi: вивід на OLED-дисплей з проєкту «Точний годинник».