前面写了一系列关于Linux的文章,从这一篇开始换个题目,叫做GPS项目实战系列,后面的很多篇内容都会围绕着这个项目展开。这个项目要完成的任务,可以参考之前的文章:https://topsemic.com/2186.html
今天这篇文章的主题是GPS数据解析,关于GPS的基础知识,我在很早之前的一篇文章(https://topsemic.com/573.html)里有过介绍过,网上也有大量的内容可以参考,不在这里解释了。
关于硬件,之前一直使用的是NUC972板子,今天换一个I.MX 8M的板子,原因是之前那个板子上没有GPS模块,我这正好有个带GPS模块的I.MX板子。其实,对应用程序来说,硬件用什么都无所谓。
因为我的编程水平真的很菜,所以我一般写代码的套路是先从网上搜索,就是典型的拿来主义,先找一个能用的工程跑通,再根据需要去修改。经过简单搜索,我找到了下面的这篇文章,https://blog.csdn.net/zouleideboke/article/details/73521122
由于作者没有给完整工程的下载地址,所以我只能手动去复制代码,经过简单一通Ctrl+C和Ctrl+V操作,在GPSProj文件夹里整了下面五个文件
整完之后,咱们还得简单修改下,一是Makefile中交叉工具链的设置,
把Make的里第一句改成你实际使用的即可,
另外一处是main函数里,修改下你板上GPS模块对应的串口
char *dev_name="/dev/ttyUSB0";
我这里改成了我用的/dev/ttymxc1串口。
然后先去编译一把,看看有没有报错,因为很多时候网上的东西都无法正常编译。
之前我一直是在我的虚拟机上编译的,由于我电脑性能较差,经常把我电脑干卡机。今天想起自己有一个阿里云账号,装的是Ubuntu系统,所以就把它利用了一下,在它上面放了交叉工具链。访问它只需要通过SecureCRT工具登录即可,非常方便。
在编译时,一上来就报了一个错误,提示没有libstdc++.so.6文件
libstdc++.so.6: cannot open shared object file: No such file or directory
咋整,不会就问度娘或者谷哥呗,强烈建议技术相关的问题用谷哥,经过我多次对比后发现,在度娘那查了好久解决不了的问题,用谷哥很快就能搞定。至于怎么用谷哥,你自己开动脑筋去解决吧。上述问题解决方法如下:
apt-get install lib32stdc++6
然后再次编译,又报错了。。。
经过再一次网上搜索,我尝试把Makefile中命令行前面的空白(如下图红框部分所示)删掉,用Tab键代替,就可以了
这一次编译成功,生成gps_test文件。
在执行程序前,建议先用microcom指令看一下板子的串口是否确实收到了GPS串口的数据。如果GPS接收正常的话,会显示类似如下的信息,
OK,我们把gps_test放到板子里跑一把,先看看情况如何?
如果你发现上述中文对应的是一堆乱码(这个问题折腾了我大半天,我起初以为是编译的问题),后来才发现是SecureCRT里配置的问题,改成下面配置就行了,
从上述执行结果来看,程序是跑起来了,也正确读取到了串口数据,但是没有解析成功。这是啥原因呢?这时就得看一下代码了,首先比较容易发现的是,程序里判断的是$GPRMC,但是我这里读取到的是$GNRMC,所以在下面这段代码就直接返回了:
if(NULL==(ptr=strstr(buff,"$GPRMC")))
{
return -1;
}
怎么改呢,你是不是会想直接把$GPRMC改成$GNRMC,可以是可以,但是不够好,万一你遇到的是$GPRMC开头的呢?可以改成如下判断条件,含义是既找不到$GPRMC同时也找不到$GNRMC的情况下才返回-1
if( NULL==(ptr=strstr(buff,"$GPRMC")) && NULL==(ptr=strstr(buff,"$GNRMC")) )
{
return -1;
}
改完了之后下面这句话也得改:
sscanf(ptr,"$GNRMC,%d.00,%c,%f,N,%f,E,%f,,%d,6.91,W,%c*",&(gps_data->time),&(gps_data->pos_state),&(gps_data->latitude),&(gps_data->longitude),&(gps_data->speed),&(gps_data->date),&(gps_data->mode));
我一开始做了如下修改,我的逻辑是如果查询到有$GPRMC就执行if条件下的语句,否则执行else下面的语句,
if(NULL != (ptr=strstr(buff,"$GPRMC")))
sscanf(ptr,"$GPRMC,%d.000,%c,%f,N,%f,E,%f,%f,%d,,,%c*",&(gps_data->time),&(gps_data->pos_state),&(gps_data->latitude),&(gps_data->longitude),&(gps_data->speed),&(gps_data->direction),&(gps_data->date),&(gps_data->mode));
else
sscanf(ptr,"$GNRMC,%d.000,%c,%f,N,%f,E,%f,%f,%d,,,%c*",&(gps_data->time),&(gps_data->pos_state),&(gps_data->latitude),&(gps_data->longitude),&(gps_data->speed),&(gps_data->direction),&(gps_data->date),&(gps_data->mode));
放进去一跑,结果提示Segmentation fault,在做应用开发的过程中,会经常遇到这个问题。这个问题又调试了半天才发现问题,你们能很快发现问题在哪里吗?
问题出在,当你$GNRMC语句时,在if判断时会给ptr指针赋一个null指针,然后在else语句里用到了ptr,自然会出问题,于是我又做了一次改动
if(NULL != (ptr=strstr(buff,"$GPRMC")))
sscanf(ptr,"$GPRMC,%d.000,%c,%f,N,%f,E,%f,%f,%d,,,%c*",&(gps_data->time),&(gps_data->pos_state),&(gps_data->latitude),&(gps_data->longitude),&(gps_data->speed),&(gps_data->direction),&(gps_data->date),&(gps_data->mode));
else if(NULL != (ptr=strstr(buff,"$GNRMC")))
sscanf(ptr,"$GNRMC,%d.000,%c,%f,N,%f,E,%f,%f,%d,,,%c*",&(gps_data->time),&(gps_data->pos_state),&(gps_data->latitude),&(gps_data->longitude),&(gps_data->speed),&(gps_data->direction),&(gps_data->date),&(gps_data->mode));
兴致勃勃放进去运行了一把,结果如下:
没有解析出正确的字段,我们很容易定位是sscanf那条语句出了问题,由于我之前没用过这个函数,为此我上网查了好久这个函数的用法,后来把思路转向对比作者的GPRMC语句和我收到的GNRMC语句,除了开头不同,其他地方的差异,
$GPRMC,131913.000,A,3029.64972,N,11423.62352,E,0.00,0.00,200617,,,A*67
$GNRMC,085959.00,A,4000.73433,N,11628.03429,E,0.461,,280220,6.91,W,D*29
发现以下有以下几处不同,
- 字段1:UTC时间这里,他的小数点后面是3个0,但是我的是2个0
- 字段8:方位角这里,他的值是0.00,我的是空白
- 字段11和12,磁偏角及磁偏角方向,他的是空白,我的有值
正是这3处不同,导致的上述运行错误问题。原作者完全是按照它的格式来先写的代码,只要有任何一处不同,就极大概率会解析错误。为了先匹配我的这个格式,将第二个sscanf做了如下修改:
sscanf(ptr,"$GNRMC,%d.00,%c,%f,N,%f,E,%f,,%d,6.91,W,%c*",&(gps_data->time),&(gps_data->pos_state),&(gps_data->latitude),&(gps_data->longitude),&(gps_data->speed),&(gps_data->date),&(gps_data->mode));
之后运行,各个字段倒是解析出来了
我们来看一看它解析的对不对
$GNRMC,085959.00,A,4000.73433,N,11628.03429,E,0.461,,280220,6.91,W,D*29
字段9:UTC日期,DDMMYY格式,280220对应2020年02月28日,没错
字段1:UTC时间,hhmmss.sss格式,转换后也是对的,代码里加了一个8,表示的是北京时间。
纬度:dddmm.mmmm度分格式,4000.73433对应,40度,0分,0.73433分,转换秒,0.73433*60=44.0598秒
经度:dddmm.mmmm度分格式,11628.03429对应,116度,28分,0.03429分,转换秒,0.03429*60=2.0574秒
速度:单位是节,1节=1.852 km/h=0.5144 m/s
综上速度单位错了,其他计算是正确的。
但是这个程序真的能这么写吗?
答案是否!
因为你只考虑了这一种情况,上述方向角字段空是因为GPS模块处于静止状态,如果运动起来这个字段就会有值,上述代码就会出问题。
另外比如在GPS信号不好的时候,整个字段的内容也会发生变化,中间会有很多空的情况,比如刚启动的时候,格式就会类似如下,
$GNRMC,064016.00,V,,,,,,,280220,6.91,W,N*2B
所以使用sscanf去处理这种协议类型的数据(数据字段之间以逗号分隔,但是字段可能会为空),显然是不合适的。针对这种类型格式,该如何用去解析呢?希望读者们也想一想,查一查,最好是动手去实现一下,没有板子没关系,直接在电脑上就可以运行。时间不早了,今天就到这里了,敬请期待下一篇文章。
欢迎关注: