资讯详情

资讯详情

VC6.0下GPS串口数据采集程序设计与NMEA协议解析实战

简介VC6.0环境下开发的GPS数据采集程序面向GIS、嵌入式及串口通信初学者旨在解决从GPS接收机获取实时定位数据并完成NMEA协议解析的关键问题。程序覆盖串口打开、波特率/数据位/停止位配置、命令发送、数据流监听与读取等完整流程并提供GPGGA、GPGLL等典型报文的解析示例帮助理解纬度、经度、高度、时间等字段的提取方法。代码还展示了利用MFC构建简单GUI实时显示数据、以及接入SQLite或Access进行记录存储的扩展思路并考虑了异常处理与程序稳定性便于在此基础上接入车辆监控、户外导航等实际场景。压缩包大小为2.99MB已有124人学习。通过研读工程代码读者可掌握VC6.0串口编程基础、NMEA协议格式及调试优化策略为后续独立开发数据采集类软件打下扎实基础。 做车载终端测试那阵子我手里最顺手的工具之一就是这个VC6.0下写的GPS数据采集程序。它通过串口读取GPS/北斗模块输出的NMEA协议数据实时显示经纬度、速度、定位状态同时把原始报文落盘存成日志。功能不复杂但非常能打。如果你也在做Windows老环境下串口类上位机或者刚接触GPS模块联调、NMEA协议解析这篇文章应该能帮你少走不少弯路。我会把程序架构、串口通信、协议解析和排查经验完整拆开讲代码都是可以直接抄的级别。1. 项目背景与整体设计思路1.1 为什么在VC6.0下写GPS数据采集现在写上位机大家首选C#、Python甚至Electron但很多老设备、老测试台尤其是工业现场和车载电子实验室Windows XP/2000的老工控机还在服役。这类机器上跑得最流畅的还是VC6.0编译出来的原生程序不需要装.NET运行时双击就能跑内存占用才几MB。GPS数据采集本身对计算性能要求极低核心工作是串口收发和字符串解析这两个恰好是C的强项。VC6.0虽然老但配合MSComm ActiveX控件做串口通信几乎是开箱即用编码效率不比高级语言差。当时项目要求是24小时连续采集不崩溃、不丢帧VC6配合简单的消息循环能轻松满足。1.2 程序功能和技术选型这套程序的核心功能如下自动枚举并打开指定串口配置波特率、校验位、数据位和停止位实时接收GPS模块发来的NMEA 0183语句包括GGA、RMC、GSV等解析出经纬度、UTC时间、定位状态、卫星数量、海拔、对地速度等关键字段在界面上实时刷新显示同时把原始NMEA数据原样写入本地日志文件记录采集开始/停止时间支持长时间无人值守运行选型上串口通信采用MSComm控件界面框架用MFC对话框。原因很直接MSComm的事件驱动模型在VC6环境下非常稳定OnComm回调不阻塞界面数据怎么来就怎么收适合实时采集场景。解析层自己手写字符串处理不依赖第三方库保证在任何精简系统上都能运行。1.3 这套方案适合谁参考这个项目适合这几类人一是刚接触GPS模块想快速验证模块好坏和输出数据格式的开发者二是需要把GPS数据接入自家系统但对协议解析还不熟悉的上位机工程师三是还在维护老设备想把串口采集工具现代化的运维人员。学会了这套思路换个串口设备也一样用。因为本质就是一条流水线接收字节流按协议切帧解析字段显示和存储。把GPS换成条码扫描枪、工业传感器、PLC串口模块代码框架完全能复用只需要改协议解析那一段。2. 硬件接线与串口通信基础2.1 GPS模块选型和接线要点项目用的模块是常见的u-blox NEO-6M系列这类模块便宜、稳定、上手资料多。如果你手头是中科微ATGM336H或者其它国产模块串口协议基本兼容因为大家都遵循NMEA 0183标准输出。接线其实很简单VCC接5V或3.3V注意模块手册要求别一上来就接12VGND共地这个必须接不共地串口通信绝对不稳定TX接电脑串口RXRX接电脑串口TX交叉接有源天线要注意安装位置尽量放在窗口或者室外保证天空视野。模块上电后正常情况下几秒到几十秒就能输出有效的定位数据红灯闪烁频率会变化可以通过这个判断是否定位成功。2.2 串口参数和NMEA协议基础GPS模块出厂默认波特率一般是9600数据位8位无校验1位停止位也就是“9600,n,8,1”。不过现在很多新模块默认115200配模块之前务必先看一遍模块AT指令手册或者用卖家提供的上位机改一下配置。NMEA 0183协议本质是ASCII字符串以“$”开头以回车换行\r\n结尾逗号分隔字段。最常用的两条语句是GGA和RMCGGA包含了定位质量、经纬度、海拔、差分状态是判断定位是否可用的重要依据RMC包含了推荐的最小定位信息有日期、时间、经纬度、速度、航向导航类应用靠它例如一条GGA报文长这样$GPGGA,092751.000,2233.4472,N,11356.0737,E,1,8,0.9,12.4,M,-2.1,M,,*5B字段含义分别是UTC时间、纬度度分格式、南北纬、经度度分格式、东西经、定位状态0未定位1单点定位2差分定位、卫星数、HDOP水平精度因子、海拔、海拔单位、大地水准面高度等。2.3 为什么需要缓冲机制GPS模块的输出频率一般是1Hz也就是每秒一帧完整数据有的模块可以配成5Hz、10Hz。看起来数据量不大但串口是字节流应用程序每次收到的不是完整的一行而是乱七八糟的字节碎片。比如第一次收到“$GPGGA,0927”下一次收到“51.000,2233.4472,N...”。这就是串口通信最核心的坑必须自己做数据缓冲把不完整的行暂存起来等收到完整的\r\n再交给解析函数处理。否则解析出来的字段必然是乱的。程序里维护一个CString类型的m_strBuffer每次收到数据就往后面追加然后循环查找换行符每找到一条完整语句就切割出来处理剩下的继续留在缓冲区。3. VC6.0下的串口编程实现3.1 对话框界面和控件布置新建一个MFC对话框工程在资源编辑器里拖好控件。主要控件有串口号下拉框、波特率下拉框、打开/关闭串口按钮、经纬度显示编辑框、定位状态框、日志路径框、停止采集按钮。在对话框初始化函数OnInitDialog里做以下事情枚举可用串口填入下拉框。枚举方法很简单从COM1到COM16逐个尝试打开能打开就说明存在。串口被别的程序占用时会打开失败这种情况下直接跳过就好。波特率下拉框提前填好常用的4800、9600、19200、38400、115200默认选9600。3.2 MSComm控件的初始化和关键配置在对话框头文件里添加MSComm控件的包装类或者在资源编辑器里直接插入ActiveX控件然后给控件关联一个变量m_Comm。打开串口的代码是这样void CGPSDlg::OnBtnOpen() { if (m_Comm.GetPortOpen()) m_Comm.SetPortOpen(FALSE); m_Comm.SetCommPort(m_nPort); // 选中的串口号 m_Comm.SetSettings(9600,n,8,1); // 波特率,校验,数据位,停止位 m_Comm.SetInputMode(1); // 1表示二进制方式接收0是文本方式 m_Comm.SetRThreshold(1); // 每接收1个字节就触发OnComm事件 m_Comm.SetInputLen(0); // 一次性读取缓冲区中全部数据 m_Comm.SetPortOpen(TRUE); }这几个参数是重点。SetRThreshold(1)表示缓冲区每收到1个字节都会触发OnComm事件实时性最高。但要注意事件频率高处理不当会占CPU好在GPS数据量不大实测CPU占用可以忽略。SetInputLen(0)是必须的否则GetInput()只能按指定字节数读取容易漏数据。3.3 接收事件和缓冲区处理OnComm事件里写接收逻辑这是整个程序的心脏void CGPSDlg::OnCommMscomm() { VARIANT vResponse; if (m_Comm.GetCommEvent() 2) // 2表示接收缓冲区内有字符 { vResponse m_Comm.GetInput(); COleSafeArray saVariant(vResponse); LONG lLen saVariant.GetOneDimSize(); CString strData; for (LONG i 0; i lLen; i) { BYTE bt; saVariant.GetElement(i, bt); strData (char)bt; } m_strBuffer strData; // 循环提取完整NMEA行 int nPos; while ((nPos m_strBuffer.Find(\n)) ! -1) { CString strLine m_strBuffer.Left(nPos); m_strBuffer m_strBuffer.Mid(nPos 1); strLine.TrimRight(\r); ProcessNMEA(strLine); } } }缓冲区切行是这套程序的精华。因为不知道GPS模块什么时候发数据也不能保证一次就收到完整的一行所以把每次收到的内容追加到m_strBuffer然后不断找换行符找到一条切一条。剩下的碎片留在缓冲区等下次数据来再拼接这样无论数据分成多少片到达解析逻辑始终是对的。实测中这个方案非常稳。我故意用串口调试工具把一条NMEA报文拆成5段、每段间隔200毫秒发送程序依然能正确解析出经纬度一行都不丢。4. NMEA协议解析与界面显示4.1 解析GGA语句提取定位信息ProcessNMEA函数按帧头分发到不同解析函数这里重点说GGA解析。NMEA字段用逗号分割最简单的做法是做一个按逗号拆分的辅助函数void SplitString(const CString strSrc, CStringArray arrDest, char chSep) { arrDest.RemoveAll(); int nPos 0; CString strTemp strSrc; while ((nPos strTemp.Find(chSep)) ! -1) { arrDest.Add(strTemp.Left(nPos)); strTemp strTemp.Mid(nPos 1); } arrDest.Add(strTemp); }然后解析GGAvoid CGPSDlg::ParseGGA(const CString strLine) { CStringArray arr; SplitString(strLine, arr, ,); if (arr.GetSize() 15) return; if (arr[0] ! $GPGGA arr[0] ! $GNGGA) return; // 0: 帧头 1: UTC时间 2: 纬度 3: N/S 4: 经度 5: E/W // 6: 定位状态 7: 卫星数量 8: HDOP 9: 海拔 m_strSatellite arr[7]; m_strStatus arr[6] 1 ? 单点定位 : (arr[6] 2 ? 差分定位 : 未定位); m_strAltitude arr[9] m; // 转换为度 double dLat ConvertNmeaToDegree(arr[2]); double dLon ConvertNmeaToDegree(arr[4]); m_strLat.Format(%.6f, dLat); m_strLon.Format(%.6f, dLon); }NMEA里的经纬度是“度分”格式比如2233.4472表示22度33.4472分。转换成常用的小数度需要这样double ConvertNmeaToDegree(const CString strValue) { double dVal atof(strValue); int nDegree (int)(dVal / 100); double dMinute dVal - nDegree * 100; return nDegree dMinute / 60.0; }如果不做这个转换直接用原始数值会差大约30倍地图软件里出现的位置会莫名其妙跑到几公里之外这是新手最容易犯的错。4.2 RMC语句解析速度、航向和日期RMC包含速度、航向、日期解析逻辑和GGA类似只是字段位置不一样。RMC的典型格式是$GPRMC,092751.000,A,2233.4472,N,11356.0737,E,0.4,47.1,290125,,,D*7B字段0是帧头1是UTC时间2是状态A有效、V无效3是纬度4是南纬/北纬5是经度6是东经/西经7是速度节8是航向角9是日期。速度单位是节海里/小时显示的时候一般转成公里/小时乘以1.852就行。这个转换不复杂但容易漏如果直接用节数给客户看数字看起来怪怪的和车载仪表对不上。日期解析要注意一点NMEA里的日期是“日日月年年”格式比如290125表示2025年1月29日。如果要存成标准格式需要把字段顺序调整成“年-月-日”。4.3 界面刷新和日志落盘解析出来的数据要先更新界面。VC6的界面刷新有一个坑直接在OnComm事件里调UpdateData或者SetDlgItemText如果数据量大界面刷新会比较频繁控件会闪烁。GPS数据1Hz还好但如果模块配成了5Hz甚至10Hz界面闪烁就明显了。稳妥做法是定义一个定时器比如250毫秒刷新一次界面用成员变量保存最新的GPS数据定时器触发时一次性全部显示出来。这样既保证实时性又避免闪烁CPU占用也更低。日志落盘则放在OnComm事件里直接写因为如果也定时写程序崩溃时最后几秒数据就丢了。日志文件打开方式用追加模式文件名带上日期比如gps_20250129.log每收到一条NMEA语句就原样写入一行。这个日志非常有用做外场测试时可以把原始数据带回实验室重放排查是不是解析bug这就是NMEA回放调试法谁用谁知道。5. 常见问题与排查技巧实录5.1 串口打开失败和MSComm控件问题最常见的问题就是点击打开串口提示失败。原因通常是串口号被占用或者电脑上根本没有对应串口。排查方法很简单拔掉GPS模块打开设备管理器看端口号对不对或者用串口调试助手测试看能不能正常收发。还有一个老生常谈的问题VC6工程打包到别的机器上提示“没有注册MSComm控件”。这是因为MSComm控件是ActiveX控件需要在目标机器上注册。把mscomm32.ocx复制到system32目录然后运行“regsvr32 mscomm32.ocx”即可。开发时如果控件面板里找不到MSComm需要在菜单“Project - Add to Project - Components and Controls”里从Registered ActiveX Controls中插入。5.2 数据乱码和定位状态一直无效打开串口后界面上显示的内容全是乱码多半是波特率不匹配。GPS模块9600配置成115200去读收到的当然是乱码。另一个可能是串口设置里校验位写错了GPS模块一般是N81如果设成E81或者O81也会出现大量错位数据。定位状态一直是“未定位”的情况我遇到过两种一是天线没接好或者模块在室内收不到卫星信号这个别无他法只能把天线放到窗边或室外等一两分钟看定位灯变化二是模块的NMEA输出被配置关闭了只输出特定语句需要先用模块厂家的上位机工具把GGA和RMC输出打开。5.3 经纬度显示正常但地图上位置偏了几百米这是中国区域内玩GPS模块绕不开的坐标偏移问题。GPS模块输出的是WGS-84坐标系坐标而国内常见的电子地图用的是GCJ-02加密坐标系两者之间存在偏移在高德、百度地图上叠加显示会有300到600米的偏移。如果只是采集数据做数据分析WGS-84直接用没问题。但如果你要把位置显示到国内地图上就必须做坐标转换。转换算法网上有现成代码简单说就是把WGS-84坐标通过一个固定的偏移模型转换到GCJ-02。我不是每次都要转换而是在程序里加了一个配置开关需要地图显示时打开转换做原始数据采集时关掉这样最灵活。5.4 长时间运行丢数据的真相有人反馈程序跑几个小时后日志里偶尔缺秒怀疑是程序bug。其实大多数时候不怪程序而是GPS模块本身的问题。模块在多路径效应明显的环境高楼、隧道口、高架桥下会短暂失锁输出不连续这是物理层面的信号问题。另一个潜在原因是模块的更新频率和串口缓冲区大小。有些模块默认输出多个星座的多条语句比如GPS和北斗同时输出每秒会多出好几条GSV卫星视图语句。如果打开了GSV输出串口数据量会明显增大。解决办法是在模块配置工具里关闭不需要的语句只保留GGA和RMC。5.5 程序退出时崩溃程序关闭时崩溃多半是因为定时器没有销毁或者串口没有正常关闭。OnOK和OnCancel时一定要先关串口再销毁控件保险做法是在OnDestroy里加关闭逻辑void CGPSDlg::OnDestroy() { if (m_Comm.GetPortOpen()) m_Comm.SetPortOpen(FALSE); KillTimer(1); CDialog::OnDestroy(); }另外日志文件句柄也要确保在进程退出前关闭否则最后几条数据可能还在缓存里没有写进磁盘。收个尾分享一点我做这类工具的习惯GPS数据采集程序看着不起眼但它教会了我三件事串口数据必须先缓冲切帧再解析坐标格式转换永远是重中之重日志文件是排查问题最好的朋友。后来我做的其它串口采集工具包括工业传感器和PLC数据采集全都沿用了这套“接收-缓冲-切行-解析-显示-落盘”的框架换的只是解析协议那几十行代码。最后再分享一个小习惯外场测试时我总是从手机上把当前GPS坐标录下来同时把程序里显示的坐标也截图保存。两个坐标一对比就能立刻判断模块定位误差、坐标转换开关状态是否正常。这个小习惯帮我在现场排查掉不少看起来像是程序bug、实际却是信号质量差的问题。希望对你有用。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →