3. NMEA-0183协议入门:听懂GPS模块的“语言”

做嵌入式定位开发,你迟早要跟NMEA-0183打交道。说白了,这就是GPS模块跟单片机“聊天”用的标准语言。不管你是用ublox、中科微还是其他厂家的模块,只要它输出定位数据,十有八九都是NMEA格式。

我个人习惯,拿到一个新模块,第一件事就是接上串口,看看它往外吐什么数据。你看到的那些以$开头、一堆逗号隔开的字符串,就是NMEA语句。今天咱们就把这套“摩斯密码”彻底搞明白。

3.1 NMEA协议是什么?

NMEA-0183是美国国家海洋电子协会制定的标准。最初是为了航海电子设备互联,后来GPS模块全用这套协议输出数据。它定义了数据格式、传输速率(通常是4800或9600波特率)、电气特性等。

一个典型的NMEA语句长这样:

$GPGGA,092725.00,4717.11399,N,00833.91290,E,1,08,1.01,499.6,M,48.0,M,,*5F

嗯,看着有点乱是吧?别急,咱们拆开看,其实结构非常清晰。

3.2 常见语句类型:四种核心“话术”

GPS模块会输出好几种NMEA语句。我最常用的就四种:$GPGGA、$GPRMC、$GPGSA、$GPGSV。它们各自负责不同的信息。

语句ID 全称 核心内容 我的使用场景
$GPGGA Global Positioning System Fix Data 时间、经纬度、定位质量、海拔 获取精确位置和海拔
$GPRMC Recommended Minimum Specific GNSS Data 时间、日期、经纬度、地速、航向 获取速度、航向、UTC时间
$GPGSA GNSS DOP and Active Satellites 定位模式、使用卫星编号、PDOP/HDOP/VDOP 评估定位精度
$GPGSV GNSS Satellites in View 可见卫星编号、仰角、方位角、信噪比 查看天空卫星分布
我的小经验: 如果你只需要位置和速度,解析$GPRMC就够了,它信息最全。但如果你需要海拔数据,那就必须解析$GPGGA。我曾经在一个无人机项目中只解析了$GPRMC,结果发现高度数据拿不到,又回头加了解析$GPGGA的代码。

3.3 语句结构解析:从帧头到校验和

每条NMEA语句的结构都一样。咱们以$GPGGA为例,一句一句拆开看。

先看完整语句:

$GPGGA,092725.00,4717.11399,N,00833.91290,E,1,08,1.01,499.6,M,48.0,M,,*5F

它由五个部分组成:

  1. 帧头($GPGGA):$是起始符,GP表示GPS系统,GGA是语句类型。
  2. 数据字段:逗号分隔的多个字段,每个字段有特定含义。
  3. 校验和标识:*号,表示后面跟着校验值。
  4. 校验和:5F,两个十六进制字符。
  5. 结束符:<CR><LF>(回车换行)。

咱们重点看$GPGGA的数据字段:

字段序号 示例值 含义 说明
1 092725.00 UTC时间 格式:hhmmss.ss
2 4717.11399 纬度 格式:ddmm.mmmm
3 N 北纬/南纬 N或S
4 00833.91290 经度 格式:dddmm.mmmm
5 E 东经/西经 E或W
6 1 定位质量 0=无效, 1=单点定位, 2=差分定位
7 08 使用卫星数 参与定位的卫星数量
8 1.01 水平精度因子(HDOP) 值越小精度越高
9 499.6 海拔高度 单位:米
10 M 海拔单位 M=米
11 48.0 大地水准面高度差 单位:米
12 M 高度差单位 M=米
13 (空) 差分GPS数据龄期 无差分时为空
14 (空) 差分参考站ID 无差分时为空
关键点: 纬度格式是“度分”格式,不是十进制。比如4717.11399表示47度17.11399分。转换成十进制是47 + 17.11399/60 = 47.285233度。这个转换我经常在代码里做,千万别忘了。

3.4 校验和:数据完整性的“守护神”

为什么会需要校验和?因为串口通信可能受干扰,数据可能出错。校验和就是用来检查数据在传输过程中有没有被篡改。

计算规则很简单:

  • 从$之后开始,到*之前结束。
  • 对每个字符做异或运算(XOR)。
  • 结果转换成两个十六进制字符(大写)。

举个例子,计算$GPGGA,092725.00,4717.11399,N,00833.91290,E,1,08,1.01,499.6,M,48.0,M,,*5F的校验和:

// 伪代码实现
uint8_t checksum = 0;
for (int i = 1; i < len; i++) {  // 从'G'开始,到最后一个','结束
    if (str[i] == '*') break;    // 遇到'*'停止
    checksum ^= str[i];
}
// 结果checksum应该等于0x5F
我曾经踩过的坑: 有一次我写的解析代码,校验和总是算不对。查了半天发现,我忘了把$排除在外。记住:从$后面的第一个字符开始算,到*前面的最后一个字符结束。另外,校验和字母必须是大写,小写会被判错。

3.5 知识体系总览

下面这张图把NMEA-0183的核心知识点串起来了。你一看就明白各个部分的关系。

NMEA-0183协议知识体系 NMEA-0183协议知识体系 NMEA-0183协议 语句类型 $GPGGA $GPRMC $GPGSA $GPGSV 语句结构 帧头 数据字段 校验和 结束符 校验和机制 从$后到*前XOR 结果转2位十六进制 核心要点 • 帧头标识语句类型,$GP代表GPS系统 • 数据字段用逗号分隔,空字段保留逗号 • 校验和确保数据完整性,必须大写 • 纬度/经度是度分格式,解析后需转换

3.6 实际项目中的解析思路

在嵌入式系统里解析NMEA,我一般分三步走:

  1. 串口接收:用中断或DMA接收完整一行(以$开头,以<CR><LF>结尾)。
  2. 校验验证:计算校验和,跟语句里的值比对。不一致就丢弃。
  3. 字段提取:用逗号分割字符串,按位置提取每个字段的值。
我的建议: 不要用strtok这类会修改原字符串的函数。我习惯用指针遍历,找到逗号位置后复制子串。这样更安全,也更容易处理空字段。

你想想看,掌握了这些,GPS模块在你手里就不再是黑盒子了。你能读懂它说的每一句话,知道什么时候信号好,什么时候该换天线位置。这就是入门到精通的第一步。