4. NMEA-0183协议详解:GGA、RMC、GSA、GSV、VTG语句格式、校验和计算、数据解析

做GNSS定位开发,NMEA-0183协议是绕不开的坎。说白了,它就是GPS模块和主控芯片之间的“通用语言”。我刚开始接触这行时,对着串口打印的一堆乱码发懵——后来才发现,只要掌握了几个核心语句,解析定位数据其实很简单。

今天我们就来拆解最常用的5条语句:GGA、RMC、GSA、GSV、VTG。我会把每条语句的格式、校验和计算方法、解析技巧都讲透。

核心要点:NMEA-0183语句以$开头,以<CR><LF>结尾。字段之间用逗号分隔。校验和位于*之后,是$与*之间所有字符的异或值。

4.1 语句通用结构

先看一个完整的NMEA语句长什么样:

$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47

拆开来看:

  • $:语句起始符
  • GP:Talker ID,表示GPS系统(GLONASS用GL,北斗用BD)
  • GGA:语句类型,这里是全球定位系统定位数据
  • 数据字段:逗号分隔,每个语句的字段数量和含义不同
  • *:校验和前缀
  • 47:校验和,十六进制值

嗯,这里要注意:校验和是可选字段,但实际项目中几乎都会带。我建议你在解析时一定要校验,否则数据错了都不知道。

4.2 GGA语句——定位核心数据

GGA(Global Positioning System Fix Data)是最常用的语句。它包含了时间、经纬度、定位质量、卫星数、海拔高度等关键信息。

格式:

$GPGGA,hhmmss.ss,ddmm.mmmm,a,dddmm.mmmm,a,x,xx,x.x,x.x,M,x.x,M,x.x,xxxx*hh

字段详解:

字段序号 名称 说明 示例
1 UTC时间 hhmmss.ss格式 123519.00
2 纬度 ddmm.mmmm,度分格式 4807.038
3 纬度方向 N或S N
4 经度 dddmm.mmmm,度分格式 01131.000
5 经度方向 E或W E
6 定位质量 0=无效,1=单点定位,2=差分定位 1
7 使用卫星数 00-12 08
8 水平精度因子 HDOP,越小越好 0.9
9 海拔高度 单位:米 545.4
10 海拔单位 固定为M M
11 大地水准面高度 单位:米 46.9
12 差分GPS数据龄期 无差分时为空白
13 差分参考站ID 0000-1023

我的经验:解析GGA时,我习惯先检查定位质量字段(第6字段)。如果为0,后面的数据直接丢弃,不用再解析了。这样可以避免无效数据污染业务逻辑。

4.3 RMC语句——推荐最小数据

RMC(Recommended Minimum Specific GNSS Data)是另一个核心语句。它包含了时间、日期、经纬度、速度、航向等信息。很多应用只解析RMC就够了。

格式:

$GPRMC,hhmmss.ss,A,ddmm.mmmm,a,dddmm.mmmm,a,x.x,x.x,ddmmyy,x.x,a*hh

字段详解:

字段序号 名称 说明 示例
1 UTC时间 hhmmss.ss 123519.00
2 定位状态 A=有效,V=无效 A
3 纬度 ddmm.mmmm 4807.038
4 纬度方向 N或S N
5 经度 dddmm.mmmm 01131.000
6 经度方向 E或W E
7 对地速度 单位:节 0.5
8 对地航向 单位:度,真北为参考 180.2
9 UTC日期 ddmmyy 150324
10 磁偏角 单位:度 0.0
11 磁偏角方向 E或W E
12 定位模式 A=自主,D=差分,N=无效 A

避坑指南:我曾经在项目里只检查了GGA的定位质量,没检查RMC的定位状态。结果有一次模块输出GGA质量=1(有效),但RMC状态却是V(无效)。后来查手册才知道,RMC的A/V状态更严格——它要求定位解算收敛到一定精度才算有效。所以我现在两个都检查,取交集。

4.4 GSA语句——精度因子与卫星使用情况

GSA(GNSS DOP and Active Satellites)语句告诉你当前使用了哪些卫星,以及各种精度因子(DOP)的值。DOP值越小,定位精度越高。

格式:

$GPGSA,a,x,xx,xx,xx,...,xx,x.x,x.x,x.x*hh

字段详解:

字段序号 名称 说明 示例
1 定位模式 M=手动,A=自动 A
2 定位类型 1=无定位,2=2D,3=3D 3
3-14 使用卫星PRN号 最多12颗,空白表示未使用 01,02,03,...
15 PDOP 位置精度因子 1.8
16 HDOP 水平精度因子 0.9
17 VDOP 垂直精度因子 1.5

你想想看,PDOP、HDOP、VDOP这三个值怎么用?我一般这样判断:

  • PDOP < 2:优秀,可以用于高精度测量
  • PDOP 2-4:良好,普通导航够用
  • PDOP 4-6:一般,可能有些误差
  • PDOP > 6:较差,建议等待或换位置

4.5 GSV语句——卫星可见信息

GSV(GNSS Satellites in View)语句告诉你天空中有哪些卫星可见,以及它们的仰角、方位角和信噪比。这个语句通常有多条,因为一条语句最多只能报4颗卫星。

格式:

$GPGSV,x,x,xx,xx,xx,xxx,xx,...,xx,xx,xxx,xx*hh

字段详解:

字段序号 名称 说明 示例
1 总条数 GSV语句的总条数(1-3) 2
2 当前条序号 当前是第几条(1-3) 1
3 可见卫星总数 所有系统合计 10
4-7 卫星1信息 PRN号,仰角,方位角,信噪比 01,45,180,35
8-11 卫星2信息 同上 02,30,200,40
12-15 卫星3信息 同上 03,60,90,42
16-19 卫星4信息 同上 04,10,300,28

我的习惯:解析GSV时,我会把信噪比低于30dBHz的卫星标记为“弱信号”,在后续定位解算中降低它们的权重。这个技巧在弱信号环境下特别有用。

4.6 VTG语句——对地速度与航向

VTG(Course Over Ground and Ground Speed)语句很简单,就是告诉你当前的运动速度和方向。

格式:

$GPVTG,x.x,T,x.x,M,x.x,N,x.x,K,a*hh

字段详解:

字段序号 名称 说明 示例
1 真北航向 单位:度 180.2
2 真北标识 固定为T T
3 磁北航向 单位:度 179.8
4 磁北标识 固定为M M
5 对地速度(节) 单位:节 0.5
6 速度单位(节) 固定为N N
7 对地速度(km/h) 单位:公里/小时 0.9
8 速度单位(km/h) 固定为K K
9 定位模式 A=自主,D=差分,N=无效 A

4.7 校验和计算——不能跳过的一步

校验和是NMEA协议的“安全锁”。我见过太多人图省事跳过校验,结果被错误数据坑惨了。

计算方法:

  1. 从$后面的第一个字符开始,到*之前结束
  2. 对每个字符做异或(XOR)运算
  3. 结果转换为两位十六进制大写

代码示例(C语言):

uint8_t calc_nmea_checksum(const char *nmea_str)
{
    uint8_t checksum = 0;
    const char *p = nmea_str;
    
    // 跳过开头的 '$'
    if (*p == '$') p++;
    
    // 逐字符异或,直到 '*' 或字符串结束
    while (*p != '\0' && *p != '*')
    {
        checksum ^= (uint8_t)(*p);
        p++;
    }
    
    return checksum;
}

// 使用示例
int verify_nmea(const char *nmea_str)
{
    // 找到 '*' 位置
    const char *star = strchr(nmea_str, '*');
    if (star == NULL) return 0;  // 没有校验和
    
    // 提取校验和字段
    uint8_t expected = (uint8_t)strtol(star + 1, NULL, 16);
    uint8_t calculated = calc_nmea_checksum(nmea_str);
    
    return (expected == calculated) ? 1 : 0;
}

