NMEA1803 协议3.0版本及以上说明
一、 NMEA1803协议格式:
二、标准信息发送器标识说明:
1.GNSS7种卫星系统说明
GNSS接收系统主要有以下7个发送系统:
1.Galileo(欧洲伽利略系统)
2.BeiDou(中国北斗系统)
3.GPS(美国GPS系统)
4.QZSS(准天顶系统)
5.NAVIC(IRNSS印度区域导航卫星系统)
6.GLONASS(俄罗斯格洛纳斯系统)
7.Combination of Multiple Satellite Systems(联合卫星系统(以上全部或部分系统联合定位时输出的数据))。
其中:
全球定位系统:
BeiDou、GPS、GLONASS、Galileo
区域定位系统:
QZSS、IRNSS
增强系统:
WAAS(美国)、MSAS(日本)、EGNOS(联合国)、GAGAN(印度)、 NIGCOMSAT-1(尼日亚非洲区域卫星)。
2.NMEA1803协议3.01与4.1版本标识符区别
三、常用标准信息语句说明:
1.RMC(推荐最小定位信息)
示例报文:
NMEA:3.0-4.0x示例数据
$GPRMC,121252.000,A,3958.3032,N,11629.6046,E,15.15,359.95,070306,,,A*54
示例报文:
NMEA:4.1x示例数据
$GNRMC,015107.00,A,3412.76124010,N,10849.67444051,E,0.003,114.8,010323,3.4,W,A,V*4C
版本区别:4.1x增加<14>导航状态标识字段
如上图两个语句使用“x”进行补齐操作
在末尾高亮处(14或13)个字段的位置当“A”对齐后3.0版本直接接替*输出校验位。
而4.1版本“A”对齐后接“,”输出“V”第14字段导航状态标识后接校验位
2.GGA(接收机时间、 位置及定位相关的数据)
示例报文:
NMEA:3.0-4.0x示例数据
$GPGGA,121252.000,3937.3032,N,11611.6046,E,1,05,2.0,45.9,M,-5.7,M,,0000*77
示例报文:
NMEA:4.1x示例数据
$GNGGA,015111.00,3412.76134514,N,10849.67440085,E,1,28,0.6,399.1382,M,-33.6324,M,,*55
版本区别:无区别字段长度相同
如上图两个语句使用“x”进行补齐操作
字段个数一致二版本下无区别
3.GSA(定位的卫星编号与 DOP 信息)
示例报文:
NMEA:3.0-4.0x示例数据
$GNGSA,M,3,87,66,77,67,76,,,,,,,,0.7,0.4,0.6*2A
$GNGSA,M,3,02,03,07,25,30,36,08,,,,,,0.7,0.4,0.6*2A
$GNGSA,M,3,01,02,06,07,10,16,24,26,03,33,14,35,0.7,0.4,0.6*23
$GNGSA,M,3,39,40,42,45,59,60,44,04,05,21,09,,0.7,0.4,0.6*2D
示例报文:
NMEA:4.1x示例数据
$GNGSA,A,3,03,04,08,16,27,28,29,31,195,199,,,1.13,0.61,0.94,1*0A
$GNGSA,A,3,,,,,,,,,,,,,1.13,.61,0.94,3*0A
$GNGSA,A,3,03,04,07,10,21,22,26,29,39,40,45,59,1.13,0.61,0.94,4*03
版本区别:4.1x 增加systemid
如上图两个语句使用“x”或者“,x”进行补齐操作后可以对比法线缺少补全“,x”内容,即缺少systemid内容。
0.6x,x2D(3.0)
0.94,403(4.1)
4.GSV(可见卫星的卫星编号及其仰角、 方位角、 载噪比等信息)
示例报文:
NMEA:3.0-4.0x示例数据
$GPGSV,3,1,10,18,84,067,23,09,67,067,27,22,49,312,28,15,47,231,30*70
$GPGSV,3,2,10,21,32,199,23,14,25,272,24,05,21,140,32,26,14,070,20*7E
$GPGSV,3,3,10,29,07,074,,30,07,163,28*7D
示例报文:
NMEA:4.1x示例数据
$GPGSV,2,1,07,04,33,316,37,03,31,260,44,16,63,232,45,26,72,026,45,1*65
$GPGSV,2,2,07,28,34,075,45,29,12,042,35,31,41,067,40,1*57
版本区别:4.1x增加<14>导航状态标识字段
如上图在GSV最后进行补齐,可以看到3.0版本缺少“,x”内容,即缺少GNSS信号ID
30*,x70
45,1*65
三、 解析程序参考思路:
这里只提供两种参考方法及部分核心代码段。
分析特点:
NMEA报文格式特点即从“ ”出发每经过一个“,”或末尾“ ∗ ”该数据内容结束, ∗ 之后为校验位。所有标识符 ( “ ”出发每经过一个“,”或末尾“*”该数据内容结束,*之后为校验位。 所有标识符(“ ”出发每经过一个“,”或末尾“∗”该数据内容结束,∗之后为校验位。所有标识符(“”、“,”、“*”)综和等于字段个数。
方法1:
采用两个指针一前一后进行遍历标识符截取字段。
适用于简单适用,只提取使用或常用的字段,写法简单清晰,可增加标识符个数检索接口,
从而再for中确定每个字段对应len长度。
缺点:编写起来比较冗余,在GSV,GSA等多条解析字段下将变得鸡肋。
void gnssscr_Ctrl(char *gnssscr)
{
char *strfirst=NULL;
char *strnext=NUNLL;
strfirst=gnssscr;
if((strfirst=strstr(strfirst,"$GNRMC"))!=NULL)
{
for(uint8_t i=0;i<14;i++)//RMC 14个段(掐头去尾去中间数据段)
{
if (i == 0)
{
if ((strfirst = strstr(gnssscr, ",")) == NULL)
{
LOG("err"); //解析错误
}
}
else//如果没有错误
{
strfirst++;
strnext = strstr(strfirst, ",");
LOG("strnext=%s",strnext);
if (strnext != NULL)
{
LOG("strfirst = %s",strfirst);
len=strnext-strfirst;
switch(i)
{
case 1: //获取UTC时间
memset(gnss_data_src.UTCTime,0,sizeof(gnss_data_src.UTCTime));
if(len==0)
{
memset(gnss_data_src.UTCTime,0x30,10);
}
else
{
memset(gnss_data_src.UTCTime,0x30,sizeof(gnss_data_src.UTCTime)-1);
memcpy(gnss_data_src.UTCTime+sizeof(gnss_data_src.UTCTime)-len-1,strfirst,len);
LOG("UTCTimelen:%d--%d",sizeof(gnss_data_src.UTCTime),len);
}
break;
....//省略
}
strfirst = strnext;//每执行一次subString的值将被替换为subStringNext
}
else
{
LOG("解析错误");
}
}
}
}
if((strfirst=strstr(strfirst,"$GNGGA"))!=NULL)
{
for(uint8_t i=0;i<11;i++)//GGA(实际17个段) 11个段(舍弃不需要的段)
{
if (i == 0)
{
if ((strfirst = strstr(gnssscr, ",")) == NULL)
{
LOG("err"); //解析错误
}
}
else//如果没有错误
{
strfirst++;
strnext = strstr(strfirst, ",");
LOG("strnext=%s",strnext);
if (strnext != NULL)
{
LOG("strfirst = %s",strfirst);
len=strnext-strfirst;
switch(i)
{
case 6: //获取定位状态
memset(gnss_data_src.Positioning_state,0,sizeof(gnss_data_src.Positioning_state));
if(len==0)
{
memset(gnss_data_src.Positioning_state,0x30,1);
}
else
{
memset(gnss_data_src.Positioning_state,0x30,sizeof(gnss_data_src.Positioning_state)-1);
memcpy(gnss_data_src.Positioning_state+sizeof(gnss_data_src.Positioning_state)-len-1,strfirst,len);
}
LOG("gnss_data_src.Positioning_state:%s",gnss_data_src.Positioning_state);
break;
...//省略
}
strfirst = strnext;//每执行一次subString的值将被替换为subStringNext
}
else
{
LOG("解析错误");
}
}
}
}
}
方法2:
遍历标识符个数,将所有标识符地址保存在一个指针数组中。两个相邻的指针相减即为该字段数据内容的长度。
方法说明:
依照核心接口,可自由开发后续解析,适应GSV或者GSA等多条内容的解析。并可兼容因3.0或4.1版本字段总个数不一的情况。
/**************************************
* 函数名称:get_Symbol_Sign
* 函数入口:
* gnsscr:定位源串
* delimt:索引
* 函数出口:
* p:索引值地址
* 正确返回已经存储的地址个数
* 错误返回0;
* 函数说明:遍历存储所有","的地址
* ***************************************/
uint8_t get_Symbol_Sign(const char *gnssscr, char *p[],const char *delimt)
{
uint8_t count= 0;
char *scr = NULL;
if ((scr = strstr(gnssscr, delimt)) != NULL)
{
p[count++] = scr; // 存首地址"$"
while (1)
{
if (*scr < ' ' || *scr > 'z') // 存在非法字符将结束
{
LOG("Illegal character exists!!!\r\n");
return count;
}
else
{
if (*scr == ',')
{
p[count++] = scr; // 存“,”地址
}
else if(*scr == '*')
{
LOG("count:%d\r\n", count);
p[count++] = scr; // 存“*”地址
return count;
}
}
scr++;
}
}
else
{
return 0;
}
return count;
}
/**************************************
* 函数名称:gnss_Addr_Ctrl
* 函数入口:
* gnsscr:定位源串
* p: “,”及“*”位置存放缓冲区
* len :报文段的个数
* buf:数据段缓冲区
* 函数出口:
* 函数说明:检索“,”“*”前每段数据存放buf
* 二维数组元素大小给定20,因为最长数据段长度如假设经度小数点前8位,小数点后8位加点1位 加\0 才18字节
* ***************************************/
void gnss_Addr_Ctrl(const char *gnssscr, char *p[], uint8_t segcont, char (*buf)[20])
{
uint8_t i=0;
uint16_t scrlen = 0;
memcpy(*buf, gnssscr, p[1] - p[0]);//先拿每个语句的头如$GNRMC
scrlen += strlen(*buf) + 1;
for(i = 1; i < segcont - 1; i++)
{
memcpy(*(buf + i), gnssscr + scrlen, p[i + 1] - p[i]-1 );//从第一个拿到除校验位最后一个
//LOG("buf:%s\r\n",*(buf + i));
scrlen += strlen(*(buf + i)) + 1;
}
memcpy(*(buf + i), gnssscr + scrlen, 2 );//最后拿校验位
}
/**************************************
* 函数名称:get_Head_Kind
* 函数入口:
* gnssscr:定位数据原串
*
* head:头存储二维数组
*
* delim:索引标识符
* 函数出口:
* head:检索同一数据(如xxGGA)头种类存储二维组
* 返回检索相关头的个数
* 函数说明: 获取语句头的种类,如定位板卡吐出数据中有GNGGA与GPGGA
*或存储这两种系统头,用于其他函数检索,可将解析后的数据解析到对应系统结构体中
* ***************************************/
uint8_t get_Head_Kind(const char *gnssscr,char (*head)[7],const char *delimt)
{
uint8_t num=0;
uint8_t cont=0;
uint8_t diff_flag=0;
char *headbuf=NULL;
if((headbuf=strstr(gnssscr,delimt))!=NULL)
{
memcpy(head[cont],headbuf-3,6);// 第一出现只存
while(1)
{
if((headbuf=strstr(headbuf+1,delimt))!=NULL)
{
for(num=0;num<cont+1;num++)
{
if(!memcmp(headbuf-3,head[num],6))
{
diff_flag=1;
break;
}
}
if(!diff_flag)
{
memcpy(head[++cont],headbuf-3,6);// 第一出现只存
}
diff_flag=0;
}
else
{
return cont+1;
}
}
}
else
{
return cont;
}
}
/**************************************
* 函数名称:get_Nmea1803_Ver
* 函数入口:
* head:GNSS头二维数组
* len:头个数
* 函数出口:
* 返回1:NEMA4.10
* 返回0:NEMA 3.0-4.0
* 函数说明:获取当前NMEA的版本号
* ***************************************/
uint8_t get_Nmea1803_Ver(char (*head)[7],uint8_t len,const char *gnssscr)
{
char *p[25] = {0};//存储每个“,”地址
char *strfirst=NULL;
char rmchead[7]={0};
for(uint8_t i=0;i<len;i++)//不完全,需要增加一些字段段个数测验
{
if((!memcmp(head[i]+1,"GB",2) ) || (!memcmp(head[i]+1,"GQ",2)) || (!memcmp(head[i]+1,"GI",2)))//4.1
{
LOG("NMEA_1803 4.1UP!!!\r\n");
return 1;
}
}
if((strfirst=strstr(gnssscr,"RMC"))!=NULL)//极端下可以再次增加其他相关字段校验判断当前默认判断RMC
{
memcpy(rmchead,strfirst-3,6);
if (get_Symbol_Sign(gnssscr, p, rmchead)==15)//其他判断方法
{
LOG("NMEA_1803 4.1UP!!!\r\n");
return 1;//4.1
}
}
LOG("NMEA_1803 3.0UP!!!\r\n");
return 0;//3.0
}
/**************************************
* 函数名称:head_to_select_num
* 函数入口:
* head:定位数据原串
* verflag:版本标识
* 函数出口:
* 根据返回的版本标识确定调用结构体
* 函数说明:解析GNSS数据到结构体
* ***************************************/
uint8_t head_to_select_num(char *head,uint8_t verflag)
{
QL_UART_DEMO_LOG("head%s\r\n",head);
if ((!memcmp(head,"GP",2)))// 公共资源 GPs
{
return 0;
}
else if((!memcmp(head,"GN",2)))//Combination of Multiple Satellite Systems
{
return 1;
}
else if((!memcmp(head,"GA",2)))//Galileo
{
return 4;
}
else if((!memcmp(head,"GL",2)))//GLONASS
{
return 3;
}
if(verflag==1)//4.11专精
{
if((!memcmp(head,"GB",2)))//BeiDou
{
return 2;
}
else if((!memcmp(head,"GQ",2)))//膏药旗Qzss
{
return 6;
}
else if((!memcmp(head,"GI",2)))//NAVIC(IRNSS)
{
return 5;
}
}
else//3.0
{
if((!memcpy(head,"BD",2)))//BeiDou
{
return 2;
}
else if((!memcpy(head,"IR",2)))//NAVIC(IRNSS)
{
return 5;
}
}
return 7;
}
下面展示 解析RMC时代码段
。
/**************************************
* 函数名称:gnss_Class_RMC
* 函数入口:
* gnssscr:定位数据原串
* 函数出口:
* 函数说明:RMC数据解析
* ***************************************/
void gnss_Class_RMC(const char *gnssscr)
{
char headbuf[7][7]={0};//不管什么协议最多上限只会存在七种头
uint8_t i=0;
uint8_t len=0;
uint8_t verflag=0;
uint8_t select_num=0;
if((len=get_Head_Kind(gnssscr,headbuf,"RMC")))//判断种类
{
LOG("len:%d\r\n",len);
verflag=get_Nmea1803_Ver(headbuf, len,gnssscr);//判断版本号
LOG("verflag:%d\r\n",verflag);
for(i=0;i<len;i++)
{
select_num=head_to_select_num(&headbuf[i][1], verflag);//头转系统号
LOG("select_num:%d\r\n",select_num);
switch(select_num)//走那个结构体 {
case GNSS_Gpgs:
gnss_RMC_Crtl(gnssscr,&headbuf[i][0],&gnss_rmc_gp);//GPS系统解析
break;
case GNSS_Comsys:
gnss_RMC_Crtl(gnssscr,&headbuf[i][0],&gnss_rmc_gn);//GN联合系统解析
break;
case GNSS_Beidou:
gnss_RMC_Crtl(gnssscr,&headbuf[i][0],&gnss_rmc_bd);//北斗系统解析
break;
case GNSS_Glonass:break;//暂未开发
case GNSS_Galileo:break;
case GNSS_Navic:break;
case GNSS_Qzss:break;
default:break;
}
}
}
else
{
LOG("RMC not have\r\n");
}
}
/**************************************
* 函数名称:gnss_RMC_Crtl
* 函数入口:
* gnssscr:定位数据原串
*
* headbuf:GNSS头标识符
*
* gnssdatastruct:参数结构体
* 函数出口:
* head:检索头种类存储二维组
* 返回检索相关头的个数
* 函数说明:解析GNSS数据到结构体
* ***************************************/
uint8_t gnss_RMC_Crtl(const char *gnssscr,const char *headbuf,GnssRMCData * gnss_rmc_struct)
{
uint8_t len=0;
char *p[25] = {0};//存储每个“,”地址
char *firststr = NULL;//中间指针
if ((len = get_Symbol_Sign(gnssscr, p, headbuf)))//遍历标识附地址存储
{
firststr = strstr(gnssscr, headbuf);
get_Rmc_Data(firststr, len, p, gnss_rmc_struct);//按照存储标识地址获取数据到结构体变量
return 0;
}
else
{
QL_UART_DEMO_LOG("[%s]not have\r\n",headbuf);
return 1;
}
}
/**************************************
* 函数名称:get_Rmc_Data
* 函数入口:
* gnsscr:定位源串
* len “,”个数
* p: “,”位置存放缓冲区
* gnssstruct:存数据放缓冲区
* 函数出口:
* 函数说明:检索gnssscr中“,”位置
* ***************************************/
void get_Rmc_Data(char *gnssscr, uint8_t len, char *p[], GnssRMCData *gnsss_rmc_struct)
{
char buf[15][20] = {0};
uint8_t i = 0;
gnss_Addr_Ctrl(gnssscr, p, len, buf);//根据标识符地址解析各段数据到BUF中
#if DATALOGSWITCH//可屏蔽LOG
for (i = 0; i < len; i++)//len即是标识符个数
{
LOG("buf:%s\r\n", buf[i]);
}
#endif
for(i=0;i<len;i++)
{
switch(i)
{
case 0:
memcpy(gnsss_rmc_struct->head, buf[i], strlen(buf[i])+1);//语句头
//LOG("RMChead:%s\r\n", gnsss_rmc_struct->head);
break;//
case 1:
memcpy(gnsss_rmc_struct->UTCtime, buf[i], strlen(buf[i])+1);//UTCTIME
//LOG("UTCTime:%s\r\n", gnsss_rmc_struct->UTCtime);
break;
case 2:
memcpy(gnsss_rmc_struct->status, buf[i], strlen(buf[i])+1);//定位状态
//LOG("UTCTime:%s\r\n", gnsss_rmc_struct->status);
break;
case 3:
memcpy(gnsss_rmc_struct->lat, buf[i], strlen(buf[i])+1);//纬度
//LOG("Latitude:%s\r\n", gnsss_rmc_struct->lat);
break;
case 4:
memcpy(gnsss_rmc_struct->uLat, buf[i], strlen(buf[i])+1);//纬度标识
//LOG("N_S:%s\r\n", gnsss_rmc_struct->uLat);
break;
case 5:
memcpy(gnsss_rmc_struct->lon, buf[i], strlen(buf[i])+1);//经度
//LOG("Longitude:%s\r\n", gnsss_rmc_struct->lon);
break;
case 6:
memcpy(gnsss_rmc_struct->uLon, buf[i], strlen(buf[i])+1);//经度标识
//LOG("E_W:%s\r\n", gnsss_rmc_struct->uLon);
break;
case 7:
memcpy(gnsss_rmc_struct->spd, buf[i], strlen(buf[i])+1);//速度
//LOG("Speed:%s\r\n", gnsss_rmc_struct->spd);
break;
case 8:
memcpy(gnsss_rmc_struct->cog, buf[i], strlen(buf[i])+1);//航向角
//LOG("Azimuth:%s\r\n", gnsss_rmc_struct->cog);
break;
case 9:
memcpy(gnsss_rmc_struct->date, buf[i], strlen(buf[i])+1);//日期
//LOG("UTCDate:%s\r\n", gnsss_rmc_struct->date);
break;
case 10:
memcpy(gnsss_rmc_struct->mv, buf[i], strlen(buf[i])+1);//磁偏角
//LOG("mv:%s\r\n", gnsss_rmc_struct->mv);
break;
case 11:
memcpy(gnsss_rmc_struct->mvE, buf[i], strlen(buf[i])+1);磁偏角方向
//LOG("mvE:%s\r\n", gnsss_rmc_struct->mvE);
break;
case 12:
memcpy(gnsss_rmc_struct->mode, buf[i], strlen(buf[i])+1);//定位模式
//LOG("rmc_mode:%s\r\n", gnsss_rmc_struct->mode);
break;
case 13:
case 14:
if(len==15)//因为第15字段因版本原因可能存在后着不存在
{
memcpy(gnsss_rmc_struct->xor, buf[i+1], strlen(buf[i])+1);//异或校验
//LOG("XOR:%s\r\n", gnsss_rmc_struct->xor);
}
else if(len==14)
{
break;
}
break;
default:break;
}
}
}
GGA解析与RMC类似
。
GSA: 解析方法类似,但是当需要进行单系统解析操作时需要做特殊处理,例如在NMEA4.0版本下由于没有systemid字段,因此在输出$GNGSA联合系统也无法进行区分定位系统,但是在NMEA4.1以上系统将需要进行判断systemid从而将联合系统解析到单系统。(当处理NMEA4.0以下版本数据时,一般遍历所有的GNGSA相比较,选最长的一个报文进行解析即可,毕竟GSA中最关心的数据是三个定位因子,此处暂不提供代码)。
GSV: 由于具备总条数、当前条数、卫星数且已经知道每条语句最多显示4组卫星参数不足将在下一条。也就是说当总条目为5,那么前4条是4组卫星参数段,最后一条GSV语句需要结合卫星总数来计算剩余卫星总数,例如卫星数为18,那么前4条GSV已经表达了4*4=16组卫星参数了,因此最后一条GSV语句将只显示18-16=2组卫星参数。需要注意GSV在不存在其他卫星组时将会缺省,卫星组内数据段与其他报文内容无数据“,”间将会补空,例如上例GSV语句最后只有2组卫星数,将只显示该两组数据加校验位结束,如下例子
$GPGSV,3,1,10,18,84,067,23,09,67,067,27,22,49,312,28,15,47,231,30*70
$GPGSV,3,2,10,21,32,199,23,14,25,272,24,05,21,140,32,26,14,070,20*7E
$GPGSV,3,3,10,29,07,074,,30,07,163,28*7D
从而在最后一条处理的时候需要做些处理及注意。
总结:以上解析内容完全依照get_Symbol_Sign(保存标识符地址API)与gnss_Addr_Ctrl(解析标识符间内容API),来完成后续操作的,可自由依照次进行开发及升级。以上仅提供交流学习。如有不正确欢迎批评指正。