GNSS 定位模块实战:ATGM336H GPS 追踪器 DIY,让设备知道自己在哪

GNSS 定位模块实战:ATGM336H GPS 追踪器 DIY,让设备知道自己在哪

为什么需要定位功能?

做物联网项目的同学肯定遇到过这种需求:设备在哪?怎么让它上报位置?共享单车、物流追踪、宠物定位、户外探险记录仪……这些场景都离不开 GNSS 定位。

今天咱们用国产的 ATGM336H 模块(北斗 + GPS 双模)做一个简易 GPS 追踪器,成本不到 50 元,还能把位置数据存到 SD 卡或者通过 4G 发送到服务器。

⚠️ 注意:GNSS 模块需要室外空旷环境才能正常定位,室内测试基本没信号。我一开始在办公室折腾了半小时没反应,还以为模块坏了,拿到楼下一分钟就搜到 8 颗卫星……

需要准备什么?

物品 型号/规格 价格
GNSS 模块 ATGM336H-5N ¥25
开发板 Arduino Nano / ESP32 ¥15-25
OLED 显示屏 0.96 寸 I2C ¥8
microSD 卡模块 SPI 接口 ¥5
锂电池 3.7V 500mAh ¥10
充电模块 TP4056 ¥2
杜邦线 公对公/母对母 ¥5
总计 ¥70-80

ATGM336H 是中科微的国产北斗 + GPS 双模模块,灵敏度不错,冷启动 30 秒左右就能定位。相比进口的 NEO-6M,价格便宜一半,性能差距不大。

硬件连接

ATGM336H 使用 UART 串口通信,默认波特率 9600。接线非常简单:

ATGM336H Arduino Nano
VCC 5V
GND GND
TX D10 (RX)
RX D11 (TX)

注意:模块的 TX 接开发板的 RX,RX 接开发板的 TX,别接反了。如果用 ESP32,可以在代码里自定义软串口引脚。

OLED 和 SD 卡模块用 I2C 和 SPI 接口:

OLED Arduino
VCC 3.3V
GND GND
SCL A5
SDA A4
SD 卡模块 Arduino
VCC 5V
GND GND
CS D4
MOSI D11
MISO D12
SCK D13

第一步:读取 NMEA 数据

GNSS 模块会持续输出 NMEA 0183 格式的数据,这是一套标准的航海电子设备通信协议。常见的数据行有:

  • $GPGGA – 定位信息(时间、经纬度、高度、卫星数)
  • $GPRMC – 推荐最小定位信息(时间、状态、经纬度、速度)
  • $GPGSV – 卫星状态(可见卫星列表)

我们主要解析 $GPGGA$GPRMC。先写个最简单的读取程序:

#include 

SoftwareSerial gpsSerial(10, 11); // RX, TX

void setup() {
  Serial.begin(9600);
  gpsSerial.begin(9600);
  Serial.println("GPS Tracker Starting...");
}

void loop() {
  if (gpsSerial.available()) {
    String line = gpsSerial.readStringUntil('\n');
    Serial.println(line);
  }
}

上传代码后打开串口监视器,你会看到类似这样的输出:

$GPGGA,083522.00,3123.45678,N,12134.56789,E,1,08,1.2,50.5,M,0.0,M,,*6A
$GPRMC,083522.00,A,3123.45678,N,12134.56789,E,0.5,123.4,120326,,*1B
$GPGSV,3,1,12,01,45,123,45,02,30,234,40,03,60,345,42,04,20,056,38*78

如果全是空行或者 $GPGGA 里卫星数是 0,说明还没定位成功。拿到室外等 1-2 分钟,看到卫星数大于 4 就可以继续了。

第二步:解析经纬度

NMEA 的经纬度格式有点奇葩:DDMM.MMMMMM(度 + 分),不是我们熟悉的十进制度数。需要转换一下:

// 将 NMEA 格式转换为十进制度数
float convertToDecimalDegrees(String nmeaCoord, char direction) {
  int degree = nmeaCoord.substring(0, 2).toInt();
  float minute = nmeaCoord.substring(2).toFloat();
  float decimal = degree + (minute / 60.0);

  // 南纬或西经需要取负
  if (direction == 'S' || direction == 'W') {
    decimal = -decimal;
  }
  return decimal;
}

// 解析 GPGGA 语句
void parseGPGGA(String sentence) {
  // $GPGGA,时间,纬度,N/S,经度,E/W,定位质量,卫星数,HDOP,高度...
  int parts[20];
  int idx = 0;
  int start = 0;

  for (int i = 0; i <= sentence.length(); i++) {
    if (sentence[i] == ',' || sentence[i] == '*') {
      String part = sentence.substring(start, i);
      if (idx < 20) {
        // 存储每个字段
      }
      start = i + 1;
      idx++;
    }
  }

  // 提取关键字段
  String time = parts[0];
  String lat = parts[1];
  char latDir = parts[2].charAt(0);
  String lon = parts[3];
  char lonDir = parts[4].charAt(0);
  int satCount = parts[6].toInt();
  float altitude = parts[8].toFloat();

  float latDec = convertToDecimalDegrees(lat, latDir);
  float lonDec = convertToDecimalDegrees(lon, lonDir);

  Serial.print("时间:"); Serial.println(time);
  Serial.print("纬度:"); Serial.println(latDec, 6);
  Serial.print("经度:"); Serial.println(lonDec, 6);
  Serial.print("卫星数:"); Serial.println(satCount);
  Serial.print("高度:"); Serial.print(altitude); Serial.println(" 米");
}

