NMEA协议解析

        NMEA 协议是为了在不同的 GPS (全球定位系统)导航设备中建立统一的 BTCM (海事无线电技术委员会)标准,由美国国家海洋电子协会( NMEA-The National Marine Electronics Associa-tion )制定的一套通讯协议。 GPS 接收机根据 NMEA-0183 协议的标准规范,将位置、速度等信息通过串口传送到 PC 机、 PDA 等设备。NMEA-0183 协议是 GPS 接收机应当遵守的标准协议,也是目前 GPS 接收机上使用最广泛的协议,大多数常见的 GPS 接收机、 GPS 数据处理软件、导航软件都遵守或者至少兼容这个协议。

NMEA协议到现在发布了好多版本,最新版本是NMEA 4.11,市面上的GPS模块大多数支持NMEA 2.30版本,不同的版本语句上存在不同,因此使用本解析库时需要根据模块的NMEA版本进行相应更改。

 NMEA协议包含了好多语句,但是GPS模块经常使用的语句如下:

 如何解析上述所有报文?

  我编写的 NMEA解析库的解析流程:

         1.传入一条报文          
         2.判断开头$和结尾\r\n
         3.验证CRC校验
         4.解析分隔符,根据分隔符','将各个数据域信息存放在node_t类型的结构体中
         5.判断数据类型
         6.解析对应的类型
         7.保存数据

参考代码(篇幅有限仅一部分,需要完整的留言给我):

/** 测试语句 */
char *buff[] = 
{
	"$GNRMC,071556.000,A,3149.29103,N,11706.92916,E,0.00,0.00,250420,,,A,S*0C\r\n",
	"$GPTXT,01,01,02,ANTSTATUS=INIT*25\r\n",
	"$GPXTE,A,A,0.67,L,N*6F\r\n",
	"$GPGGA,123204.00,5106.94086,N,01701.51680,E,1,06,3.86,127.9,M,40.5,M,,*51\r\n",
	"$GPGSA,A,3,02,08,09,05,04,26,,,,,,,4.92,3.86,3.05*00\r\n",
	"$GPGSV,4,1,13,02,28,259,33,04,12,212,27,05,34,305,30,07,79,138,*7F\r\n",
	"$GPGSV,4,2,13,08,51,203,30,09,45,215,28,10,69,197,19,13,47,081,*76\r\n",
	"$GPGSV,4,3,13,16,20,040,17,26,08,271,30,28,01,168,18,33,24,219,27*74\r\n",
	"$GPGSV,4,4,13,39,31,170,27*40\r\n",
	"$GPGLL,5106.94086,N,01701.51680,E,123204.00,A,A*63\r\n",
	"$GPRMC,123205.00,A,5106.94085,N,01701.51689,E,0.016,,280214,,,A*7B\r\n",
	"$GPVTG,,T,,M,0.016,N,0.030,K,A*27\r\n",
	"$GPGST,024603.00,3.2,6.6,4.7,47.3,5.8,5.6,22.0*58\r\n",
	"$GPZDA,160012.71,11,03,2004,-1,00*7D\r\n",	//13
	"$GPRMC,,V,,,,,,,,,,N*53\r\n",
	"$GPVTG,,,,,,,,,N*30\r\n",
	"$GPGGA,,,,,,0,00,99.99,,,,,,*48\r\n",
	"$GPGSA,A,1,,,,,,,,,,,,,99.99,99.99,99.99*30\r\n",
	
	"$GPGLL,,,,,,V,N*64\r\n"

};

/*
 * 说明: 解析xxRMC报文(不考虑粘包的情况)
 *       数据域组成如下:
 *             0.报文类型(TalkerID)(√)
 *             1.UTC时间           (√)
 *             2.定位状态          (√)
 *             3.纬度              (√)
 *             4.纬度方向          (√)
 *             5.经度              (√)
 *             6.经度方向          (√)
 *             7.对地速度          (√)
 *             8.对地真航向        (√)
 *             9.日期              (√)
 *             10.磁偏角           (×)
 *             11.磁偏角方向       (×)
 *             12.导航模式         (√)
 *             13.导航状态         (×)
 * 参数: info_ptr, 返回结果
 *       node_ptr, 分离开的数据域
 *       n, 数据域个数
 * 参数: 成功返回0, 数据错误返回-1, 定位异常返回-2
 */