避坑指南:我曾经在一个项目中,发现某些模块输出的校验和字段是*00。一开始以为是模块坏了,后来查手册才知道——有些模块在未定位时,校验和字段会填充00。所以校验失败时,别急着报错,先看看定位状态是否有效。

4.8 数据解析实战——从字符串到结构体

解析NMEA语句,说白了就是字符串分割+类型转换。我一般用strtok或sscanf来做。下面是一个完整的GGA解析示例:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

typedef struct {
    uint8_t     hour;
    uint8_t     minute;
    uint8_t     second;
    double      latitude;   // 十进制度
    double      longitude;  // 十进制度
    uint8_t     fix_quality;
    uint8_t     sat_count;
    float       hdop;
    float       altitude;
} GGA_t;

int parse_gga(const char *nmea_str, GGA_t *out)
{
    char buf[128];
    char *token;
    int field = 0;
    
    strncpy(buf, nmea_str, sizeof(buf) - 1);
    
    token = strtok(buf, ",");
    while (token != NULL)
    {
        switch (field)
        {
            case 1: // UTC时间
                sscanf(token, "%2hhu%2hhu%2hhu", 
                       &out->hour, &out->minute, &out->second);
                break;
            case 2: // 纬度 ddmm.mmmm
            {
                double ddmm = atof(token);
                int degrees = (int)(ddmm / 100);
                double minutes = ddmm - degrees * 100;
                out->latitude = degrees + minutes / 60.0;
                break;
            }
            case 3: // 纬度方向
                if (token[0] == 'S') out->latitude = -out->latitude;
                break;
            case 4: // 经度 dddmm.mmmm
            {
                double ddmm = atof(token);
                int degrees = (int)(ddmm / 100);
                double minutes = ddmm - degrees * 100;
                out->longitude = degrees + minutes / 60.0;
                break;
            }
            case 5: // 经度方向
                if (token[0] == 'W') out->longitude = -out->longitude;
                break;
            case 6: // 定位质量
                out->fix_quality = atoi(token);
                break;
            case 7: // 卫星数
                out->sat_count = atoi(token);
                break;
            case 8: // HDOP
                out->hdop = atof(token);
                break;
            case 9: // 海拔
                out->altitude = atof(token);
                break;
        }
        token = strtok(NULL, ",");
        field++;
    }
    
    return (out->fix_quality > 0) ? 1 : 0;
}

我的建议:解析经纬度时,记得把度分格式转成十进制度。公式很简单:十进制度 = 度 + 分/60。北纬和东经为正,南纬和西经为负。这样后续计算距离、面积都方便。

4.9 知识体系总览

下面这张图总结了本章的核心内容,你可以把它当作快速参考:

NMEA-0183 核心语句知识体系 NMEA-0183 协议 GGA 定位核心数据 RMC 推荐最小数据 GSA 精度因子与卫星 GSV 卫星可见信息 VTG 速度与航向 关键字段:UTC时间 | 经纬度(度分格式) | 定位质量/状态 | 精度因子(PDOP/HDOP/VDOP) | 信噪比 校验和:从 $ 后第一个字符到 * 前,逐字符异或,结果转两位十六进制大写 解析流程 接收原始字符串 校验和验证 按逗号分割字段 类型转换与存储 注意:GGA和RMC都包含定位状态,建议同时检查 GSV可能有多条,需要拼接解析;校验和不能跳过 经纬度需从度分格式转为十进制度,便于后续计算

4.10 总结与建议

好了,NMEA-0183的5条核心语句就讲完了。我个人觉得,掌握这些就够应付90%的定位项目了。最后给你几个实用建议:

  • 优先用RMC:如果只需要位置和速度,RMC一条语句就够了,解析量最小
  • GGA+GSA组合:需要评估定位质量时,GGA看定位类型,GSA看精度因子
  • GSV用于诊断:定位效果不好时,看GSV能快速判断是卫星数量不够还是信号太弱
  • 校验和必须做:别偷懒,串口通信随时可能丢字节,校验和是最后一道防线