元器件 · 传感器

NEO-6M GPS 模块

NEO-6M 通过串口为户外机器人提供绝对位置。本文讲怎么读取 NMEA 语句、为什么 HDOP 比标称精度更重要,以及它做不到什么。

它是什么

GNSS 接收机:一块接收卫星星座广播的授时信号、并根据各信号到达时刻之差解算自身位置的芯片。NEO-6M 是入门机器人最先见到的模块——u-blox 接收机、陶瓷贴片天线和一个串口。

车上唯一能报告绝对位置的传感器。本站其他传感器——编码器、IMU、反射阵列——测的都是变化量或局部特征。正是 GPS 让里程计漂移不至于无界累积,也正是它让一台户外小车可以被告知一公里外的目的地。

工作原理

不用你问它,它 9600 波特率 NMEA 0183 句子往外说——一逗号分隔行一消息,每秒好几条。

其中两条就承载了机器人需要的一切:

$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,...   fix quality, sats, HDOP
$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,...      validity, position, speed, course

读到定位解之后,先做有效性筛选。RMC 里的状态字段 A 表示有效、V 表示无效,而 GGA 里的定位质量为 0 表示无解——这两种情况下,句子照样带着看起来很合理的坐标。HDOP 字段抓的是更隐蔽的失效:它描述的是卫星几何分布,而几何分布差时,它会给出一个自信却偏出几十米的答案。

纬度是度分格式(4807.038 是 48° 07.038′),不是度。转一次,再转相对固定原点的米——算术在这里——下游全是控制器懂的单位。

什么时候用它

机器人在户外作业、行驶距离超过里程计能撑住的范围时用它:航点车、割草机、测绘平台、农业机器人。粗略地说,开了三十米之后,纯靠航位推算累积的误差就已经超过一次 GPS 定位所携带的误差。

室内永远别用,追求精度的时候也别用:2.5 m 对一辆 30 cm 宽的车来说实在太远了。GPS 告诉你身处田地的哪一块;轮子编码器告诉你在这块里的哪一米以内。两者都用,并做融合。

按价值排序的升级路径:先提高更新率(5 或 10 Hz 能消除大部分数据陈旧问题),然后换多星座模块(GPS 加上 GLONASS 或 Galileo 能改善几何分布,也就是改善 HDOP),最后才轮到 RTK——它能达到厘米级,但需要基站、数据链路,以及一份不同的预算。

常见坑

  • **冷启动以分钟计,不是以秒计。**没有星历时,接收机必须以 50 bps 从卫星下载星历。机器人在户外首次上电时看起来像是死了,通常只是在等。
  • **它没有航向。**只有位置。要配上磁力计或陀螺仪;对地航向在约 1 m/s 以下毫无意义。
  • **往 RX 灌 5 V 会烧掉 3.3 V 模块。**查一下你的转接板有没有做电平转换。模块 TX 接 5 V 板子没问题。
  • **天线要的是天空,不是视野。**它上方的金属——包括你自己的底盘板——都会让你损失卫星。把它装在顶部,平放、朝上。
  • 绝不直接跟随原始定位解。在两次定位之间做航位推算,每来一个新解就修正一次;直接跟随定位解会让跟踪误差大一个数量级,转向次数是四倍。
  • **六位小数是 11 cm——而且已经超出 float 能存的精度。**换算成相对于任务原点的米,而不是拿原始的度数去做算术。

引脚与接线

引脚 名 作用
1 VCC 3.3–5 V。查自己转接板稳压没——很多 NEO-6M 板有
2 RX 模块收配置。裸模块 3.3 V 逻辑
3 TX 模块输出 NMEA。接 5 V 输入安全
4 GND 地
5 PPS 秒脉冲,对 GPS 时。大多数项目无视
GPS Arduino Uno 说明
VCC 5 V 板子有稳压 5 V;没有 3.3 V
GND GND
TX 4 脚(软串口 RX) 模块说,板子听
RX 经分压接 3 脚 只配置模块才要

RX 完全可以悬空。GPS 默认 9600 波特率 NMEA 一直流,不问也说,只读位置的车这就够。提更新率、静默无用句子才接 RX。

不用库读它

#include <SoftwareSerial.h>

SoftwareSerial gps(4, 3);          // RX from module TX, TX to module RX

char line[100];
uint8_t len = 0;

void setup() {
  Serial.begin(115200);
  gps.begin(9600);
}

void loop() {
  while (gps.available()) {
    char c = gps.read();
    if (c == '\n') {
      line[len] = '\0';
      if (strncmp(line, "$GPRMC", 6) == 0) handleRmc(line);
      len = 0;
    } else if (len < sizeof(line) - 1) {
      line[len++] = c;
    }
  }
}