更简单的做法是用现成的库,比如 TinyGPSPlus,解析更可靠:

#include 
#include 

TinyGPSPlus gps;
SoftwareSerial gpsSerial(10, 11);

void setup() {
  Serial.begin(9600);
  gpsSerial.begin(9600);
}

void loop() {
  while (gpsSerial.available()) {
    gps.encode(gpsSerial.read());
  }

  if (gps.location.isUpdated()) {
    Serial.print("纬度:");
    Serial.println(gps.location.lat(), 6);
    Serial.print("经度:");
    Serial.println(gps.location.lng(), 6);
    Serial.print("卫星数:");
    Serial.println(gps.satellites.value());
    Serial.print("高度:");
    Serial.print(gps.altitude.meters());
    Serial.println(" 米");
  }
}

第三步:显示和存储

加上 OLED 显示屏,可以实时看到当前位置:

#include 
#include 

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

void displayGPS(float lat, float lon, int sats, float alt) {
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 0);

  display.println("GPS Tracker");
  display.println("-----------");
  display.print("Lat: ");
  display.println(lat, 5);
  display.print("Lon: ");
  display.println(lon, 5);
  display.print("Sats: ");
  display.println(sats);
  display.print("Alt: ");
  display.print(alt);
  display.println("m");

  display.display();
}

如果要记录轨迹,可以把数据存到 SD 卡:

#include 
#include 

File logFile;

void setupSD() {
  if (!SD.begin(4)) {
    Serial.println("SD 卡初始化失败!");
    return;
  }
  Serial.println("SD 卡就绪");
}

void logGPS(float lat, float lon, int sats, float alt) {
  String fileName = "track" + String(millis()) + ".txt";
  logFile = SD.open(fileName, FILE_WRITE);

  if (logFile) {
    logFile.print(millis());
    logFile.print(",");
    logFile.print(lat, 6);
    logFile.print(",");
    logFile.print(lon, 6);
    logFile.print(",");
    logFile.print(sats);
    logFile.print(",");
    logFile.println(alt, 1);
    logFile.close();
  }
}

数据格式是 CSV,方便后续用 Excel 或地图工具可视化。

第四步:低功耗设计

如果是电池供电的追踪器,功耗很重要。ATGM336H 工作电流约 20mA,ESP32 深度睡眠可以做到 10μA。

策略:每 5 分钟唤醒一次,定位 30 秒,记录位置,然后继续睡眠。

#include 

#define uS_TO_S_FACTOR 1000000ULL
#define TIME_TO_SLEEP  300  // 5 分钟

void setup() {
  // 初始化 GPS、SD 等
  esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
  Serial.println("准备进入深度睡眠...");
}

void loop() {
  // 唤醒后执行
  readAndLogGPS();

  // 再次睡眠
  esp_deep_sleep_start();
}

这样一节 1000mAh 的电池可以用好几个月。

常见问题排查

问题 1:模块一直输出空数据

  • 原因:室内无信号或天线没接好
  • 解决:拿到室外空旷处,检查天线是否拧紧(ATGM336H 需要外接有源天线)

问题 2:经纬度一直是 0

  • 原因:还没完成首次定位(冷启动需要 30-60 秒)
  • 解决:耐心等待,保持模块静止,确保天线朝上

问题 3:串口乱码

  • 原因:波特率不匹配或接线错误
  • 解决:确认波特率是 9600,检查 TX/RX 是否接反

问题 4:SD 卡写入失败

  • 原因:供电不足或接触不良
  • 解决:用独立 5V 供电给 SD 卡模块,检查 CS 引脚是否正确

问题 5:定位漂移严重

  • 原因:多路径效应(高楼反射)或卫星数太少
  • 解决:确保天空视野开阔,等待卫星数>8 再记录,软件上做滤波处理

扩展应用

有了基础定位功能,可以继续扩展:

  1. 4G 远程追踪:加上 EC200U 模块,把位置发送到服务器,手机随时查看
  2. 电子围栏:设定活动范围,超出范围发送报警
  3. 轨迹回放:把 SD 卡数据导入 Google Earth,回放运动轨迹
  4. 速度计算:根据位置变化和时间差计算移动速度
  5. 北斗短报文:用 RD 系列模块实现无信号区通信(需要 SIM 卡)

总结

GNSS 定位是物联网项目的常用功能,ATGM336H 作为国产模块性价比高,配合 Arduino 或 ESP32 可以快速实现定位追踪器。关键点:

  • 室外测试,室内没信号
  • 用 TinyGPSPlus 库简化解析
  • 低功耗设计延长电池寿命
  • CSV 格式存储方便后续处理

希望这篇博客文章对您有所帮助!


相关资源: