
做定位相关的项目不管是两轮小车、四轴无人机还是车载终端、宠物追踪器绕不开的就是GPS模块。而只要一接上GPS模块串口里就会刷刷刷往外冒字符串——$GPGGA开头、$GPRMC开头各种字母和数字混在一起看着头皮发麻。这篇文章就把NMEA-0183协议彻底讲透。我会从GPS模块为什么输出这种格式讲起逐条拆解最常见的$GPGGA和$GPRMC语句再给出从串口读取到经纬度解析的完整流程和代码示例最后整理我这些年调GPS模块踩过的坑。无论你是刚接触嵌入式的学生还是已经在做物联网项目的开发者看完都能直接上手把经纬度从原始数据里干净地拎出来。1. GPS模块为什么不直接输出经纬度协议在中间扮演什么角色1.1 GPS芯片的语言NMEA-0183是怎么来的先说一个很多人没想过的点GPS模块里真正干活的是那颗GPS芯片比如Ublox的NEO系列、中科微的AT6558、天工测控的SKG系列。芯片计算完位置后会把结果通过串口发出去——但发什么格式芯片厂家说了不算得有一个统一的“通用语言”这就是NMEA-0183。NMEA-0183是美国国家海洋电子协会National Marine Electronics Association制定的标准最早用于航海电子设备之间的通信后来GPS接收机也广泛采用。它的核心思路特别朴素所有数据用纯ASCII文本表达按行发送行与行之间用回车换行分隔字段之间用逗号分隔。这样的好处非常明显——任何单片机、任何电脑串口工具、任何编程语言都能轻松读取和处理甚至你用手机的蓝牙串口app都能直接看到内容。我经常用一个类比NMEA-0183就像餐馆的菜单模板。不同厨师不同GPS芯片厂家做法可以不一样但端上来的菜都得按照“凉菜-热菜-主食-汤”这个顺序写顾客单片机才知道先吃哪个。没有这个标准协议的话换了Ublox的模块要改一套解析代码换成中科微又要改一套那开发效率就崩了。1.2 NMEA-0183的核心特性与常见语句类型NMEA-0183的句子有几百种但日常用到GPS模块时关注的就那么几条。我在项目里实际处理过的语句中这几个见得最多$GPGGA全球定位系统定位数据Global Positioning System Fix Data包含经纬度、定位质量、卫星数量、海拔高度。$GPRMC推荐最小定位信息Recommended Minimum Specific GPS/Transit Data包含经纬度、速度、航向、日期。$GPGSV可见卫星信息包含天空中搜索到的卫星编号、仰角、方位角、信噪比。$GPGSA精度及卫星使用信息包含正在使用的卫星编号和位置精度因子PDOP、HDOP、VDOP。$GPVTG地面速度信息Course Over Ground and Ground Speed包含对地航向和速度。其中$GPGGA和$GPRMC是我们做定位解析必用的两条。GGA偏“静态质量”适合判断定位是否解算成功RMC偏“运动状态”里面有速度、航向和日期适合车载或运动轨迹记录。后面我会把这两条语句逐字段拆开讲这是解析流程里最核心的环节。这里还要强调一个容易忽略的参数波特率。绝大多数GPS模块默认是9600波特率也有模块出厂设成115200比如部分4GGPS模组。如果串口工具波特率设置不对看到的就是一堆乱码。这一点在后面的实操环节会再次提到算是新手最常犯的第一个错误。2. 一帧NMEA数据长什么样GGA与RMC核心字段逐行拆解2.1 从一条$GPGGA看起每个数字背后是什么我随便拿一条真实的GGA语句举例这是我在窗边实测得到的$GPGGA,092204.999,4250.5589,S,14718.5084,E,1,04,24.4,19.7,M,--.-,M,,*6E拆开看逗号分隔的每一个字段都有明确含义。我按索引标出来字段索引示例值含义与说明0$GPGGA语句标识符GGA代表定位数据1092204.999UTC时间格式为时分秒毫秒09时22分04.999秒24250.5589纬度格式为度分ddmm.mmmm42度50.5589分3S南纬标志S表示南纬N表示北纬414718.5084经度格式为度分dddmm.mmmm147度18.5084分5E东经标志E表示东经W表示西经61定位质量0未定位或无效1GPS单点定位2差分定位704参与定位的卫星数量824.4HDOP水平精度因子值越小精度越高919.7海拔高度单位米10M高度单位固定为M米11--.-大地水准面高度差部分模块不输出则为空12M高度差单位固定为M13空差分定位时间非差分模式为空14空差分站ID非差分模式为空15*6E校验和星号后跟两位十六进制数绝大多数解析代码只关心第0~9个字段。其中第6个字段“定位质量”是判断数据是否有效的关键值等于1或2说明当前定位有效经纬度可以拿去用值是0说明卫星没锁定经纬度还停留在上次的位置或者是空数据。2.2$GPRMC怎么读速度、航向和有效状态都在这RMC语句在运动场景下非常有用比GGA多出了速度和航向信息。看一条实测示例$GPRMC,092204.999,A,4250.5589,S,14718.5084,E,123.45,89.68,211005,,,A*7D字段拆解如下字段索引示例值含义与说明0$GPRMC语句标识符RMC代表推荐最小定位信息1092204.999UTC时间同GGA时分秒毫秒2A定位状态A有效定位V无效警告34250.5589纬度度分格式4S南纬/北纬514718.5084经度度分格式6E东经/西经7123.45对地速度单位节海里/小时1节约等于1.852千米/小时889.68对地航向单位度相对真北方向9211005UTC日期格式为日/月/年21日10月05年10空磁偏角部分模块为空11空磁偏角方向E/W12A定位模式A自主定位D差分定位E估算N无效13*7D校验和注意RMC第2个字段是单字符A/V判断有效性时比GGA更直接。A就是定位有效V代表当前数据无效。我在写解析程序时一般优先用RMC判断有效状态因为它连日期都给了在记录轨迹时间戳时很方便。2.3 校验和算法怎么防止一帧脏数据毁掉整个定位NMEA-0183每一行语句在*后面都有一个两位十六进制校验和。算法不复杂但很多人会忽略把$和*之间的所有字符不包含$和*逐字节做异或运算得到一个8位整数转成两位大写十六进制就是校验和。拿GGA那行举例需要参与校验的是从GPGGA开始到校验前最后一个逗号为止的所有字符。用Python写校验函数非常简单def calculate_checksum(sentence_without_prefix): checksum 0 for char in sentence_without_prefix: checksum ^ ord(char) return f{checksum:02X}实际解析时先按*把语句拆成“数据区”和“校验区”对数据区计算校验和再和校验区比对。不一致就丢弃这一帧。我实测过串口在长线传输、干扰环境下确实会出现个别字符跳变的情况如果不做校验解析出来的经纬度可能突然跳到几百公里外轨迹上出现一个离谱的毛刺点。解析流程里加一道校验拦截是性价比最高的稳定性优化。3. 从接线到串口数据读取完整实操过程3.1 模块接线与上电注意事项先说硬件接线。绝大多数GPS模块引出的脚位都差不多VCC、GND、TX、RX有的还有PPS秒脉冲和1PPS输出。只需要把模块的TX接到单片机的RX模块的RX接到单片机的TX交叉连接即可。我第一次焊模块时就踩过坑把TX接TX、RX接RX结果串口一点动静都没有排查了半天才发现是同向对接信号根本没通。供电方面要特别小心。很多GPS模块比如中科微的AT6558系列工作电压是3.3V但板载了LDO可以直接用5V供电。如果模块没有任何稳压电路你用5V直接怼上去大概率直接烧掉。稳妥做法是先看模块丝印或者卖家说明默认按3.3V供电TTL电平也选3.3V。连接单片机和USB转TTL时同样要确认电平STM32的板子很多是3.3V逻辑传数据没问题但如果接的是5V单片机注意电平转换免得把模块串口引脚打坏。GPS天线朝向也很重要。模块背面那一整块是陶瓷天线必须朝向天空。有人把它贴在铁板下面、扣在金属外壳里结果死活搜不到星这其实是天线被屏蔽了。室内靠窗能搜到几颗星但定位效果不稳定空旷的窗边或者室外冷启动通常在30秒到2分钟之间。3.2 用串口工具直接看原始数据第一手数据长什么样拿到GPS模块后我建议第一件事不是写代码而是先用USB转TTL模块接电脑打开串口工具直接看原始数据。这一步能帮你提前确认模块工作是否正常、波特率是多少、输出哪些语句为后续写解析代码打下基础。接线方法GPS模块的TX接USB转TTL的RXGPS模块的RX接USB转TTL的TX共地然后插电脑。打开串口工具我用过sscom、MobaXterm的串口模式还有Arduino IDE自带的串口监视器波特率选9600打开串口。如果一切正常你会看到一行行以$开头的ASCII字符串不断滚动。有的模块默认只输出GGA、RMC和GSV有的输出全部语句。看到数据后先确认RMC中第2个字段是不是A$GPRMC,055702.000,A,3115.5584,N,12126.2845,E,0.00,0.00,050423,,,A*7A这个例子里A就代表定位有效。如果显示V说明GPS还在搜星状态把天线往窗户边挪一挪等一会儿再看。下面是一段我在室内窗边实测抓到的GGA完整输出你可以拿来练手解析$GPGGA,055702.000,3115.5584,N,12126.2845,E,1,07,1.2,8.5,M,12.3,M,,*5B注意经纬度字段第2位是3115.5584代表的是北纬31度15.5584分解析成十进制度数是31 15.5584/60 31.259306。关于度分转换的具体算法下一节详细展开。3.3 用Python写一个最小解析demo从串口到十进制度数串口工具确认数据正常后就可以上代码了。我用Python的pyserial库写过一个最小的GPS解析程序整个过程就三步读串口一行、校验、按逗号拆字段并转换经纬度。先把依赖装上pip install pyserial然后直接写解析脚本import serial def calculate_checksum(payload): checksum 0 for ch in payload: checksum ^ ord(ch) return f{checksum:02X} def convert_to_decimal(coord, direction): # coord 形如 ddmm.mmmm 或 dddmm.mmmm # 度分转换为十进制度度 分/60 if not coord: return None dot_index coord.index(.) degrees int(coord[:dot_index - 2]) minutes float(coord[dot_index - 2:]) decimal degrees minutes / 60.0 if direction in (S, W): decimal -decimal return decimal def parse_gga(sentence): if not sentence.startswith($GPGGA): return None star_index sentence.index(*) payload sentence[1:star_index] received_checksum sentence[star_index 1:] if calculate_checksum(payload) ! received_checksum: return None fields payload.split(,) # 字段顺序见前面表格 if len(fields) 6: return None # 判断定位质量1或2才有效 fix_quality fields[5] if fix_quality not in (1, 2): return None latitude convert_to_decimal(fields[2], fields[3]) # 纬度 longitude convert_to_decimal(fields[4], fields[5]) # 经度 satellites fields[6] altitude fields[8] if len(fields) 8 else None return { lat: latitude, lon: longitude, satellites: satellites, altitude: altitude } ser serial.Serial(/dev/ttyUSB0, 9600, timeout1) while True: line ser.readline().decode(ascii, errorsignore).strip() if line.startswith($GPGGA): result parse_gga(line) if result: print(f经度: {result[lon]:.6f}, 纬度: {result[lat]:.6f}, 卫星数: {result[satellites]}, 海拔: {result[altitude]}m)这段代码看起来不长但已经把NMEA解析最关键的几个点都覆盖了校验和判断、度分转换、定位质量判断。convert_to_decimal函数是核心它先找到小数点位置往前倒推两位是“分”的整数部分之前是“度”再把分除以60加进去。南纬和西经要转成负值很多人在这一步栽跟头坐标反了或者符号丢了地图上定位点直接跑到另一个半球。我建议你把这段代码保存下来等GPS模块到手后跑一遍看到串口滚动输出经纬度你就已经完成了从“原始字符串”到“可用坐标”的完整链路。4. GPS解析的通用流程与精度优化技巧4.1 从原始数据到坐标的全流程一个能复用的解析架构在实际项目中我不会把解析逻辑写死在while循环里而是整理成一个小的解析模块。通用流程就五步串口读取原始字节流设定波特率、超时时间持续读取。按行切分数据NMEA语句以\r\n结尾按行切分最简单同时能避免粘包问题。解析语句类型通过每行开头的$GPGGA、$GPRMC等标识判断是哪条语句。校验和验证算异或和*后面两位比对不通过直接丢弃。提取字段并转换按逗号拆分提取目标字段做度分转换和符号处理。这个架构的优点在于每一层都可以单独测试。你可以先喂一段固定字符串进去验证校验函数对不对再验证度分转换函数最后再接串口跑真实数据。如果一上来就全部糊在一起写出了问题很难定位是串口读取问题、数据格式问题还是转换逻辑问题。我还习惯再加一个“数据新鲜度”判断GPS模块在定位有效后会持续输出数据但一旦卫星信号丢失模块输出的还是携带旧经纬度的GGA语句只是定位质量字段变成0。如果程序只看字段格式很容易把旧数据当成新数据用。我一般会记录每帧数据的到达时间超过3秒没收到有效定位就认为当前位置是失效的上传平台时打一个离线标签。4.2 定位精度没法提升时先检查这些因素很多人换了贵价模块后发现精度并没有比便宜的好多少就开始怀疑模块有问题。实际情况是GPS定位精度受环境影响极大NMEA数据里能直观反映精度的是GGA第8个字段HDOP和卫星数量。我整理了一个经验参考表HDOP值精度等级说明 1.0优秀多见于开阔地带卫星数量多且几何分布好1.0~2.0良好正常室外定位水平误差约3~8米2.0~4.0一般略有遮挡或者卫星数偏少误差可能到15米 4.0较差室内、高楼峡谷、密集树冠下常见不建议做精度敏感应用我在项目里遇到过一种情况HDOP稳定在1.0以内经纬度输出却很飘。排查到最后发现是模块的PPS引脚没有做滤波导致模块内部参考时钟抖动影响了定位解算。这里想表达的是模块标称精度只是理想值实际效果依赖天线、电源纹波、周围环境这三大因素。电源纹波大的板子GPS信号非常容易受干扰建议模块供电处加一个10uF以上的去耦电容。4.3 地图坐标与GPS坐标的差异WGS-84、火星坐标与偏转还有一个很多人会踩的坑把GPS模块输出的经纬度直接标到国内地图平台上结果发现位置偏了几十米甚至几百米。这不是GPS模块坏了而是坐标系统的问题。GPS模块默认输出的坐标基于WGS-84World Geodetic System 1984国际标准这是一个全球统一的坐标系。但国内地图产品比如高德、百度使用的是经过偏移处理的坐标系统其中最常见的是GCJ-02“火星坐标”百度的BD-09又是在火星基础上做了第二次加密。你需要把GPS解析出的WGS-84经纬度换算成目标平台的坐标系后再展示。坐标转换有现成库可以用Python生态里有coord_convert等库C/C也有开源转换函数核心算法就是把经纬度做一次基于椭圆体参数的平移变换。在写定位采集程序时一定给经纬度单独留一个坐标转换模块方便后面适配不同平台。5. 常见问题与排查技巧实录5.1 GPS模块搜不到星或长时间无输出排查这类问题我一般按下面的顺序来看串口有没有数据先开串口工具看有没有$开头字符串滚出来。如果一行都没有检查TX/RX是否接反、波特率是否正确、模块供电是否正常。看数据里有没有GGA和RMC如果有GSV一直在变但没有GGA说明模块还在搜星阶段如果GGA有输出但定位质量是0说明虽然看到卫星但没有定位成功。看天线环境把模块放到室外或窗边空旷处避免金属遮挡和强干扰源。看冷启动时间首次通电或者模块断电很久后需要重新下载星历这个过程可能持续几十秒到几分钟不要一开机没定位就以为坏了。我遇到过最离奇的一次是模块所有引脚都正常串口也有数据但就是没有任何卫星信息。排查到最后发现是模块的散热焊盘焊得不好地线接触不良GPS射频信号根本没有被正常处理。这类问题很难从外部看出端倪只能用替换法排除。5.2 解析出来的经纬度差了好几公里这个问题的诊断方向很明确。经纬度偏差达到公里级通常只有四种可能度分转换算错最常见。把4250.5589直接当成42.505589度用差了将近0.3度大概是30公里误差。记住小数点前最后两位或三位是“分”要除以60。南北纬/东西经符号丢失南纬没转负值位置跑到赤道另一侧差得就很离谱。定位质量没有判断把定位质量字段为0无效的数据也拿去用了位置可能是上一个有效点的旧值一两公里偏差很正常。坐标混用拿WGS-84数据直接标到GCJ-02地图上偏几十米到几百米在小范围电子地图上特别明显。我建议在代码里加一个断言式检查纬度绝对值必须在0到90之间经度绝对值必须在0到180之间不符合直接丢弃。这一条能拦住绝大多数脏数据。5.3 串口数据乱码、断帧和半包问题怎么处理GPS数据是持续的ASCII流单片机接收时不注意处理很容易出现半包数据。比如你按固定长度去读串口一条NMEA语句被拦腰截断解析自然失败。我的习惯是按\r\n做行切分维护一个环形缓冲区等收到完整的\n再处理一行彻底规避半包问题。如果乱码是偶发的通常是波特率不匹配或者线路干扰。波特率设置错误表现为整片乱码线路干扰表现为随机几个字符变成?或乱码伴随偶发的校验失败。USB转TTL模块质量差、杜邦线太长超过20cm就要注意都会引入干扰。还有一个容易忽略的点GPS模块串口输出的数据量不大但GSV语句一多低性能单片机如果不及时读取串口缓冲区会有溢出丢数据的情况。处理办法是提高串口中断优先级或者减少不需要的语句输出。部分模块支持用专用配置指令关闭GSV、GSA等不必要语句只保留GGA和RMC能显著降低数据量。6. 经验总结真正用时才发现的门道做定位项目这几年最大的体会是NMEA-0183协议本身不难难的是把它放到真实环境里用起来。一个小小的GPS模块要处理好串口电平匹配、天线布局、冷热启动、数据有效性判断、坐标系转换才能在最终产品里稳定输出一个可信的位置。我建议新手不要急着上复杂的双频模块、RTK方案先把最基础的UART转NMEA解析跑通再用误差分析去判断上高精度方案的必要性避免盲目堆硬件。最后分享一个很实用的习惯先把原始串口数据录下来存成日志文件再离线调试解析逻辑。这样你手头永远有一份可复现的数据集改代码不会受天气和天线位置影响。当初我就是靠这个习惯快速定位过好几个“理论上绝对不可能”的Bug比如RMC语句里日期字段少了一位数导致时间戳错乱、某个模块会把磁偏角输出成空字段导致字段错位。有了日志所有解析问题都能在几分钟内复原和核查。