IoTLabs

Nghiên cứu, Sáng tạo và Thử nghiệm

Series ESP32 & Cảm biến: Bài 35 – Đọc cảm biến GPS NEO-6M: lấy toạ độ GPS & theo dõi realtime

GPS NEO-6M là module GPS rất phổ biến để lấy vĩ độ/kinh độ, tốc độ, độ cao, số vệ tinh, rồi đẩy lên MQTT/Dashboard để theo dõi realtime (map + lịch sử di chuyển).

1) Chuẩn bị

  • ESP32 (DevKit / ESP32-S3/C3 đều được)
  • GPS NEO-6M (kèm anten gốm hoặc anten rời)
  • Dây dupont
  • (Tuỳ chọn) pin cúc áo cho chân VBAT (giữ dữ liệu nhanh fix)

2) Nối dây NEO-6M ↔ ESP32 (UART)

NEO-6M thường có các chân: VCC, GND, TXD, RXD.

Kết nối khuyến nghị (ESP32 DevKit dùng UART2):

NEO-6MESP32
VCC5V (đa số module có regulator) hoặc 3V3 (nếu module hỗ trợ)
GNDGND
TXDGPIO16 (RX2)
RXDGPIO17 (TX2) (tuỳ chọn, chỉ cần nếu bạn muốn gửi lệnh cấu hình)

Lưu ý điện áp: NEO-6M TXD thường là mức TTL 3.3V (an toàn cho ESP32). Nhưng tuỳ module, tốt nhất kiểm tra thông số/board bạn mua.

3) Nguyên lý hoạt động (hiểu nhanh)

  • GPS xuất dữ liệu dạng NMEA sentence qua UART (mặc định thường 9600 baud).
  • Ta đọc chuỗi NMEA và parse ra:
    • lat, lng
    • alt (m)
    • speed (km/h)
    • course (độ)
    • satellites, hdop
    • fix hợp lệ hay chưa

Để “realtime” ổn định:

  • Chỉ publish khi có fix hợp lệ
  • Publish đều mỗi 1–2 giây (hoặc theo chu kỳ GPS)

4) Cài thư viện Arduino

Dùng thư viện parse GPS dễ nhất:

  • TinyGPSPlus (Arduino Library Manager)

Vào Arduino IDE → Library Manager → tìm TinyGPSPlus → Install.

5) Code ESP32 (Arduino) – đọc GPS + publish MQTT realtime

Ví dụ dưới đây:

  • Đọc GPS qua HardwareSerial(2)
  • Parse bằng TinyGPSPlus
  • Publish MQTT JSON theo topic chuẩn IoTLabs:
    • iotlabs/{project_id}/{device_id}/telemetry

Thay Wi-Fi/MQTT config theo hệ của bạn.

#include <WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <TinyGPSPlus.h>

#define GPS_RX 16     // ESP32 RX2  <- GPS TXD
#define GPS_TX 17     // ESP32 TX2  -> GPS RXD (optional)
#define GPS_BAUD 9600

#define PUBLISH_EVERY_MS 2000

// --- WiFi/MQTT config
const char* WIFI_SSID = "YOUR_WIFI";
const char* WIFI_PASS = "YOUR_PASS";

const char* MQTT_HOST = "broker.iotlabs.vn";
const int   MQTT_PORT = 1883; // TLS: 8883
const char* MQTT_USER = "YOUR_USER";
const char* MQTT_PASS = "YOUR_PASS";

const char* PROJECT_ID = "demo_project";
const char* DEVICE_ID  = "esp32_gps_neo6m_01";

WiFiClient espClient;
PubSubClient mqtt(espClient);

TinyGPSPlus gps;
HardwareSerial GPS(2);

unsigned long lastPub = 0;

uint32_t nowSeconds() {
  // Nếu bạn có time sync chuẩn (NTP/time service) thì dùng unix seconds thật.
  return (uint32_t)(millis() / 1000);
}

void wifiConnect() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  while (WiFi.status() != WL_CONNECTED) delay(300);
}

void mqttConnect() {
  mqtt.setServer(MQTT_HOST, MQTT_PORT);
  while (!mqtt.connected()) {
    String clientId = String("iotlabs-") + DEVICE_ID;
    mqtt.connect(clientId.c_str(), MQTT_USER, MQTT_PASS);
    delay(500);
  }
}

void publishTelemetry() {
  // Chỉ publish khi có fix hợp lệ
  if (!gps.location.isValid() || gps.location.age() > 5000) return;

  const double lat = gps.location.lat();
  const double lng = gps.location.lng();

  const double alt = gps.altitude.isValid() ? gps.altitude.meters() : NAN;
  const double spd = gps.speed.isValid() ? gps.speed.kmph() : NAN;
  const double crs = gps.course.isValid() ? gps.course.deg() : NAN;

  const int sats = gps.satellites.isValid() ? gps.satellites.value() : -1;
  const double hdop = gps.hdop.isValid() ? gps.hdop.hdop() : NAN;

  String topic = String("iotlabs/") + PROJECT_ID + "/" + DEVICE_ID + "/telemetry";

  StaticJsonDocument<384> doc;
  doc["ts"] = nowSeconds();

  JsonObject metrics = doc.createNestedObject("metrics");
  metrics["lat"] = lat;
  metrics["lng"] = lng;
  if (!isnan(alt)) metrics["alt_m"] = alt;
  if (!isnan(spd)) metrics["speed_kmh"] = spd;
  if (!isnan(crs)) metrics["course_deg"] = crs;
  if (sats >= 0) metrics["sat"] = sats;
  if (!isnan(hdop)) metrics["hdop"] = hdop;

  JsonObject tags = doc.createNestedObject("tags");
  tags["sensor"] = "neo6m";
  tags["fix"] = true;

  char payload[384];
  size_t n = serializeJson(doc, payload);

  mqtt.publish(topic.c_str(), payload, n);
}

void setup() {
  Serial.begin(115200);

  // GPS UART
  GPS.begin(GPS_BAUD, SERIAL_8N1, GPS_RX, GPS_TX);

  wifiConnect();
  mqttConnect();

  Serial.println("GPS NEO-6M started. Waiting for fix...");
}

void loop() {
  if (WiFi.status() != WL_CONNECTED) wifiConnect();
  if (!mqtt.connected()) mqttConnect();
  mqtt.loop();

  // Feed GPS parser
  while (GPS.available()) {
    gps.encode(GPS.read());
  }

  // Publish periodically
  if (millis() - lastPub >= PUBLISH_EVERY_MS) {
    lastPub = millis();

    if (gps.location.isValid()) {
      Serial.printf("GPS: %.6f, %.6f | sat=%d | hdop=%.1f\n",
        gps.location.lat(), gps.location.lng(),
        gps.satellites.isValid() ? gps.satellites.value() : -1,
        gps.hdop.isValid() ? gps.hdop.hdop() : -1.0
      );
    } else {
      Serial.println("GPS: no fix yet...");
    }

    publishTelemetry();
  }
}

6) Dashboard realtime gợi ý (IoTLabs)

Card “Live Location”

  • Lat/Lng + số vệ tinh + HDOP
  • Trạng thái: fix = true/false

Map

  • Marker theo lat/lng (Leaflet/Mapbox/Google Maps)
  • Vẽ polyline lịch sử (last 1h / last 24h)

Rule cảnh báo

  • Nếu fix=false liên tục > 60s → cảnh báo mất GPS
  • Nếu sat < 4 hoặc hdop > 3.0 → chất lượng định vị kém

7) Mẹo để bắt GPS nhanh và ổn định

  • Đặt anten hướng lên trời, tránh gần kim loại/nguồn nhiễu.
  • Lần đầu “cold start” có thể 1–5 phút (tuỳ môi trường).
  • Nếu ở trong nhà, gần cửa sổ vẫn có thể yếu → tốt nhất test ngoài trời.
  • Nếu không thấy dữ liệu: kiểm tra baud 9600, dây TX/RX có bị đảo không.

8) Gợi ý format dữ liệu “chuẩn để mở rộng”

Ngoài lat/lng, bạn có thể bổ sung:

  • accuracy_m (nếu module/firmware hỗ trợ)
  • geohash (để query nhanh theo vùng)
  • trip_id / route_id (để gom tuyến)