void handleRmc(char *s) {
  // $GPRMC,time,status,lat,N/S,lon,E/W,speed,course,date,...
  char *field[13] = {nullptr};
  uint8_t n = 0;
  for (char *tok = strtok(s, ","); tok && n < 13; tok = strtok(nullptr, ",")) field[n++] = tok;

  if (n < 7 || field[2][0] != 'A') return;      // 'V' = void. Coordinates are still present
                                                 // and still meaningless. Gate on this.
  Serial.print(toDegrees(field[3], field[4][0]), 6);
  Serial.print(' ');
  Serial.println(toDegrees(field[5], field[6][0]), 6);
}

// NMEA gives ddmm.mmmm — degrees and DECIMAL MINUTES, not decimal degrees.
double toDegrees(const char *ddmm, char hemisphere) {
  double raw = atof(ddmm);
  int deg = (int)(raw / 100);
  double minutes = raw - deg * 100;
  double result = deg + minutes / 60.0;
  return (hemisphere == 'S' || hemisphere == 'W') ? -result : result;
}

最要紧两行正是大家跳的两行。

field[2][0] != 'A' 是有效性判据。没有定位解的接收机照样会发出格式完美的 RMC 句子并带着坐标——通常是上次已知位置,或者全零。不检查状态标志的代码,会自信地把机器人导航到大西洋中央,也就是 0°N 0°E 所在之处。

toDegrees 之所以存在,是因为 4807.038 不是 4807.038 度,也不是 48.07038 度,而是 48° 07.038′,也就是 48.1173°。把 NMEA 坐标当成十进制度来用,是最常见的 GPS 缺陷,而误差大到不可能忽略——这倒是它唯一的好处。

真正的项目请用 TinyGPS++。它把所有句子类型、校验和以及各种边界情况都处理掉了。上面这个手写版本值得跑一遍,好让你知道它究竟在做什么。

精度:数字真意思

数 典型 NEO-6M 含义
水平精度 约 2.5 m CEP 一半的定位解落在 2.5 m 内,另一半落在外面
更新率 默认 1 Hz,可配 5 Hz 在 1 m/s 下,1 Hz 的定位解在读到时最多已经旧了一米
冷启动 约 27 s,没有星历时以分钟计 没坏;它正以 50 bps 在下载
热启动 约 1 s 有备份电源维持星历
速度精度 约 0.1 m/s 确实不错——比位置精度还好

**CEP 是中位数,不是上界。**一半的定位解比 2.5 m 更差,而在建筑物附近这条长尾拖得更长。对一辆 30 cm 宽的车来说,“几米内的某个地方”不是一个位置——而是一片区域。

**HDOP 是要看的字段。**讲卫星几何不讲信号质量,自信答案偏几十米的就是它:

HDOP 质量 用吗
< 1 理想 用
1–2 极好 用
2–5 好 小心用
5–10 一般 只慢修正
> 10 差 丢弃这个定位解

有效标志和 HDOP 都要筛选。HDOP 为 12 的定位解不算定位解;那只是一个带小数点的猜测。

静态漂移,为什么要紧

把 GPS 模块纹丝不动地放十分钟,记录下每一个定位解。你会得到一团几米宽的散布点。什么都没动。

在写任何导航代码之前亲眼看到这一点,是最重要的一件事,因为直接跟随原始定位解为什么没有希望,答案就在这里。喂给控制器这样一团散布,控制器就会追着它跑,于是机器人持续蛇形前进却几乎没有进展。而在两次定位之间用编码器做航位推算、只把每个新定位解用来修正缓慢漂移的话,同一个控制器就能跟踪得很干净——航点导航教程里有具体数字。

配置模块

提高更新率是性价比最高的一项改动,值得在任何其他事情之前先做。u-blox 模块接受二进制 UBX 命令:

// UBX-CFG-RATE: 200 ms measurement period -> 5 Hz
const uint8_t setRate5Hz[] = {
  0xB5, 0x62, 0x06, 0x08, 0x06, 0x00,
  0xC8, 0x00,          // 200 ms
  0x01, 0x00,          // one measurement per solution
  0x01, 0x00,          // align to GPS time
  0xDE, 0x6A           // checksum
};

void setup() {
  gps.begin(9600);
  delay(100);
  gps.write(setRate5Hz, sizeof(setRate5Hz));
}

5 Hz 下 9600 波特率成瓶颈——全 NMEA 句子 200 ms 塞不下。要么波特率 38400,要么关不用的句子(GSV 尤其啰嗦,报车不需要的卫星细节)。

注意 NEO-6M 配置不存,模块无电池备份。每次开机发。

故障排查

