GPS模块这件事,圈子里聊得最多的就是协议。NMEA 0183和UBX,一个公开透明、一个专有高效,几乎所有主流GPS模块都会同时支持这两套协议输出。很多初学者拿到模块,直接接上串口看数据,看到$GNGGA、$GNRMC开头的一串字符,知道这是NMEA;但一旦需要改波特率、调更新率、读星历、做高精度定位,NMEA就明显不够用了,这时候就得跟UBX打交道。
这篇文章就围绕这两套协议展开,从报文结构、核心字段、校验算法到实际配置流程、问题排查,完整梳理一遍GPS模块通信的关键点。不管你是刚接触定位模块的嵌入式新手,还是已经被客户需求逼着改协议的老工程师,这篇都能帮你省下不少翻手册、抓包、试错的时间。
1. 先搞清楚:为什么GPS模块需要两套协议
1.1 NMEA 0183:公开标准,人人可用
NMEA 0183是美国国家海洋电子协会制定的串行通信标准,最早用于海洋电子设备之间的数据交换,后来被GPS接收机广泛采用。它的特点是纯文本、逗号分隔、以$开头、以回车换行结束,调试起来非常直观。用串口助手下拉一串数据,肉眼就能看懂时间、经纬度、速度、航向这些信息。
但NMEA的缺点也很明显:信息密度低、解析开销大、字段精度有限。以经纬度为例,NMEA输出的是ddmm.mmmm格式,也就是度分格式,有效位数大约能到小数点后4位,实际厘米级精度勉强可用,但在RTK这类高精度场景下,直接用NMEA传输原始观测量是完全不够的。另外NMEA语句是周期性广播,哪怕某个字段没用也在一直发,浪费带宽和MCU的解析时间。
1.2 UBX协议:u-blox的私有二进制协议
UBX协议是u-blox公司定义的二进制通信协议,专门用于配置u-blox系列GPS/GNSS接收机,以及传输高精度定位数据。它和NMEA有三大本质区别:
- 二进制格式,字段紧凑,解析效率高,带宽利用率远高于文本协议;
- 支持双向通信,主机可以主动向模块发送配置命令,实时修改模块行为;
- 数据类型丰富,包括经纬度、海拔、速度、卫星状态、定位精度、时间信息、原始观测量等,几乎涵盖了定位模块所有内部信息。
简单类比:NMEA像是模块对外发布的“广播电台”,你只能被动收听固定的几档节目;UBX则像一条“双向电话线”,你可以打电话给模块,让它按你的要求上报你需要的信息。
1.3 怎么选?两者不是替代关系,而是互补关系
我在实际项目中的选择逻辑是这样:
- 只做基础定位显示、轨迹记录、小型设备集成,优先用NMEA。常见如车载定位器、手持设备、农业导航终端,客户只要一个标准协议,对接起来方便。
- 需要改模块配置、读取扩展状态、做高精度应用,必须用UBX。比如修改串口波特率、调整输出频率、读取接收机固件版本、设置动态模式,这些操作通过NMEA基本做不到,或者只能完成极少部分配置。
- 量产阶段建议用UBX做配置固化,把模块参数一次性设置好再切换到低功耗或所需模式,避免每一台设备都跑一遍配置流程。
所以两套协议不是竞争关系,而是GPS模块在“对外广播”和“内部管理”两个层面的分工。真正做GPS产品开发,这两套协议都需要熟练,这篇就以实际工程为主线,分开拆解它们的工作原理和实操方法。
2. NMEA 0183协议拆解:逐条语句、逐字段吃透
2.1 报文整体结构:从$到换行的完整链路
NMEA 0183报文的标准结构是一个ASCII字符串,字段之间用逗号分隔。一条完整的GGA语句大概是这样的:
$GPGGA,092750.000,5321.6802,N,00630.3372,W,1,8,1.03,61.7,M,55.2,M,,*75拆开看:
$:起始标志,表示新语句开始;GP: talker ID,表示定位系统来源(GP代表GPS,GL代表GLONASS,GA代表Galileo,BD代表北斗,GN代表多系统组合);GGA:语句类型,代表“Global Positioning System Fix Data”,即全球定位系统定位数据;- 从第一个逗号后面开始是数据字段,具体含义由语句类型定义;
*(星号):校验和分隔符,后面的两位十六进制数是前面所有字符(不包括$和*本身)按位异或的结果;- 最后是
\r\n(回车换行),表示语句结束。
解析NMEA语句时有一个关键认知:校验和是验错手段,不是纠错手段。如果校验和算出来和报文字段不一致,最稳妥的做法是丢弃这一帧,而不是尝试修复,因为GPS数据本身有冗余,丢一帧问题不大,强行使用坏帧反而可能引起位置跳变。
2.2 常用语句逐个拆解:GGA、RMC、GSA、GSV
实际项目里用得最多的NMEA语句是GGA和RMC,其次是GSA和GSV,下面逐个过一遍字段含义。
GGA语句(定位数据)字段含义如下:
| 字段序号 | 字段名 | 示例值 | 说明 |
|---|---|---|---|
| 1 | UTC时间 | 092750.000 | 时:分:秒.毫秒 |
| 2 | 纬度 | 5321.6802 | ddmm.mmmm(度分格式) |
| 3 | 北/南纬 | N | N=北纬,S=南纬 |
| 4 | 经度 | 00630.3372 | dddmm.mmmm(度分格式) |
| 5 | 东/西经 | W | E=东经,W=西经 |
| 6 | 定位状态 | 1 | 0=无效,1=单点定位,2=差分定位,4=RTK固定解 |
| 7 | 正在使用的卫星数 | 8 | 参与定位解算的卫星数量 |
| 8 | HDOP | 1.03 | 水平精度因子,越接近1精度越好 |
| 9 | 海拔高度 | 61.7 | 单位:米 |
| 10 | 高度单位 | M | 固定为M(米) |
| 11 | 大地水准面高度 | 55.2 | 单位:米 |
| 12 | 差分数据龄期 | 空 | 差分GPS时有效,单位为秒 |
| 13 | 差分基站ID | 空 | 差分GPS时有效 |
RMC语句(推荐最小定位信息)是最常用的精简语句,一条语句就包含了时间、经纬度、速度、航向、日期,字段含义如下:
$GPRMC,092750.000,A,5321.6802,N,00630.3372,W,0.02,31.66,221111,,,A*43- UTC时间:092750.000
- A:定位状态,A=有效定位,V=无效定位
- 纬度、北/南纬、经度、东/西经:同GGA格式
- 0.02:地面速度,单位节(1节约0.5144米/秒)
- 31.66:地面航向,单位度(相对真北的顺时针角度)
- 221111:日期,22日11月11年(即2022年11月11日)
这里有一个经常踩的坑:速度单位是节,不是千米/小时。换算公式是速度(km/h) = 速度(节) × 1.852。很多刚入门的开发者直接拿这个字段显示“多少千米每小时”,结果速度数值偏小一半,就是没做单位换算。
GSA语句(精度因子与有效卫星)输出当前使用的卫星编号、PDOP、HDOP、VDOP,用于评估当前定位几何精度。GSV语句(可见卫星信息)输出所有可见卫星的编号、仰角、方位角和信噪比,主要用于天线调试和信号分析。
2.3 NMEA解析实战:以GGA为例写出核心逻辑
实际写解析代码时,不建议自行逐字节扫描拼接字段,效率低且容易出bug。工程上更推荐的做法是:先用串口接收线程把完整的语句按\r\n切分出来,再调用现成的NMEA解析库处理字段。C/C++环境可以用libnmea、Minmea,Python环境可以用pynmea2,都不用重复造轮子。
如果坚持自己写解析,核心步骤其实也就三步:
- 找到
$开头,读取到\r\n结尾,按逗号切分得到字段数组; - 计算校验和:从
$后的第一个字符开始,逐个异或直到*之前,将结果与*后两位十六进制数字比较; - 校验通过后,根据语句类型(第二个字段)进入对应的字段解析函数。
以GGA解析为例,代码逻辑大致如下:
import re def parse_gga(sentence): if not sentence.startswith('$') or '*' not in sentence: return None body, checksum = sentence.split('*') calc = 0 for ch in body[1:]: # 跳过$符号 calc ^= ord(ch) if int(checksum, 16) != calc: return None fields = body.split(',') if fields[2] != 'GGA': return None lat_deg = float(fields[3][:2]) + float(fields[3][2:]) / 60.0 lat_dir = fields[4] lon_deg = float(fields[5][:3]) + float(fields[5][3:]) / 60.0 lon_dir = fields[6] return { 'time': fields[1], 'latitude': lat_deg if lat_dir == 'N' else -lat_deg, 'longitude': lon_deg if lon_dir == 'E' else -lon_deg, 'quality': fields[7], 'satellites': int(fields[8]) }解析时特别注意经纬度是度分格式,一定要先转成十进制度再存入业务变量,否则后续地图显示、距离计算全都会出错。这个转换公式是:十进制度 = 度 + 分 / 60,例如5321.6802对应53°21.6802′,十进制度就是53 + 21.6802 / 60 ≈ 53.3613367。
3. UBX协议深度解析:从帧结构到常用消息
3.1 UBX帧结构逐字节拆解
UBX协议有严格定义的二进制帧结构,每一帧由以下几部分组成:
| 字节偏移 | 长度 | 含义 | 取值 |
|---|---|---|---|
| 0 | 1 | 同步字符1 | 0xB5 |
| 1 | 1 | 同步字符2 | 0x62 |
| 2 | 1 | 消息类(Class) | 由消息定义 |
| 3 | 1 | 消息ID(ID) | 由消息定义 |
| 4 | 2 | 有效载荷长度(小端序) | 有效载荷字节数 |
| 6 | N | 有效载荷 | 实际数据 |
| 6+N | 1 | 校验和CK_A | 由校验算法计算 |
| 7+N | 1 | 校验和CK_B | 由校验算法计算 |
开头两个字节固定是0xB5 0x62,这是UBX帧的同步标志。只要串口数据流里有这两个字节连续出现,接收方就应该尝试解析后面的内容。后面紧跟一个字节的“类”和一个字节的“ID”,这两个字段共同决定了消息的类型。比如类0x01是导航类(NAV)、0x06是配置类(CFG)、0x0A是监控类(MON)、0x05是接收机状态类(ACK)。
这里有一个工程上容易忽略的点:长度字段是小端序存储的。也就是说,如果有效载荷是256个字节,那么长度字段的首字节是0x00 0x01,而不是0x01 0x00。很多人在解析长度字段时用大端读法,解析出来的数据直接差了一个量级,后面再排查半天才发现是这个细节。
3.2 UBX校验和算法:比NMEA稍微复杂一点的异或算法
UBX的校验和算法很容易和NMEA搞混。NMEA是“从头到尾逐个字符异或成一个字节”,UBX则是维护两个累加字节:第一个字节本轮累加原始字节值,第二个字节累加第一个字节的当前值。伪代码如下:
uint8_t ck_a = 0, ck_b = 0; for (int i = 0; i < payload_len + 4; i++) { ck_a += data[i]; // data是类、ID、长度、有效载荷的完整片段 ck_b += ck_a; } // 校验成功后 ck_a 和 ck_b 应分别等于帧尾的两个字节注意循环的范围:从消息类字节开始(跳过同步字符),一直到有效载荷最后一个字节结束。类、ID、长度字段全部纳入校验。发送方计算校验值时,输出顺序是先CK_A再CK_B;接收方对收到的整帧(不含尾部校验字节)重新计算,然后和收到的CK_A、CK_B比较。
我在很多项目里见过一个经典错误:发送UBX配置帧时,校验和算法对了一半,对着协议说明看了半天,最后发现是漏掉了长度字段那两字节。这里提醒一句:校验范围覆盖的是“从Class到Payload末尾”这些字节,不是只覆盖Payload本身,也不是从同步字符开始的完整帧。
3.3 常用UBX消息逐一介绍
配置串口(CFG-PRT,类0x06 ID 0x00):这是最常用的配置帧,用来改波特率、设置输出协议。发送时Payload里要指定端口ID、目标协议、波特率等字段。改波特率这类操作有一点特别:模块收到配置命令并执行成功后,会立刻把当前串口切换成新波特率,但之前你还得把命令本身用旧波特率发送过去。所以流程是“旧波特率发命令→模块回ACK→模块切波特率→重新连接新波特率”,顺序不能搞反。
配置输出频率(CFG-RATE,类0x06 ID 0x08):设置定位数据的更新频率,设置项是测量周期(毫秒)和导航周期(周期倍数)。如果想设置10Hz输出,就把测量周期设为100ms;想设置5Hz,就设200ms。注意模块支持的更新率有上限,普通消费级模块最高支持10Hz,个别支持20Hz甚至更高,设置值超出范围后模块会忽略或自动降级,所以设备选型时要提前确认模块规格。
导航/定位数据(NAV-PVT,类0x01 ID 0x07):这是最常用的导航消息,一条消息包含了经纬度、海拔、速度、航向、日期时间、定位质量、卫星数量,以及位置精度等信息。经纬度在UBX里是整数格式,单位是1e-7度,解析时除以1e7再使用。这一点和NMEA的度分字符串格式完全不同——UBX是纯整数,没有小数点,数值精度更高,但解析时一定要记得做除法转换,否则得到的经纬度会是一个大得离谱的数。
接收机监控信息(MON-VER,类0x0A ID 0x04):读取模块固件版本、协议版本、支持的功能特性。拿到新模块别急着配置,先发一条MON-VER把模块信息读出来,确认固件版本和文档匹配再继续。
接收机状态确认(ACK-ACK,类0x05 ID 0x01):模块收到配置命令后,会返回ACK-ACK表示配置生效,或者返回ACK-NAK(类0x05 ID 0x00)表示命令无法执行。调试配置命令时,这可能是最有用的一个消息——命令发出去后有没有被接受,看ACK就一目了然。
3.4 UBX命令帧构造实操:以修改波特率为例
修改波特率的完整过程,我通常会在工程里写成这样一个工具函数:
// 构造CFG-PRT消息,将UART1配置为115200波特率,输出NMEA+UBX协议 uint8_t cfg_prt[] = { 0xB5, 0x62, // 同步字符 0x06, 0x00, // 类CFG, ID PRT 0x14, 0x00, // 长度 20字节 0x01, 0x00, 0x00, 0x00, // 端口ID=1(UART1) 0xD0, 0x08, 0x00, 0x00, // 模式:8位数据位,无校验,1停止位 0x00, 0x00, // 保留 0x00, 0x00, // 保留 0xC2, 0x01, 0x00, 0x00, // 波特率115200(0x0001C200) 0x01, 0x00, // 输入协议:UBX+NMEA(按bit位设定) 0x01, 0x00 // 输出协议:UBX+NMEA };构造完成后,最后两个字节要求计算校验和并填入。发送前有没有合理计算校验和,直接决定了这条命令会不会被模块接受。发送后模块回复的ACK-ACK或者ACK-NAK,就是配置是否生效的依据。
这里完整说一遍校验和计算的代码流程:
uint8_t calc_ck_a = 0, calc_ck_b = 0; // 从第2字节(类)开始,到Payload最后一个字节结束 for (int i = 2; i < 20; i++) { // 20是Payload长度 calc_ck_a += cfg_prt[i]; calc_ck_b += calc_ck_a; } cfg_prt[20] = calc_ck_a; cfg_prt[21] = calc_ck_b; // 注意:数组长度是2+2+2+20+2 = 28字节每次发UBX命令前,都建议先做一次校验和计算再发送。调试期间如果发现模块毫无反应,先用串口助手看看发送的帧末尾两个字节算得对不对,至少能排除一半问题。
4. GPS模块实战配置流程:从接线到协议切换
4.1 硬件连接与串口参数检查
GPS模块的硬件连接看着简单,但翻车率不低。以最常见的UART接口模块为例,有四个引脚必须接对:VCC、GND、TX、RX。TX是模块发数据给MCU的引脚,接MCU的RX;RX是模块接收数据的引脚,接MCU的TX,这个交叉连接跟普通串口通信完全一致。
供电是最容易忽略的坑。GPS模块常用的工作电压是3.3V,也有5V供电的型号,接错电压轻则模块发热、定位异常,重则烧毁模块射频前端。很多模块板载了LDO稳压芯片,5V供电也能正常工作,但输出电平是3.3V还是5V,不同模块就不一样了。和MCU对接时,务必确认电平兼容性,不然MCU端RX引脚可能直接烧坏,或者模块输出被MCU钳位导致收不到数据。
天线部分,有源天线需要馈电,模块的ANT_BIAS引脚或VCC_RF引脚会输出3.3V或5V给天线放大器供电。这时要确认模块是否支持天线检测功能,以及天线是否短路或开路。很多模块支持通过UBX的监控消息读取天线状态,量产自检时可以把这个状态纳入出厂测试项。
4.2 第一次上电:先看NMEA还是先切UBX?
首次拿到GPS模块,我习惯先把模块的TX直接接到USB转串口工具上,打开串口助手,波特率设成9600(u-blox模块默认波特率通常是9600),然后观察串口输出。正常情况下应该能看到连续的NMEA语句,在室外或窗边,一分钟内GGA语句的定位状态字段应该从0变为1或2。
确认NMEA输出正常后,再考虑切UBX协议。切换有两种方式:一种是用u-blox官方工具u-center,通过图形界面勾选协议;另一种是自己发UBX命令切换。量产场景下,推荐用自己发命令的方式,因为可以集成到产测脚本里,实现全自动配置。
切协议的命令核心是CFG-PRT的输入/输出协议字段。这个字段是一个16位的bitmap,bit0表示UBX协议,bit1表示NMEA协议,bit2表示RTCM协议。如果想完全关闭NMEA输出、只用UBX,就把输出协议字段设为0x01;如果想两者并存,设为0x03。
4.3 用u-center快速交换数据包
u-center是u-blox官方提供的上位机工具,支持Windows平台,界面不算现代,但功能极其强大,特别适合做GPS模块调试。连接上模块后,在“View”菜单里找到“Message View”窗口,可以实时看到模块上报的所有UBX消息,也可以直接在窗口里编辑并下发配置命令。这个窗口对于协议学习阶段非常有用——你以为你发出去的帧和自己想的一样,但实际串口上跑的是不是那回事,只有抓包才知道。
u-center还有一项功能是“Config”菜单下的“Receiver Configuration”,可以图形化地配置串口波特率、更新频率、动态模式、电源模式等。配置完成后记得点击“Send”,然后再通过“Poll”确认配置已生效。如果后续要量产,建议用“Configuration Database”功能把配置保存为XML文件,产测时通过命令行批量下发,效率会高很多。
4.4 量产场景:如何用脚本固化模块配置
量产阶段如果每台模块都要手工配置一遍,效率低且容易漏配。我通常会在产测软件里集成一个自动化配置流程:
- 模块上电,以默认波特率连接串口;
- 发送MON-VER读取固件版本,确认模块型号和固件匹配;
- 发送CFG-PRT,把波特率从9600切到目标值(比如38400或115200);
- 等待ACK确认后,以新波特率重新连接串口;
- 发送CFG-RATE设定更新频率,比如10Hz;
- 发送CFG-CFG(类0x06 ID 0x09),将配置保存到模块的永久存储区;
- 发送CFG-PRT关闭不用的协议输出,只保留目标协议;
- 断电重启,再次读取配置,确认固化成功。
最后一步特别重要。很多模块支持在RAM中临时配置,断电后配置就会丢失。如果你没有发送CFG-CFG类的保存命令,模块重启后就会回到默认配置,之前所有操作全部白做。CFG-CFG的Payload里要用一个掩码告诉模块要保存哪些类别的配置,一般建议三类(端口配置、协议配置、其他配置)全部写入。
5. 高频问题与排查方法
5.1 串口无数据输出?先查硬件再查配置
串口接收不到任何数据,是GPS模块调试时最折磨人的问题。我的排查顺序是固定的:
- 万用表量模块供电电压,确认VCC在规格范围内,GND确实共地;
- 确认TX/RX接线是交叉的,模块TX接MCU RX,模块RX接MCU TX;
- 把模块TX直接接到USB转串口工具,用串口助手收数据,排除MCU端问题;
- 确认波特率和模块默认波特率一致(9600最常见,也有115200的模块);
- 检查模块是否处于“冷启动无星历+室内环境”,这种情况也会导致没有定位语句输出。
有一类情况容易误判:模块信号正常但没有输出,是因为模块处于SLEEP Mode或BACKUP Mode,需要发送特定唤醒指令或拉高唤醒引脚。可以参考模块数据手册中的电源模式章节确认。还有一类情况是模块默认输出协议被改成了只输出UBX,你用串口助手按NMEA的ASCII格式看,当然什么都看不到——这时候用十六进制显示看一眼数据流,如果是B5 62开头,说明模块其实在工作,只是协议不对。
5.2 定位数据一直无效?问题多半在天线和环境
GGA语句里的第7个字段(定位状态)一直是0,说明模块一直在搜星但没有解算出位置。常见原因有以下几类:
- 天线没有接好,或有源天线馈电异常。有源天线信号会经过LNA放大,馈电断开直接导致信号强度下降十几个dB,室内基本无法定位;
- 模块处于冷启动状态,星历和粗略位置都丢失了,首次定位需要几分钟。这种情况下可以用“冷启动命令”明确触发一次完整搜星流程,U-blox的冷启动命令是CFG-RST(类0x06 ID 0x04),冷启动模式设为0x00;
- 天线摆放位置靠近金属物体或强干扰源。GPS信号本身很弱,天线附近放一个金属外壳设备,信号就可能被屏蔽。把天线挪到窗口,或保证天线面朝天空,是最有效的操作。
判断信号质量,最直接的手段是看GSV语句里的信噪比。每颗卫星的信噪比通常在25-50之间,数值越高说明信号越好。如果所有卫星的信噪比都在20以下,基本可以断定是天线或环境问题,而不是模块本身的问题了。
5.3 校验和总是不对?用数据抓包器反向验证
调试UBX命令时,很多人陷进“自己构造的帧哪里错了”的死循环。我的建议是:别只盯自己的代码,先用串口助手或者逻辑分析仪把模块实际收到或发出的原始字节抓出来,用十六进制模式肉眼对照协议文档。
如果校验和一直不对,可以先从这几个点排查:
- 同步字符是否真的是
B5 62,有没有被转义或者被串口硬件改动; - 长度字段是否用小端序写入。很多开发板的代码里用了大端序,一改动长度超过255的Payload就会出错;
- 校验和的循环范围是否正确:从Class开始到Payload结束,不包括同步字符和校验字节自身;
- 发送前有没有在串口里多加字节,比如多了
\r\n或者0x00填充,模块会把多余字符当成帧的一部分,导致整个帧解析失败。
这里补充一个经验:u-blox模块对校验和错误的帧一般不会回复任何信息。所以如果发送配置命令后没有收到ACK,最简单的排查方法就是重新核对校验和,把帧头到Payload末尾的每个字节都过一遍,大概率能找到问题。
5.4 经纬度漂移与单位混淆
前面提到,UBX的经纬度单位是1e-7度,NMEA的经纬度是度分格式,两者混用是GPS开发最常见的业务错误之一。排查方法是确认好数据链路中每个环节的单位转换只做一次,且转换逻辑有单测覆盖。
还有一种情况是“定位结果正常但在地图上显示偏移几百米”,大概率是坐标系问题。GPS模块原生输出的是WGS-84坐标系,而国内大部分地图服务用的是GCJ-02火星坐标系或BD-09百度坐标系,两者之间存在加密偏移。如果你做的是民用地图应用,务必在拿到WGS-84坐标后做一次坐标系转换,再交给地图渲染。这个过程不要试图靠模块端配置解决,应用层转换即可。
5.5 固件升级需要注意的细节
部分u-blox模块支持固件升级(比如通过UART下载新固件),但升级过程有风险,操作不当可能把模块变砖。升级前务必确认以下几点:
- 固件文件和模块型号严格匹配,不同型号固件不可混用;
- 升级过程中保持串口连接稳定,不要断电,不要在升级过程中发送其他数据;
- 部分模块升级后需要重新配置参数,之前固化的配置可能被清零。
升级完成后,先读MON-VER确认固件版本,再做一次完整的配置固化流程,能避免很多后续麻烦。
6. 写在最后的实操体会
把NMEA和UBX协议完全吃透,不是一两天的事,但我可以负责任地说,这两套协议只要认真走一遍收发流程,之后遇到的GPS相关项目都会顺畅很多。我最初做GPS模块调试时,也经历过对着串口助手发呆的时段,后来发现大多数问题其实都是基础细节没抓牢:接线不对、波特率不匹配、校验和算错、单位没转换、配置没固化。这些问题在协议层面都很简单,但如果不系统梳理,调试时就会反复踩坑。
再分享一个小技巧:调试UBX协议时,可以先在u-center里让模块“记录”一段原始串口数据,然后离线用脚本解析。对比自己解析的结果和u-center解析的结果,一旦发现字段对不上,就能很清楚地看出是字节序、位宽还是单位的问题,比自己对着文档猜要高效得多。
后续如果项目继续深入,还可以把RTCM协议、SPARTN协议、PPP-RTK这些更高阶的差分定位协议纳入研究范围。但地基还是要打牢——把NMEA 0183和UBX先彻底搞清楚,再上差分协议,会顺手很多。