int Nmea_Parse_xxRMC(struct nmea_info_t* info_ptr, struct node_t* node_ptr, uint8_t n)
{
	char buff[NODE_MAX_LEN];
    struct nmea_info_t info;
    int nsew_d;   /*!< 度 */
	double nsew_m; /*!< 分 */   
	int year=0;
	
	/** 判断数据域个数 */
	if(n < 13 || n > 14)
	{
		return -1;
	}
	/** 解析定位状态 */
	if(node_ptr[2].ptr != NULL)
	{
		if(node_ptr[2].ptr[0] == 'V')
		{
			info_ptr->state = 'V';
			return -2;
		}
		else 
		{
			info.state = 'A';
		}
    }

	if(node_ptr[12].ptr != NULL)
	{
		if(node_ptr[12].ptr[0] == 'N')
		{
			info_ptr->mode = 'N';
			return -2;
		}
		else
		{
			info.mode = node_ptr[12].ptr[0];
		}
    }	
	/** 解析UTC时间 */
	if(node_ptr[1].ptr != NULL)
	{
		/** 复制数据 */
		memcpy(buff, node_ptr[1].ptr, node_ptr[1].len);
		/** 数据域后面添加字符'\0',转换成字符串,方便后续解析 */
		buff[node_ptr[1].len] = '\0';
		if(node_ptr[1].len == 6)
		{
			/** 待转换的格式为:hhmmss */
			if(sscanf(buff, "%2d%2d%2d", &info.utc.hour, &info.utc.min, &info.utc.sec) != 3)
			{
				return -1;
			}
			info.utc.hsec = 0;
	  }
		else if((node_ptr[1].len > 6) && (node_ptr[1].len < 11))
		{
			/** 待转换的格式为:hhmmss.s/hhmmss.ss/hhmmss.sss */
			if(sscanf(buff, "%2d%2d%2d.%d", &info.utc.hour, &info.utc.min, &info.utc.sec, &info.utc.hsec) != 4)
			{
				return -1;
			}			
		}
		else
		{
			return -1;
		}
    }
	/** 判断南北纬 */
	if(node_ptr[4].ptr != NULL)
	{
		if((node_ptr[4].ptr[0] != 'N') && (node_ptr[4].ptr[0] != 'S'))
		{
			return -1;
		}
		info.ns = node_ptr[4].ptr[0];
    }	
	/** 解析纬度 */
	if(node_ptr[3].ptr != NULL)
	{
		/** 复制数据 */		
		memcpy(buff, node_ptr[3].ptr, node_ptr[3].len);
		/** 数据域后面添加字符'\0',转换成字符串,方便后续解析 */
		buff[node_ptr[3].len] = '\0';
		/** 待转换的格式为:ddmm.mmmmm */
		nsew_d = Nmea_StrToInt(buff, 2);
		nsew_m = atof(&buff[2]);
		/** 北纬为正,南纬为负 */
		if(info.ns == 'N')
		{
			info.lat = nsew_d + nsew_m / 60; /*!< 纬度转成小数 */
		}
		else if(info.ns == 'S')
		{
			info.lat = 0-(nsew_d + nsew_m / 60); /*!< 纬度转成小数 */
		}
    }
	/** 判断东西经 */
	if(node_ptr[6].ptr != NULL)
	{
		if((node_ptr[6].ptr[0] != 'E') && (node_ptr[6].ptr[0] != 'W'))
		{
			return -1;
		}
		info.ew = node_ptr[6].ptr[0];
    }	
	/** 解析经度 */
	if(node_ptr[5].ptr != NULL)
	{
		/** 复制数据 */		
		memcpy(buff, node_ptr[5].ptr, node_ptr[5].len);
		/** 数据域后面添加字符'\0',转换成字符串,方便后续解析 */
		buff[node_ptr[5].len] = '\0';
		/** 待转换的格式为:dddmm.mmmmm */
		nsew_d = Nmea_StrToInt(buff, 3);
		nsew_m = atof(&buff[3]);
		/** 东经为正,西经为负 */
		if(info.ew == 'E')
		{
			info.lon = nsew_d + nsew_m / 60; /*!< 纬度转成小数 */
		}
		else if(info.ew == 'W')
		{
			info.lon = 0-(nsew_d + nsew_m / 60); /*!< 纬度转成小数 */
		}
    }
    /** 对地速度 */
	if(node_ptr[7].ptr != NULL)
	{
		/** 复制数据 */		
		memcpy(buff, node_ptr[7].ptr, node_ptr[7].len);
		/** 数据域后面添加字符'\0',转换成字符串,方便后续解析 */
		buff[node_ptr[7].len] = '\0';
		/** 待转换的格式为:浮点格式 */
        info.sog = atof(buff);
    }
    /** 对地航向 */
	if(node_ptr[8].ptr != NULL)
	{
		/** 复制数据 */		
		memcpy(buff, node_ptr[8].ptr, node_ptr[8].len);
		/** 数据域后面添加字符'\0',转换成字符串,方便后续解析 */
		buff[node_ptr[8].len] = '\0';
		/** 待转换的格式为:浮点格式 */
        info.cog = atof(buff);  
    }	
    /** 日期 */
	if(node_ptr[9].ptr != NULL)  
	{
		/** 复制数据 */		
		memcpy(buff, node_ptr[9].ptr, node_ptr[9].len);
		/** 数据域后面添加字符'\0',转换成字符串,方便后续解析 */
		buff[node_ptr[9].len] = '\0';
		/** 待转换的格式为:ddmmyy */
		if(sscanf(buff, "%2d%2d%2d", &info.utc.day, &info.utc.mon, &year) != 3)
		{
			return -1;
		}	
		info.utc.year = 2000+year;
	}
	/** 全都解析成功才更新 */
	info_ptr->state = info.state;
	info_ptr->mode = info.mode;
	info_ptr->utc.hour = info.utc.hour;
	info_ptr->utc.min = info.utc.min;
	info_ptr->utc.sec = info.utc.sec;
	info_ptr->utc.hsec = info.utc.hsec;
	info_ptr->ns = info.ns;
	info_ptr->lat = info.lat;
	info_ptr->ew = info.ew;
	info_ptr->lon = info.lon;	
	info_ptr->sog = info.sog;
	info_ptr->cog = info.cog;
	info_ptr->utc.year = info.utc.year;
	info_ptr->utc.mon = info.utc.mon;
	info_ptr->utc.day = info.utc.day;
	
	return 0;
}


