美文网首页
从NMEA0183到GNSS定位数据获取(二)软件篇

从NMEA0183到GNSS定位数据获取(二)软件篇

作者: 良知犹存 | 来源:发表于2020-08-06 09:16 被阅读0次

    作者:良知犹存

    转载授权以及围观:欢迎添加微信公众号:Conscience_Remains

    总述

    GPS我们都知道,一种用来全球定位的系统,后来俄罗斯推出了格洛纳斯定位系统,中国推出了北斗定位,欧盟有伽利略,印度与日本也有有发展。所以后来把覆盖全球的自主地利空间定位的卫星系统成为GNSS。

    现在卫星定位那么热,那么作为一个嵌入式人怎么获取这些数据为我们所用呢?下面就听作者一一道来。

    上一篇文章的传送门从NMEA0183到GNSS定位数据获取(一)原理篇

    需要资料和代码的朋友可以关注公众号回复GNSS解析获得自动回复的链接。

    三、程序介绍

    上一篇文章介绍了NMEA-0183的协议内容,当我们知道数据格式,那对于底层的开发人员来说就是如何把我们需要的数据解析出来。

    话不多说上实例来看:

    还是这张图,上面可以看到SOC与模块只是串口相连

    我们第一步就是先配置通讯IO,STM32和Linux大家自行选择

    外设配置好了,接下来就开始对”模块“发过来的数据动手了。

    $GNGGA,032220.291,,,,,0,0,,,M,,M,,*5D

    $GNRMC,032220.291,V,,,,,0.00,0.00,140716,,,N*5D

    $GNVTG,0.00,T,,M,0.00,N,0.00,K,N*2C

    $GPGSA,A,1,,,,,,,,,,,,,,,*1E

    $BDGSA,A,1,,,,,,,,,,,,,,,*0F

    $GPGSV,2,1,07,23,,,31,08,,,49,30,,,33,16,,,45*7E

    $GPGSV,2,2,07,07,,,44,27,,,49,26,,,43*72

    $BDGSV,1,1,03,10,,,47,04,,,40,07,,,48*62

    $GNGLL,,,,,032220.291,V,N*6F

    首先先定义一个结构体,用来对解析好的数据进行存放。

    //GPS NMEA-0183协议重要参数结构体定义

    //卫星信息

    __packed typedef struct 

    {                       

      u8  num;    //卫星编号

      u8  eledeg;  //卫星仰角

      u16 azideg;  //卫星方位角

      u8  sn;    //信噪比     

    }nmea_slmsg;

    //UTC时间信息

    __packed typedef struct 

    {                       

      u16 year;  //年份

      u8 month;  //月份

      u8 date;  //日期

      u8 hour;  //小时

      u8 min;  //分钟

      u8 sec;  //秒钟

    }nmea_utc_time;       

    //NMEA 0183 协议解析后数据存放结构体

    __packed typedef struct 

    {                       

      u8 svnum;          //可见卫星数

      nmea_slmsg slmsg[12];    //最多12颗卫星

      nmea_utc_time utc;      //UTC时间

      u32 latitude;        //纬度 分扩大100000倍,实际要除以100000

      u8 nshemi;          //北纬/南纬,N:北纬;S:南纬         

      u32 longitude;          //经度 分扩大100000倍,实际要除以100000

      u8 ewhemi;          //东经/西经,E:东经;W:西经

      u8 gpssta;          //GPS状态:0,未定位;1,非差分定位;2,差分定位;6,正在估算.         

      u8 posslnum;        //用于定位的卫星数,0~12.

      u8 possl[12];        //用于定位的卫星编号

      u8 fixmode;          //定位类型:1,没有定位;2,2D定位;3,3D定位

      u16 pdop;          //位置精度因子 0~500,对应实际值0~50.0

      u16 hdop;          //水平精度因子 0~500,对应实际值0~50.0

      u16 vdop;          //垂直精度因子 0~500,对应实际值0~50.0

      u16 course;      //航向

      int altitude;        //海拔高度,放大了10倍,实际除以10.单位:0.1m 

      u32 speed;          //地面速率,放大了1000倍,实际除以10.单位:0.001公里/小时 

    }nmea_msg;

    其次因为协议是以字符串的形式发过来的,我们要把解析的信息进行符号的定义以及字符的转化,准备了以下两个函数:

    //从buf里面得到第cx个逗号所在的位置

    //返回值:0~0XFE,代表逗号所在位置的偏移.

    //      0XFF,代表不存在第cx个逗号               

    u8 NMEA_Comma_Pos(u8 *buf,u8 cx)

    {         

      u8 *p=buf;

      while(cx)

      {   

        if(*buf=='*'||*buf<' '||*buf>'z')return 0XFF;//遇到'*'或者非法字符,则不存在第cx个逗号

        if(*buf==',')cx--;

        buf++;

      }

      return buf-p; 

    }

    //str转换为数字,以','或者'*'结束

    //buf:数字存储区

    //dx:小数点位数,返回给调用函数

    //返回值:转换后的数值

    /*遇到冒号以及竖杠、注释的斜杠的时候进行返回*/

    int NMEA_Str2num(u8 *buf,u8*dx)

    {

      u8 *p=buf;

      u32 ires=0,fres=0;

      u8 ilen=0,flen=0,i;

      u8 mask=0;

      int res;

      while(1) //得到整数和小数的长度

      {

        if(*p=='-'){mask|=0X02;p++;}//是负数

        if(*p==','||(*p=='*')||(*p=='|')||(*p==':')\

        ||(*p=='!')  ||(*p=='/'))break;//遇到结束了

        if(*p=='.'){mask|=0X01;p++;}//遇到小数点了

        else if(*p == 0)//截至符 0

        {

          break;

        }

        else if(*p>'9'||(*p<'0'))  //有非法字符

        { 

          ilen=0;

          flen=0;

          break;

        } 

        if(mask&0X01)flen++;

        else ilen++;

        p++;

      }

      if(mask&0X02)buf++;  //去掉负号

      for(i=0;i<ilen;i++)  //得到整数部分数据

      { 

        ires+=NMEA_Pow(10,ilen-1-i)*(buf[i]-'0');//

      }

      if(flen>5)flen=5;  //最多取5位小数

      *dx=flen;      //小数点位数

      for(i=0;i<flen;i++)  //得到小数部分数据

      { 

        fres+=NMEA_Pow(10,flen-1-i)*(buf[ilen+1+i]-'0');

      }

      res=ires*NMEA_Pow(10,flen)+fres;

      if(mask&0X02)res=-res;     

      return res;

    }   

    //m^n函数

    //返回值:m^n次方.

    u32 NMEA_Pow(u8 m,u8 n)

    {

      u32 result=1; 

      while(n--)result*=m;   

      return result;

    }

    因为项目的需求没有对协议全部的解析,只是针对性的进行了解析。其余大家想要解析的数据也是类似:

    下面是以GPGGA 解析为例:其他标志头的数据大家以此类推。

    从上面图片可以看到本条信息带有14条信息,但是我只解析了第六个、第七个和第九个字节的数据。虽然这条信息中也有UTC时间,但是一般都是建议在*RMC中获得。

    //分析GPGGA信息

    //gpsx:nmea信息结构体

    //buf:接收到的GPS数据缓冲区首地址

    void NMEA_GPGGA_Analysis(nmea_msg *gpsx,u8 *buf)

    {

      u8 *p1,dx;     

      u8 posx;   

      p1 = (u8*)strstr((const char *)buf,"$GNGGA");//GN 标志开头

      if(p1 == NULL)

      {

        p1=(u8*)strstr((const char *)buf,"$GPGGA");//或者GP开头的标志 你也可以用BD开头的标志

      }

      posx=NMEA_Comma_Pos(p1,6);                //得到GPS状态

      if(posx!=0XFF)

        gpsx->gpssta=NMEA_Str2num(p1+posx,&dx); 

      posx=NMEA_Comma_Pos(p1,7);                //得到用于定位的卫星数

      if(posx!=0XFF)

        gpsx->posslnum=NMEA_Str2num(p1+posx,&dx);

      posx=NMEA_Comma_Pos(p1,9);                //得到海拔高度

      if(posx!=0XFF)

        gpsx->altitude=NMEA_Str2num(p1+posx,&dx); 

    }

    //分析GPGSA信息

    //gpsx:nmea信息结构体

    //buf:接收到的GPS数据缓冲区首地址

    void NMEA_GPGSA_Analysis(nmea_msg *gpsx,u8 *buf)

    {

      u8 *p1,dx;     

      u8 posx;

      u8 i; 

      p1=(u8*)strstr((const char *)buf,"$GPGSA");

      if(p1 == NULL)

        p1 = (u8*)strstr((const char *)buf,"$GNGSA");

      posx=NMEA_Comma_Pos(p1,2);                //得到定位类型

      if(posx!=0XFF)gpsx->fixmode=NMEA_Str2num(p1+posx,&dx); 

      for(i=0;i<12;i++)                    //得到定位卫星编号

      {

        posx=NMEA_Comma_Pos(p1,3+i);         

        if(posx!=0XFF)gpsx->possl[i]=NMEA_Str2num(p1+posx,&dx);

        else break;

      }         

      posx=NMEA_Comma_Pos(p1,15);                //得到PDOP位置精度因子

      if(posx!=0XFF)gpsx->pdop=NMEA_Str2num(p1+posx,&dx); 

      posx=NMEA_Comma_Pos(p1,16);                //得到HDOP位置精度因子

      if(posx!=0XFF)gpsx->hdop=NMEA_Str2num(p1+posx,&dx); 

      posx=NMEA_Comma_Pos(p1,17);                //得到VDOP位置精度因子

      if(posx!=0XFF)gpsx->vdop=NMEA_Str2num(p1+posx,&dx); 

    }

    //分析GPRMC信息

    //gpsx:nmea信息结构体

    //buf:接收到的GPS数据缓冲区首地址

    void NMEA_GPRMC_Analysis(nmea_msg *gpsx,u8 *buf)

    {

      u8 *p1,dx;     

      u8 posx;   

      u32 temp;   

      float rs; 

      p1 = (u8*)strstr((const char *)buf,"GNRMC"); //GNSS

      if(p1 == NULL)

      {

        p1=(u8*)strstr((const char *)buf,"GPRMC");//"$GPRMC",经常有&和GPRMC分开的情况,故只判断GPRMC.

      }

      posx=NMEA_Comma_Pos(p1, 1);                //得到UTC时间  hhmmss.ss

      if(posx!=0XFF)

      {

        temp=NMEA_Str2num(p1+posx,&dx)/NMEA_Pow(10,dx);    //得到UTC时间,去掉ms

        gpsx->utc.hour=temp/10000;

        gpsx->utc.min=(temp/100)%100;

        gpsx->utc.sec=temp%100;     

      }

      posx=NMEA_Comma_Pos(p1,2);/*判断RMC数据状态,A=数据有效 V=数据无效*/ 

      if(posx!=0XFF)

      {

        u8* p2=(u8*)strstr((const char *)(p1+posx), "A");

        if(p2 == NULL)

        {

          posx = 0;  //数据无效 TODO

        }

      }

      posx=NMEA_Comma_Pos(p1,3);                //得到纬度 ddmm.mmmm

      if(posx!=0XFF)

      {

        temp=NMEA_Str2num(p1+posx,&dx);       

        gpsx->latitude=temp/NMEA_Pow(10,dx+2);  //得到°

        rs=temp%NMEA_Pow(10,dx+2);        //得到'   

        gpsx->latitude=gpsx->latitude*NMEA_Pow(10,5)+(rs*NMEA_Pow(10,5-dx))/60;//转换为°

      }

      posx=NMEA_Comma_Pos(p1,4);                //南纬还是北纬

      if(posx!=0XFF)

        gpsx->nshemi=*(p1+posx);         

      posx=NMEA_Comma_Pos(p1,5);                //得到经度 dddmm.mmmm

      if(posx!=0XFF)

      {                         

        temp=NMEA_Str2num(p1+posx,&dx);       

        gpsx->longitude=temp/NMEA_Pow(10,dx+2);  //得到°

        rs=temp%NMEA_Pow(10,dx+2);        //得到'   

        gpsx->longitude=gpsx->longitude*NMEA_Pow(10,5)+(rs*NMEA_Pow(10,5-dx))/60;//转换为°

      }

      posx=NMEA_Comma_Pos(p1,6);                //东经还是西经

      if(posx!=0XFF)

        gpsx->ewhemi=*(p1+posx); 

      posx=NMEA_Comma_Pos(p1,8);                //得到方位 度

      if(posx!=0XFF)

      {                         

        temp=NMEA_Str2num(p1+posx,&dx);       

        gpsx->course = temp*10;               

      }

      posx=NMEA_Comma_Pos(p1, 9);                //得到UTC日期 ddmmyy

      if(posx!=0XFF)

      {

        temp=NMEA_Str2num(p1+posx, &dx);       

        gpsx->utc.date  = temp/10000;

        gpsx->utc.month = (temp/100)%100;

        gpsx->utc.year  = 2000+temp%100;     

      }

    }

    这就是我分享的第二篇NMEA-0183到GNSS数据的文章,里面代码都是实践过的。如果大家有什么更好的思路,欢迎分享交流哈。

    相关文章

      网友评论

          本文标题:从NMEA0183到GNSS定位数据获取(二)软件篇

          本文链接:https://www.haomeiwen.com/subject/sdturktx.html