news 2026/10/26 13:22:52

VC6.0串口GPS采集:MSComm与NMEA 0183解析落盘

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
VC6.0串口GPS采集:MSComm与NMEA 0183解析落盘

简介:面向GIS与导航开发入门者的VC6.0 GPS数据采集程序源码,聚焦串口通信与NMEA报文解析这一典型实战场景。程序围绕GPS接收器的串口打开、波特率与数据位等参数配置展开,演示如何请求并监听数据流,再按逗号分隔解析GPGGA等报文,提取经纬度、高度与时间信息,并兼顾格式错误、通信中断等异常处理。配套内容还涉及基于MFC的简易界面设计,用按钮与文本框呈现实时定位数据,以及SQLite或Access记录保存、VC6.0调试与性能优化思路。压缩包约2.99MB,文件总数与类型明细上游暂未提供,解压后可按串口、解析、界面与存储等环节对照学习。已有126人学习,适合希望理解GPS采集完整链路、补齐串口编程与报文解析细节的初中级开发者参考。

1. 从一台老工控机说起:VC6.0 GPS数据采集程序到底解决什么问题

一台还在跑产线巡检的老工控机上,插着串口GPS模块,界面是典型的VC6.0对话框程序。GPS数据采集程序在VC6.0里跑起来,最反直觉的一点是:串口能打开、界面能刷数,不等于采到的数据能用。这套程序的核心不是“收字节”,而是把RS-232上连续到达的NMEA 0183语句拆成可校验、可落盘、可回放的经纬度与时间。它适合还在维护老工控项目、需要接手VC6.0串口采集链路的人,也适合拿它当NMEA解析和串口缓冲区的对照模板。工程包常见形态是.dsw/.dsp加对话框资源,依赖MSComm32.ocx;拿到后先别改解析,先让一条$GPRMC稳定通过校验和,再谈坐标转换和存盘。

2. 把VC6.0工程跑起来:MSComm控件注册与串口初始化

老工程最容易卡在第一步:工程能编译,运行时弹“没有注册类”,或者串口根本打不开。VC6.0时代串口采集常见两条路,一条是MSComm控件,一条是Win32串口API。这套GPS采集程序既然以VC6.0对话框工程形态出现,大概率走的是MSComm,因为界面拖控件、事件驱动收数,改起来快。先把运行环境、控件注册和串口参数钉死,后面解析才有意义。

2.1 为什么这套采集程序绕不开MSComm控件

MSComm是ActiveX串口控件,VC6.0里通过“Project → Add to Project → Components and Controls”注册后拖到对话框上,生成一个CMSComm成员。它的好处是OnComm事件直接告诉你“收到字节了”,不用自己写OVERLAPPED和WaitCommEvent;坏处是它依赖ocx注册,并且在UI线程里触发,处理不当会卡界面。

常见做法是把MSComm当“字节搬运工”:OnComm里只做接收和追加缓冲区,不做解析、不写文件、不刷列表。解析放到定时器或工作线程里按行消费。这样即使GPS模块输出频率高,界面也不至于一顿一顿。选型上,如果只是维护老工程,不建议上来就换Win32 API重写,先把MSComm的边界摸清,再决定要不要替掉。

串口参数没有玄学,NMEA 0183常见的串口配置是4800或9600波特率、8数据位、无校验、1停止位、无流控。模块手册写多少就用多少,别自作聪明改成115200。GPS模块上电后通常每秒输出一组语句,$GPRMC、$GPGGA、$GPGSV等混在一起,接收端必须按行处理,不能假设一次OnComm就是一条完整语句。

2.2 注册MSComm32.ocx与VC6.0工程配置

先确认系统里有MSCOMM32.OCX,并且是32位版本。VC6.0编出来的是32位程序,别拿64位控件糊弄。注册命令在管理员权限的命令行里执行:

regsvr32 /s MSCOMM32.OCX

/s表示静默注册,成功后会弹一个注册成功提示;如果失败,先看路径是不是含空格、当前用户有没有权限、ocx是不是被杀毒软件隔离。注册完在VC6.0里重新打开工程,对话框上右键“Insert ActiveX Control”,能找到Microsoft Communications Control就说明环境通了。

工程配置上有四个动作别漏:第一,确认.dsp里已经包含MSComm的包装类,通常是mscomm.cpp/mscomm.h;第二,对话框资源里控件ID别手工改乱,OnComm事件靠它绑定;第三,项目设置里C/C++的预处理定义不要动老工程的_AFXDLL,除非你清楚MFC链接方式;第四, release版也要注册ocx,别只测Debug。老工控机上如果装过多个版本,注册表里可能残留旧路径,必要时先反注册再注册。

2.3 串口初始化:参数、缓冲区和OnComm事件

打开串口的代码不要写成“点一次按钮就SetPortOpen(TRUE)”就完事。先判断当前是否已打开,再设参数,再清缓冲区,最后开端口。下面这段是VC6.0对话框类里的常见写法:

BOOL CGpsDlg::OpenGpsPort(int nPort, int nBaud) { if (m_comm.GetPortOpen()) m_comm.SetPortOpen(FALSE); m_comm.SetCommPort(nPort); // 1 表示 COM1,3 表示 COM3 CString strSettings; strSettings.Format("%d,N,8,1", nBaud); // NMEA 常用 4800 或 9600 m_comm.SetSettings(strSettings); m_comm.SetInputMode(1); // 1=二进制,按字节取 m_comm.SetRThreshold(1); // 收到 1 字节就触发 OnComm m_comm.SetInputLen(0); // 0=一次读整个接收缓冲区 m_comm.SetOutBufferCount(0); m_comm.SetInBufferCount(0); try { m_comm.SetPortOpen(TRUE); } catch (CException* e) { e->Delete(); return FALSE; } return TRUE; }

nPort是1基的端口号,不是字符串“COM3”,MSComm的CommPort属性只认short。strSettings里N表示无校验,8是数据位,1是停止位;如果模块手册要求偶校验,这里要改成E。InputMode=1让GetInput返回字节数组,便于处理半包;RThreshold=1保证每个字节都能进缓冲区,但会频繁触发事件,所以OnComm里只做搬运。InputLen=0表示一次取完接收缓冲区,避免残留。

OnComm事件里不要直接解析。先把VARIANT里的字节数组追加到成员缓冲区:

void CGpsDlg::OnCommMscomm1() { if (m_comm.GetCommEvent() != 2) // 2=接收事件 return; VARIANT vIn = m_comm.GetInput(); if (vIn.vt != (VT_ARRAY | VT_UI1)) return; long nUpper = 0; SafeArrayGetUBound(vIn.parray, 1, &nUpper); BYTE* pData = NULL; SafeArrayAccessData(vIn.parray, (void**)&pData); m_strRxBuf += CString((LPCSTR)pData, nUpper + 1); SafeArrayUnaccessData(vIn.parray); }

GetCommEvent()==2是MSComm的接收事件常量,别写成1。SafeArrayGetUBound取的是上界,长度要加1。m_strRxBuf是CString成员,NMEA语句是ASCII,不会出现0字节,所以用CString拼接够用;如果换成二进制协议,这里必须用BYTE数组加长度,不能靠CString。追加完就返回,解析放到下一步。

2.4 用最小闭环验证“能收数”再改解析

接上模块后,先做一个最小闭环:打开串口,把m_strRxBuf原样显示到一个多行Edit里,不做校验、不做坐标转换。如果能看到连续的$GPRMC、$GPGGA,说明串口参数、接线、控件注册都通了。看不到数据时,按这个顺序查:模块供电和天线、TX/RX是否交叉、波特率是否匹配、CommPort号是否对应、OnComm是否绑定。血泪经验是,很多“程序收不到数”最后查出来是USB转串口线把COM号变了,代码里还写死COM3。

能收数之后再把Edit显示换成按行切分。每次OnComm追加完缓冲区,检查里面有没有\n,有就切出一整行,校验和通过才送解析。这样即使一次收到半条语句,也不会把$GPRMC截断成垃圾。最小闭环跑通之前,不要动CSV落盘和列表刷新,不然出了问题分不清是串口、解析还是文件写入。

3. NMEA 0183拆包:从字节流到经纬度的解析链

GPS模块吐出来的不是“坐标”,是ASCII语句。VC6.0这边收到的是字节流,里面可能一次带三条语句,也可能一条语句被拆成两次。解析链要解决三件事:按行切、校验和、字段转换。任何一步偷懒,后面地图上就会差出几十公里,或者时间永远差8小时。

3.1 NMEA 0183语句结构与校验和边界

常见语句以$开头,以*加两位十六进制校验和结尾,字段用逗号分隔。$GPRMC管推荐最小定位信息,$GPGGA管定位质量、卫星数、海拔。$GPRMC的字段位置如下:

字段序号示例含义备注
0$GPRMC语句头也可能$GNRMC
1082245.00UTC时间hhmmss.ss
2A定位状态A有效,V无效
33112.3456纬度ddmm.mmmm
4N南北半球N/S
512123.4567经度dddmm.mmmm
6E东西半球E/W
70.02地面速度节
8123.4航向度
9230924UTC日期ddmmyy

校验和计算范围是$之后、*之前的所有字符,逐字节异或,不包括$和*。很多翻车现场是把$也异或进去,或者把*后面的回车换行算进去,结果永远对不上。校验和函数要能处理大小写十六进制,模块输出一般是大写,但回放文件可能被编辑器改过。

BOOL CheckNmeaSum(const CString& line) { int star = line.Find('*'); if (star <= 0 || star + 2 > line.GetLength()) return FALSE; BYTE sum = 0; for (int i = 1; i < star; i++) // 从$后一个字符开始 sum ^= (BYTE)line[i]; CString hex = line.Mid(star + 1, 2); int expect = 0; for (int i = 0; i < hex.GetLength(); i++) { char c = hex[i]; expect <<= 4; if (c >= '0' && c <= '9') expect += c - '0'; else if (c >= 'A' && c <= 'F') expect += c - 'A' + 10; else if (c >= 'a' && c <= 'f') expect += c - 'a' + 10; } return (sum == expect); }

star是*的位置,star+2判断后面至少有两个十六进制字符。循环从i=1开始,跳过$。expect按十六进制左移累加,不依赖strtol,在VC6.0里少引一个头文件麻烦。校验失败直接丢弃整行,不要把坏行送进解析,否则坐标会突然跳到0,0。

3.2 接收缓冲区:粘包、半包与按行切分

串口是流式设备,OnComm触发时缓冲区里可能是$GPRMC,...\r\n$GPGGA,...\r\n,也可能是半条$GPRMC,0822。常见做法是维护一个CString接收缓冲区,每次追加新字节,然后循环找\n,找到就切出一行,剩余部分留在缓冲区。切出来的行去掉\r\n,再送校验和解析。

void CGpsDlg::ConsumeRxBuffer() { int pos = -1; while ((pos = m_strRxBuf.Find('\n')) >= 0) { CString line = m_strRxBuf.Left(pos); m_strRxBuf = m_strRxBuf.Mid(pos + 1); line.TrimRight("\r\n"); if (line.GetLength() < 6) continue; if (line[0] != '$') continue; if (!CheckNmeaSum(line)) continue; if (line.Left(6) == "$GPRMC") ParseGPRMC(line); else if (line.Left(6) == "$GPGGA") ParseGPGGA(line); } }

Find('\n')找换行,Left(pos)取整行,Mid(pos+1)保留剩余。TrimRight去掉回车,因为不同模块可能只发\n,也可能发\r\n。line[0] != '$'过滤掉上电时的乱码;CheckNmeaSum过滤坏行。这个循环要放在OnComm之后、定时器里调用,别在OnComm里递归处理,否则事件嵌套容易把界面拖死。

缓冲区要设上限。如果模块一直发但程序不消费,CString会一直涨。常见做法是超过8KB还没找到换行就清空,说明波特率或接线有问题,收到的不是完整语句。清空时记一条日志,别静默吞掉。

3.3 GPRMC与GPGGA字段解析

字段拆分不要用AfxExtractSubString一把梭,自己写一个按逗号切分的函数更可控。下面这个函数最多切max个字段,返回实际字段数:

int SplitFields(const CString& s, CString* out, int max) { int count = 0, start = 0; for (int i = 0; i <= s.GetLength(); i++) { if (i == s.GetLength() || s[i] == ',') { if (count < max) out[count++] = s.Mid(start, i - start); start = i + 1; } } return count; }

i <= s.GetLength()保证最后一个字段被切出来;s[i]在i等于长度时不会访问越界,因为前面的||短路。切完$GPRMC后先看f[2]是不是A,V表示无效定位,直接丢。经纬度转换用NmeaToDegree:

double NmeaToDegree(const CString& v, const CString& hemi) { double raw = atof(v); int deg = (int)(raw / 100); double min = raw - deg * 100; double d = deg + min / 60.0; if (hemi == "S" || hemi == "W") d = -d; return d; }

raw/100取整数度,raw - deg*100取分,分除以60转十进制度。纬度是ddmm.mmmm,经度是dddmm.mmmm,但除以100的逻辑一样,因为经度度的部分可能是三位,raw/100仍然能拿到整数度。S和W取负,别只处理纬度忘了经度。

$GPGGA的字段侧重点不同:f[6]是定位质量,0无效,1单点定位,2差分定位;f[7]卫星数;f[8]HDOP;f[9]海拔。采集时可以把这些一起记下来,后面判断数据可信度比只看$GPRMC的A/V更细。

3.4 经纬度、速度与UTC的转换细节

速度字段单位是节,1节=1.852公里/小时。存CSV时建议同时保留原始节和换算后的km/h,不然以后看表还得猜单位。航向是正北顺时针角度,0到359.9,别拿它当弧度。

UTC时间在$GPRMC里是hhmmss.ss,日期在f[9]是ddmmyy。VC6.0里把UTC转本地时间,常见做法是先拼成CTime,再调用GetLocalTime或自己按时区偏移。注意CTime的月份从1开始,年份要补2000。跨天时如果只取时间不取日期,23:59之后的点会排到00:00前面,回放时轨迹会乱跳。采集表里至少保留UTC日期和时间两列,或者直接存一个YYYY-MM-DD HH:MM:SS字符串。

还有一点:$GPRMC的f[1]在无效定位时可能是空字段,atof会返回0,别把它当成00:00:00。解析前先判断字段长度和定位状态,空字段直接跳过。

4. 采集落盘与界面刷新:CSV写入、时间处理和线程边界

串口通了、解析对了,下一步是让数据能留下来。很多老程序只把经纬度刷在界面上,关掉就没了;或者每收一条就CStdioFile打开写一次,跑几小时磁盘全是小文件句柄。落盘和刷新要分开设计:文件按行追加,界面按固定频率刷新,中间用队列隔开。

4.1 CSV落盘字段设计:别把原始语句丢掉

CSV字段不要只存经纬度。定位质量、卫星数、HDOP、速度、航向、UTC时间、原始语句都值得留。原始语句在排查“这个点为什么漂”时是后悔药,能回放、能重新校验。字段建议如下:

列名类型说明
utc_time字符串hhmmss.ss
utc_date字符串ddmmyy
lat_deg浮点十进制度,南纬为负
lon_deg浮点十进制度,西经为负
speed_kmh浮点节×1.852
course浮点0-359.9
fix_quality整数$GPGGA的f[6]
satellites整数$GPGGA的f[7]
hdop浮点$GPGGA的f[8]
raw_nmea字符串原始语句,逗号替换成分号

写文件用CStdioFile追加模式,打开时带modeCreate | modeNoTruncate,写完立刻关闭。不要长期持有文件句柄,老工控机断电、拔盘时句柄不释放容易留下坏文件。原始语句里逗号多,直接塞CSV会多列,写之前把逗号替换成分号,或者整列加双引号。

void CGpsDlg::WriteFixCsv(const GpsFix& f) { CStdioFile file; if (!file.Open(m_strLogPath, CFile::modeCreate | CFile::modeNoTruncate | CFile::modeWrite | CFile::typeText)) return; CString raw = f.rawLine; raw.Replace(",", ";"); CString line; line.Format("%s,%s,%.6f,%.6f,%.2f,%.1f,%d,%d,%.1f,%s\r\n", f.utcTime, f.utcDate, f.lat, f.lon, f.speedKnot * 1.852, f.course, f.fixQuality, f.satellites, f.hdop, raw); file.SeekToEnd(); file.WriteString(line); file.Close(); }

modeNoTruncate保证每次追加不覆盖原文件;SeekToEnd把写指针移到末尾。%.6f给经纬度足够精度,NMEA的ddmm.mmmm大约能到0.0001分,十进制度保留6位不会丢太多。raw.Replace是为了不破坏CSV列数,代价是原始语句里的逗号变成分号,回放时再替换回来。

4.2 时间处理:UTC、本地时区与跨天

采集表里存UTC最稳,因为GPS模块给的就是UTC,换时区不用改历史数据。界面显示可以转本地时间,但落盘不要混。VC6.0里用CTime拼UTC:

CTime MakeUtcTime(const CString& date, const CString& time) { int day = atoi(date.Left(2)); int mon = atoi(date.Mid(2, 2)); int year = 2000 + atoi(date.Mid(4, 2)); int hour = atoi(time.Left(2)); int minute = atoi(time.Mid(2, 2)); int second = atoi(time.Mid(4, 2)); return CTime(year, mon, day, hour, minute, second); }

date是ddmmyy,time是hhmmss.ss,秒的小数部分可以丢,或者单独存毫秒。CTime构造函数月份从1开始,年份直接给四位。跨天判断不要用hour < prevHour,直接比较CTime对象。如果界面要显示本地时间,用GetTimeZoneInformation拿偏移,或者直接用CTime::GetLocalTm,但注意老工控机时区可能没设对,别把系统时区当成GPS时区。

4.3 界面刷新和文件写入的线程边界

MSComm的OnComm在UI线程触发,如果在里面直接WriteFixCsv,磁盘一慢界面就卡。常见做法是OnComm只把解析后的GpsFix推进一个环形队列,定时器每200ms取一批刷列表,工作线程每500ms取一批写文件。VC6.0里可以用CArray<GpsFix, GpsFix&>加临界区,简单够用。

CRITICAL_SECTION g_cs; CArray<GpsFix, GpsFix&> g_fixQueue; void CGpsDlg::PushFix(const GpsFix& f) { EnterCriticalSection(&g_cs); if (g_fixQueue.GetSize() < 2000) g_fixQueue.Add(f); LeaveCriticalSection(&g_cs); }

CRITICAL_SECTION在对话框初始化时InitializeCriticalSection,退出时DeleteCriticalSection。队列上限2000条,防止工作线程卡住时内存一直涨。刷界面用SetTimer,每次取队列前100条追加到CListCtrl,别每条都UpdateWindow。写文件线程用AfxBeginThread,循环Sleep(500)后批量取,写完清空已取部分。

4.4 断线重连与串口异常收尾

GPS模块可能被拔掉,USB转串口可能掉线。OnComm里如果GetCommEvent()返回非2,要记录错误码;连续多次收不到数据,比如10秒没有新行,就关端口再重开。重开前先SetPortOpen(FALSE),清空接收缓冲区,避免旧半包拼到新数据上。程序退出时先关串口,再停工作线程,最后删临界区,顺序反了容易在调试版里崩在LeaveCriticalSection。

5. 避坑排查:GPS采集程序最容易翻车的五个点

这套程序的技术栈不新,坑却很集中。下面五条都是维护老工程时反复遇到的,按“现象→原因→解决”记,比看一堆配置手册快。

5.1 现象:一打开串口就提示“无效的端口号”

现象是点“打开串口”后弹异常,或者SetPortOpen(TRUE)直接进catch,界面显示端口无效。原因通常不是代码,而是CommPort属性给错了:MSComm要的是1、3、5这样的短整型,不是字符串“COM3”;或者设备管理器里根本没有这个COM号,USB转串口线换了个口,代码里还写死3。还有一种情况是端口被别的程序占着,比如另一个采集软件或调试助手没关。

解决顺序:先在设备管理器确认端口号,再在程序里做端口枚举,至少把1到16都试一遍SetCommPort不抛异常。打开前先SetPortOpen(FALSE)释放旧句柄,再清InBufferCount。如果还不行,换一条USB转串口线,老工控机上前置面板的COM口有时候 BIOS 里被禁用了。

5.2 现象:数据只出一行或全是乱码

现象是Edit里只有一行$GPRMC,后面不更新;或者满屏烫烫烫、问号。原因分两类:一是波特率不匹配,模块是9600,程序设了4800,收到的字节被错位采样,校验和永远不过;二是InputMode或GetInput用法不对,把VARIANT当字符串读,字节数组被截断。还有一种是OnComm没绑定,控件ID改了但事件映射没跟着改。

解决:先确认模块手册的波特率,再用串口助手原样收一遍,看能不能看到完整NMEA。程序里把InputMode=1,用SafeArrayAccessData取字节,不要VariantChangeType转BSTR。OnComm里加一句日志,记录GetCommEvent(),确认事件真的进来了。

5.3 现象:校验和永远对不上

现象是串口助手看语句完全正常,程序里CheckNmeaSum一直返回FALSE,所有行都被丢。原因多半是校验范围算错:把$异或进去了,或者把*后面的\r也算进去了;也有的是模块输出小写十六进制,解析函数只认大写;还有的是编辑器把回放文件存成了UTF-8带BOM,行首多了不可见字节。

解决:打印一行line的十六进制,确认第一个字节是不是0x24($),*位置对不对。校验循环从i=1到star-1,不包含star。十六进制转换同时处理A-F和a-f。回放文件用ASCII保存,别用带BOM的UTF-8。

5.4 现象:经纬度差了几十公里

现象是地图上点落在另一个区,或者纬度看着差不多、经度偏了一大截。原因几乎都是ddmm.mmmm当成了十进制度:把3112.3456直接当31.123456度,实际应该是31度加12.3456分除以60。经度是dddmm.mmmm,度的部分三位,除以100照样能拆,但有人用substr(0,2)切纬度、substr(0,3)切经度,遇到纬度不足两位就错位。

解决:统一用NmeaToDegree,先raw/100取整数度,再raw - deg*100取分,分除以60。南纬、西经取负。验证时拿一条已知语句手算一遍,比如3112.3456,N应该约等于31.20576度,差太多就是转换写错。

5.5 现象:跑几小时后程序无响应

现象是刚开始正常,跑几小时后界面越来越卡,最后点不动,只能杀进程。原因常见三个:OnComm里直接写文件,磁盘慢时UI线程堵死;接收缓冲区只追加不消费,CString涨到几十MB;列表每收一条就InsertItem加EnsureVisible,消息队列被刷爆。

解决:OnComm只追加缓冲,解析和写盘挪到定时器/工作线程;缓冲区设8KB上限,超了清空并记日志;列表批量插入,每200ms最多刷100条,别EnsureVisible每行都调。跑长时间测试时开任务管理器看内存和GDI对象,涨得厉害就是句柄或缓冲没释放。

6. 把采集结果验到可信:回放、漂移过滤与一个小工具

现场蹲守成本高,GPS采集程序最值得先做的不是加功能,而是做回放。把一段真实NMEA文本存成文件,逐行喂给解析函数,校验和、坐标、时间、CSV写入全走一遍,比在车上晃一下午更容易复现问题。我一般会留一个ReplayNmea函数,读文件、去回车、按语句头分派,和串口链路共用同一套解析。

6.1 用文本回放替代现场蹲守

回放文件按行存原始语句,一行一条,不要带额外列。读取时用CStdioFile::ReadString,逐行TrimRight("\r\n"),只处理$GPRMC和$GPGGA。这样串口收到的半包问题在回放里复现不了,但校验和、字段错位、坐标转换、CSV列数这些逻辑问题都能提前暴露。

BOOL ReplayNmea(const CString& path) { CStdioFile file; if (!file.Open(path, CFile::modeRead | CFile::typeText)) return FALSE; CString line; while (file.ReadString(line)) { line.TrimRight("\r\n"); if (line.Left(6) == "$GPRMC") { GpsFix fix; if (ParseGPRMC(line, fix)) WriteFixCsv(fix); } } file.Close(); return TRUE; }

ParseGPRMC内部会再走一遍校验和,回放文件里故意留几条坏校验,确认坏行被丢弃而不是写进CSV。WriteFixCsv用临时路径,别覆盖现场采集文件。

6.2 静止漂移过滤:一个速度加距离的门槛

GPS静止时也会输出微小漂移,尤其是HDOP高的时候。简单过滤可以用速度和距离双门槛:当前速度小于0.5节,且和上一个记录点的球面距离小于5米,就不落盘。参数不用太激进,否则低速行走会被吃掉。

参数建议值说明
速度阈值0.5节(约0.93km/h)低于此值视为静止
距离阈值5米静止时相邻点抖动范围
HDOP上限4.0高于此值定位质量差,可标记不删
卫星数下限4少于4颗不建议参与轨迹
BOOL ShouldLog(const GpsFix& prev, const GpsFix& cur) { double dist = HaversineMeters(prev.lat, prev.lon, cur.lat, cur.lon); double speedMs = cur.speedKnot * 0.514444; if (speedMs < 0.5 * 0.514444 && dist < 5.0) return FALSE; return TRUE; }

HaversineMeters用球面距离,地球半径取6371000米。这个过滤只影响落盘,不影响界面显示;调试时把被过滤的点标成灰色,方便判断阈值是不是太狠。

6.3 验收时看哪几个参数

验收别只看“有数据”。打开CSV,先看UTC时间是不是每秒一条、有没有倒序;再看纬度经度小数位是不是6位、南纬西经有没有负号;然后挑一条原始语句手算校验和;最后看长时间跑下来内存和文件行数是否线性增长。回放加过滤这套走完,再上车实测,心里才有底。从那以后我每次接串口采集项目,都强制先用回放文件跑通校验、坐标和落盘三件事,再碰现场硬件。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/26 13:22:52

SQL标准差函数STDDEV在数据分析中的应用与实践

1. 标准差在数据分析中的核心价值 标准差是统计学中最基础也最重要的离散程度度量指标&#xff0c;它量化了一组数据点围绕其平均值的波动幅度。在SQL数据分析场景中&#xff0c;STDDEV函数让我们能够直接在数据库层面完成这种关键计算&#xff0c;无需将数据导出到外部工具。 …

作者头像 李华
网站建设 2026/10/26 13:22:49

开发治理一体化:把数据治理嵌入开发生命周期

简介&#xff1a;《网易数据治理白皮书8.0》PDF面向企业数据管理者、数据架构师与数据治理从业者&#xff0c;系统梳理从数据分类、数据资产定义到数据治理落地的方法框架。内容覆盖数据治理的政策背景与概念辨析、传统治理在质量、复杂性、合规和自动化方面的挑战&#xff0c;…

作者头像 李华
网站建设 2026/10/26 13:21:20

C++11嵌入ChaiScript脚本引擎:Makefile构建与热加载实践

简介&#xff1a;这份 ChaiScript 集成示例面向希望在 C11 可执行文件中嵌入脚本能力的开发者&#xff0c;重点解决动态配置、插件扩展与逻辑热更新等场景下的集成问题&#xff0c;适合具备一定 C 基础、想了解脚本绑定的中级读者。压缩包共 18 个文件&#xff0c;约 18KB&…

作者头像 李华
网站建设 2026/10/26 13:20:41

程序员AI技术变现实战:从模型到商业的5大路径

1. 程序员如何从零开始掌握AI技术变现 作为一名在AI领域摸爬滚打多年的技术老兵&#xff0c;我见过太多程序员空有一身AI技术却无法变现的案例。今天我想分享的&#xff0c;不是那些浮于表面的"AI创业指南"&#xff0c;而是真正经过实战验证的AI技术商业化路径。 AI…

作者头像 李华
网站建设 2026/10/26 13:20:30

2026大模型版图:模型与应用双维度选型全解析

今天是2026年10月6日。如果把这个月的热搜词拉出来看&#xff0c;其实是能读出不少东西的——有人搜"免费大模型api"&#xff0c;有人搜"怎么调用lm studio的大模型"&#xff0c;有人搜"ai应用 使用说明"&#xff0c;有人搜"大模型微调实战…

作者头像 李华
网站建设 2026/10/26 13:20:23

GraalVM JDK 17 x64安装与native-image避坑

简介&#xff1a;GraalVM JDK 17 Windows x64 二进制包&#xff0c;面向需要在 Windows 64 位环境体验 GraalVM 与 JDK 17 的 Java 开发者、架构师及多语言运行时研究者&#xff0c;可用于搭建高性能 JVM、Native Image 和多语言互操作实验环境。压缩包共 586 个文件&#xff0…

作者头像 李华