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