现象 可能原因 解决
完全无数据 TX/RX 没交叉或波特率错 模块 TX → 板子 RX;先试 9600
有数据但永远没有定位解 室内 GPS 需要天空,一扇窗户通常不够
首次定位要几分钟 没有星历的冷启动 正常。到开阔的户外去等
位置 0,0 用了无效的定位解 按 RMC 状态标志 A 做筛选
位置离谱 把 ddmm.mmmm 当成了度 换算:deg + minutes/60
位置跳几米 接收机的正常噪声 正常现象。与里程计融合;绝不直接跟随原始定位解
定位解很自信但偏得很远 卫星几何分布差 丢弃 HDOP 高于约 5 的定位解
卫星比预期少 天线被遮挡 装在顶部朝上,避开底盘板
模块发烫死 RX 灌 5 V 进 3.3 V 接线前查板子有没有做电平转换
设成 5 Hz 后工作一会儿就停 9600 波特率带不动这些句子 提高波特率或关掉 GSV

选哪个模块

模块 星座 速率 说明
NEO-6M GPS 1–5 Hz 便宜默认。老但到处都是
NEO-M8N GPS + GLONASS + Galileo 最高 10 Hz 几何分布好、HDOP 好——合乎理性的升级
NEO-M9N 支持多频段 最高 25 Hz 城区环境明显更好
ZED-F9P(RTK) 多频 20 Hz 厘米精度,要基站数据链

升级路按序走。更新率去大半陈旧,白送。多星座接收机见星多,几何好即 HDOP 好——精度规格之外真实世界提升大,尤其楼边。RTK 能达到厘米级,对一台需要在田里沿着线条走的机器人来说是质变,但它需要基站、修正数据链路,以及大约十倍的预算。

任何价位、任何型号的 GPS 模块都不给你的一样东西:静止时的航向。对地航向是从连续位置推算出来的,所以在约 1 m/s 以下它全是噪声,零速时更毫无意义。一台需要知道自己朝哪儿的机器人,无论 GPS 多好,都需要磁力计或陀螺仪。

探索关联图谱

用到它的搭建

包含 NEO-6M GPS 模块 的项目、学习路线和模拟器。

问题

NEO-6M GPS 模块 常见问题

NEO-6M 实际能有多准?

大约有一半时间误差在 2.5 米以内,前提是天空视野开阔、卫星几何分布良好——这是数据手册上的数字,对一台在建筑物旁边或树下行驶的机器人来说是偏乐观的。重要的是,它精度变差时给出的是自信的错误答案,而不是明显的故障,所以每一个定位解都要按上报的定位质量和 HDOP 进行筛选,而不是因为解析成功就相信这个位置。

HDOP 是什么?为什么它比精度数字更重要?

水平精度因子(HDOP)描述的是当前可见卫星的几何分布。卫星在天空中分散开来,解算的条件就好;卫星挤在一起,解算的条件就差,而接收机照样会以十足的信心上报一个位置。HDOP 低于 2 算好,高于约 2.5 就值得丢弃。它是 GGA 句子里最有用的一个字段,却几乎没人去读。

GPS 模块能告诉机器人它朝向哪里吗?

不能。定位解只是一个位置,一台面朝北静止不动的机器人和一台面朝南的机器人,得到的定位解完全相同。对地航向是由连续的位置计算出来的,所以只有机器人真正在移动时才有意义——速度低于大约每秒 1 米时,它基本上就是噪声。户外漫游车会把 GPS 与磁力计(提供绝对航向)或陀螺仪(提供短期航向)搭配使用,或者两者都用。

为什么机器人沿 GPS 航点行驶时会蛇形摆动?

因为 1 Hz 的定位解既陈旧又有噪声,而直接喂原始定位解的路径跟踪器会朝每一个新解猛扑过去。在同一条路线上、用同一个控制器实测,带米级噪声的 1 Hz 定位解会把 0.09 m 的均方根横向误差放大到 1.25 m,并让转向动作增加到四倍半。解决办法是在两次定位之间做航位推算,用每个新的定位解去修正这个估计,而不是替换它。

在室内能用吗?

基本上永远不能,而这并不是故障。GPS 卫星的信号到达时比热噪声底还弱,需要毫无遮挡的天空;一个屋顶就能把它挡住。放在窗台上的模块有时能捕获卫星,但会因多路径效应报告很大的误差。请在户外开阔处测试,并为冷启动留出一分钟,再下结论说有东西坏了。

用 5 V 还是 3.3 V?

大多数转接板带有稳压器,VCC 可以接 5 V,但模块自身的逻辑是 3.3 V。在没有电平转换的板子上把 5 V 灌进 RX,是这类模块最常见的死法。请查清你手上那块板子的情况,拿不准就让 RX 经过一个分压电路——模块的 TX 接到 5 V 单片机上则没问题,因为 3.3 V 已经超过 AVR 的逻辑高电平阈值。

延伸阅读

参考资料