/*
 * 说明: 解析NMEA报文(不考虑粘包的情况)
 * 参数: ptr, 报文指针
 *       len, 报文长度
 * 参数: 成功报文类型, 数据错误返回-1, 定位异常返回-2
 */
int Nmea_Parse(struct nmea_info_t* info_ptr, char* ptr, uint8_t len)
{
	struct node_t node[20]={0};
	int res=0, type=0;	
	uint8_t num=0;  /*!< 数据域个数 */
	uint8_t offset=0; /*!< 有效数据域长度 */
	
	/** 检查报文头、定界符、尾 */
	if(Nmea_Parse_Header_Tail(ptr, len) != 0)
	{
		return -1;
	}
	/** 检查校验 */
	if(Nmea_Parse_Crc(ptr, len) != 0)
	{
		return -1;
	}
	/** 按照字符','分离各个数据域 */
	for(int i=1; i<len; i++)
	{	
		if(ptr[i] == '*')
		{
			if(offset == 0)
			{
				node[num].len = 0;
				node[num].ptr = NULL;
			}
			else
			{
				node[num].len = offset;
			    node[num].ptr = &ptr[i-offset];
				offset = 0;
			}
			if(num < 20)
			{
			  num++;
			}			
			break; /*!< 检测到字符'*'结束 */
		}
		else if(ptr[i] == ',')
		{
			if(offset == 0)
			{
				node[num].len = 0;
				node[num].ptr = NULL;
			}
			else
			{
				node[num].len = offset;
			  node[num].ptr = &ptr[i-offset];
				offset = 0;
			}
			if(num < 20)
			{
			  num++;
			}
		}
		else
		{
			offset++;					
		}
	}
  /** 解析报文类型 */
	type = Nmea_Parse_Type(node[0].ptr, node[0].len);
	switch(type)
	{
		case GNERR:
			break;
		case GNRMC:
		case GPRMC:		
			res = Nmea_Parse_xxRMC(info_ptr, node, num);
		    if(res != 0)
			{
				return res;
			}
			break;
		case GNGGA:
		case GPGGA:
		    res = Nmea_Parse_xxGGA(info_ptr, node, num);
		    if(res != 0)
			{
				return res;
			}		
		break;
		case GNGLL:
		case GPGLL:		
			res = Nmea_Parse_xxGLL(info_ptr, node, num);
		    if(res != 0)
			{
				return res;
			}		
		break;
		case GPGSV:
		case BDGSV:		
		    res = Nmea_Parse_xxGSV(info_ptr, node, num);
		    if(res != 0)
			{
				return res;
			}		
		break;
		case GPGSA:
		case GNGSA:		
		    res = Nmea_Parse_xxGSA(info_ptr, node, num);
		    if(res != 0)
			{
				return res;
			}		
		break;	
		case GPVTG:
		case GNVTG:		
		    res = Nmea_Parse_xxVTG(info_ptr, node, num);
		    if(res != 0)
			{
				return res;
			}		
		break;
		case GPZDA:
		case GNZDA:		
		    res = Nmea_Parse_xxZDA(info_ptr, node, num);	
		    if(res != 0)
			{
				return res;
			}		
		break;
		case GPTXT:		
		    res = Nmea_Parse_GPTXT(info_ptr, node, num);	
		    if(res != 0)
			{
				return res;
			}		
		break;		
		default:
			break;
	}
	return type;
}

/*
 * 说明: 解析函数
 * 参数: 无
 * 参数: 无
 */
struct nmea_info_t GPS_Info; /*!< 存放解析结果 */
void App_Handle(void)
{
	int res;
	
    switch(Debug.process)
	{
		case 0:

		  Debug.process = 1;
			break;
		case 1:
		  Debug.process = 2;
			break;
		case 2:
			Debug.process = 3;
			break;
		case 3:
		  for(uint8_t i=0; i<19; i++)
		  {
				printf("--------------%d-------------\n\n", i);				
				res = Nmea_Parse(&GPS_Info, buff[i], strlen(buff[i]));
				if(res >= 0)
				{
					switch(res)
					{
						case 0:
							printf("Undefined\n");
							break;
						case 1:
						case 2:
							printf("-->xxRMC:\n");
						  printf("   state=%c, mode=%c\n", GPS_Info.state, GPS_Info.mode);
						  printf("   %d-%d-%d-%d:%d:%d\n", GPS_Info.utc.year, GPS_Info.utc.mon, GPS_Info.utc.day, GPS_Info.utc.hour, GPS_Info.utc.min, GPS_Info.utc.sec);
						  printf("   %c=%f, %c=%f\n", GPS_Info.ns, GPS_Info.lat, GPS_Info.ew, GPS_Info.lon);
						  printf("   sog=%f, cog=%f\n\n", GPS_Info.sog, GPS_Info.cog);							
							break;
						case 3:
						case 4:
							printf("-->xxGGA:\n");
						  printf("   Quality=%c\n", GPS_Info.quality);
						  printf("   %d:%d:%d\n", GPS_Info.utc.hour, GPS_Info.utc.min, GPS_Info.utc.sec);
						  printf("   %c=%f, %c=%f\n", GPS_Info.ns, GPS_Info.lat, GPS_Info.ew, GPS_Info.lon);
						  printf("   Satusecnt=%d\n", GPS_Info.nsat);
						  printf("   hdop=%f, alt=%f, sep=%f\n\n", GPS_Info.hdop, GPS_Info.alt, GPS_Info.sep);						
							break;
						case 5:
						case 6:
							printf("-->xxGLL:\n");
						  printf("   %d:%d:%d\n", GPS_Info.utc.hour, GPS_Info.utc.min, GPS_Info.utc.sec);
						  printf("   %c=%f, %c=%f\n\n", GPS_Info.ns, GPS_Info.lat, GPS_Info.ew, GPS_Info.lon);					
							break;
						case 7:
						case 8:
							printf("-->xxGSV:\n");
						  printf("   msgcnt=%d, satcnt=%d, msgid=%d\n", GPS_Info.msgcnt, GPS_Info.satcnt, GPS_Info.msgid);
						  printf("   Satid:  %2d  %2d  %2d  %2d\n", GPS_Info.satid[0], GPS_Info.satid[1], GPS_Info.satid[2], GPS_Info.satid[3]);
						  printf("   satele: %2d  %2d  %2d  %2d\n", GPS_Info.satele[0], GPS_Info.satele[1], GPS_Info.satele[2], GPS_Info.satele[3]);
						  printf("   sataz:  %3d  %3d  %3d  %3d\n", GPS_Info.sataz[0], GPS_Info.sataz[1], GPS_Info.sataz[2], GPS_Info.sataz[3]);
						  printf("   satsnr: %2d  %2d  %2d  %2d\n\n", GPS_Info.satsnr[0], GPS_Info.satsnr[1], GPS_Info.satsnr[2], GPS_Info.satsnr[3]);											
							break;	
						case 9:
						case 10:
							printf("-->xxGSA:\n");
						  printf("   fix=%d\n\n", GPS_Info.fix);
              for(uint8_t i=0; i<12; i++)
						  {
								printf("%2d", GPS_Info.satidarr[i]);
							}
							printf("\n");
						  printf("   pdop=%4.2f, hdop=%4.2f, vdop=%4.2f\n\n", GPS_Info.pdop, GPS_Info.hdop, GPS_Info.vdop);						
							break;	
						case 11:
						case 12:
							printf("-->xxVTG:\n");
						  printf("   mode=%c\n", GPS_Info.mode);
						  printf("   cogt=%f, cogm=%f, sogn=%f, sogk=%f\n\n", GPS_Info.cogt, GPS_Info.cogm, GPS_Info.sogn, GPS_Info.sogk);						
							break;	
						case 13:
						case 14:
							printf("-->xxZDA:\n");
						  printf("   %d-%d-%d-%d:%d:%d\n\n", GPS_Info.utc.year, GPS_Info.utc.mon, GPS_Info.utc.day, GPS_Info.utc.hour, GPS_Info.utc.min, GPS_Info.utc.sec);					
							break;
						case 15:
							printf("-->GPTXT:\n");
						  printf("   textid=%d\n", GPS_Info.txtid);
						  printf("   %s\n\n", GPS_Info.txt);					
							break; 
            default:
							printf("err res!! \n");
              break;							
					}
				}
				else if(res == -1)
				{
					printf("err:-1\n");
				}
				else
				{
					printf("err:-2\n");
				}
			}
		    LED1_Toggle();
		    LL_mDelay(1000);		
		    Debug.process = 1;
			break;
	}	
}

测试结果如下:

总结:

此NMEA解析库支持常见语句的解析,同时按照此架构方便二次修改,比如修改成只解析某一个或者某几个语句。此解析库只有整个报文解析成功之后才会更新变量,因此每个解析函数需要定义一个存放解析结果的局部变量,这个变量占据栈空间比较大,对于MCU用户来说可能会出现栈溢出现象。建议使用者一方面可以注释掉一些变量减小栈空间的使用,另一方面可以增大栈空间。 另一个由于水平有限,此解析库会存在一些BUG,希望有人使用的话能更加完善,需要的给我留言或者自己下载,同时也希望此代码能给大家提供一些小帮助。

下载链接:

https://download.csdn.net/download/qq_27718231/21148209?spm=1001.2014.3001.5501

给大家提供一个NMEA介绍比较全面的链接:

https://gpsd.gitlab.io/gpsd/NMEA.html#_nmea_standard_sentences

评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值