总述
GPS我们都知道,一种用来全球定位的系统,后来俄罗斯推出了格洛纳斯定位系统,中国推出了北斗定位,欧盟有伽利略,印度与日本也有有发展。所以后来把覆盖全球的自主地利空间定位的卫星系统成为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倍,实际要除以100000u8 nshemi; //北纬/南纬,N:北纬;S:南纬u32 longitude; //经度 分扩大100000倍,实际要除以100000u8 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.0u16 hdop; //水平精度因子 0~500,对应实际值0~50.0u16 vdop; //垂直精度因子 0~500,对应实际值0~50.0 u16 course; //航向int altitude; //海拔高度,放大了10倍,实际除以10.单位:0.1mu32 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"); //GNSSif(p1 == NULL){p1=(u8*)strstr((const char *)buf,"GPRMC");//"$GPRMC",经常有&和GPRMC分开的情况,故只判断GPRMC.}posx=NMEA_Comma_Pos(p1, 1); //得到UTC时间 hhmmss.ssif(posx!=0XFF){temp=NMEA_Str2num(p1+posx,&dx)/NMEA_Pow(10,dx); //得到UTC时间,去掉msgpsx->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.mmmmif(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.mmmmif(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日期 ddmmyyif(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数据的文章,里面代码都是实践过的。如果大家有什么更好的思路,欢迎分享交流哈。