公共的函数
typedef unsigned char uint8_t;
/**
* @brief 时间
* @details 定位数据解算的时间
*/
typedef struct _nmeaTIME
{
int year; /**< Years since 1900 */
int mon; /**< Months since January - [0,11] */
int day; /**< Day of the month - [1,31] */
int hour; /**< Hours since midnight - [0,23] */
int min; /**< Minutes after the hour - [0,59] */
int sec; /**< Seconds after the minute - [0,59] */
int hsec; /**< 秒的百分之一 - [0,99] */
}nmeaTIME;
/**
* @brief 计算 UTC
* @details 将 UT C时间解算成整数
* @param[in] buff 需要解算的时间 stat 0:时分秒 1:年月日
* @param[out] time 解算后的 UTC
* @retval NULL
* @par 修改日志
* fanl于2021-12-11创建
*/
void GetTime(const char *buff, nmeaTIME *time, int stat)
{
int num;
if(stat == 0){
sscanf(buff, "%d.%d", &num, &(time->hsec));
time->hour = num/10000;
time->min = (num%10000)/100;
time->sec = num%100;
}else{
sscanf(buff, "%d", &num);
time->day = num/10000;
time->mon = (num%10000)/100;
time->year = num%100;
}
}
/**
* @brief char to double
* @details 字符串转 double
* @param[in] str 字符串
* @param[out] NULL
* @retval double 数值
* @par 修改日志
* fanl于2021-12-11创建
*/
double CharToDou(char str[])
{
double start = 0, num = 0;
int i = 0;
for(; str[i] != '\0'; i++)
if(str[i] <= '9' && str[i] >= '0'){
if(start == 0){
num = num*10 + str[i] - '0';
}else{
num = num + (str[i] - '0')/start;
start = start*10;
}
}else
start = 10;
return num;
}
/**
* @brief 从buf里面得到第cx个逗号所在的位置
*
* @param buf
* @param cx
* @return 0~0XFE,代表逗号所在位置的偏移.
* 0XFF,代表不存在第cx个逗号
* @par 修改日志
* fanl于2021-12-11创建
*/
uint8_t NMEA_Comma_Pos(uint8_t *buf, uint8_t cx)
{
uint8_t *p = buf;
while(cx)
{
if(*buf=='*'||*buf<' '||*buf>'z')
return 0XFF;//遇到'*'或者非法字符,则不存在第cx个逗号
if(*buf==',')
cx--;
buf++;
}
return buf-p;
}
解析GPRMC
/**
* @brief GPRMC
* @details 解算的 GPRMC
*/
typedef struct _nmeaGPRMC
{
nmeaTIME utc; /**< 位置UTC */
char status; /**< 地位 (A = 定位 or V = 未定位) */
double lat; /**< NDEG 中的纬度 - [度][min].[sec/60] */
char ns; /**< [N]北方 or [S]南方 */
double lon; /**< NDEG 中的经度 - [度][min].[sec/60] */
char ew; /**< [E]东方 or [W]西方 */
double speed; /**< 以节为单位在地面上的速度 */
double direction; /**< 轨迹角度(以度为单位) */
double declination; /**< 磁变化度(从实际航向减去东风变化) */
char declin_ew; /**< [E]东方 or [W]西方 */
char mode; /**< 固定式模式指示器 (A = 自主, D = 差分, E = 估测, N = 数据无效, S = 模拟机) */
}nmeaGPRMC;
/***********************************************************************
* 推荐最小特定GPS / TRANSIT数据(RMC)推荐定位信息
* 格 式:
* $GPRMC,<1>,<2>,<3>,<4>,<5>,<6>,<7>,<8>,<9>,<10>,<11>,<12>*hh<CR><LF>
* $GPRMC,024813.640,A,3158.4608,N,11848.3737,E,10.05,324.27,150706,,,A*50
* 说 明:
* 字段 0:$GPRMC,语句ID,
* 字段 1:UTC时间,hhmmss.sss格式
* 字段 2:状态,A=定位,V=未定位
* 字段 3:纬度ddmm.mmmm,度分格式(前导位数不足则补0)
* 字段 4:纬度N(北纬)或S(南纬)
* 字段 5:经度dddmm.mmmm,度分格式(前导位数不足则补0)
* 字段 6:经度E(东经)或W(西经)
* 字段 7:速度,节,Knots(一节也是1.852千米/小时)
* 字段 8:方位角,度(二维方向指向,相当于二维罗盘)
* 字段 9:UTC日期,DDMMYY格式
* 字段10:磁偏角,(000 - 180)度(前导位数不足则补0)
* 字段11:磁偏角方向,E=东,W=西
* 字段12:模式,A=自动,D=差分,E=估测,N=数据无效(3.0协议内容)
* 字段13:校验值
*
***********************************************************************/
/**
* @brief 解算RMC数据
* @details 将导航定位数据解算成 RMC 数据
* @param[in] buff 需要解算的字符串内容
* @param[out] Date 解算后的 RMC 数据
* @retval 0: 解算成功
* @retval 1:没有 $GPRMC 数据
* @retval 2:无定位数据
* @par 修改日志
* fanl于2021-12-11创建
*/
int GPRMCSolution(nmeaGPRMC *Date, const char *buff)
{
char *buf, time[20]={0};
int i;
buf = strstr(buff, "$GPRMC");
if(buf == NULL)
return 1;
buf += 7;
if(*buf == '*' || *buf == ',')
return 2;
/*** 字段 0:时分秒 time ***/
for(i=0; i < 20; i++, buf++){
if(*buf == ',' || *buf == '\0' || *buf == '\n'){
buf++; time[i] = '\0';
GetTime(time, &(Date->utc), 0);
break;
}
time[i] = *buf;
}
/*** 字段 1:状态,A=定位,V=未定位 ***/
Date->status = *buf;
if(Date->status == 'V'){
return 2;
}else{
buf += 2;
}
/*** 字段 3:纬度ddmm.mmmm ***/
for(i=0; i < 20; i++, buf++){
if(*buf == ',' || *buf == '\0' || *buf == '\n'){
buf++; time[i] = '\0';
Date->lat = CharToDou(time);
break;
}
time[i] = *buf;
}
/*** 字段 4:纬度N(北纬)或S(南纬) ***/
if(*buf == ','){
buf++;
}else{
Date->ns = *buf; buf += 2;
}
/*** 字段 5:经度dddmm.mmmm ***/
for(i=0; i < 20; i++, buf++){
if(*buf == ',' || *buf == '\0' || *buf == '\n'){
buf++; time[i] = '\0';
Date->lon = CharToDou(time);
break;
}
time[i] = *buf;
}
/*** 字段 6:经度E(东经)或W(西经) ***/
if(*buf == ','){
buf++;
}else{
Date->ew = *buf; buf += 2;
}
/*** 字段 7:速度,节,Knots ***/
for(i=0; i < 20; i++, buf++){
if(*buf == ',' || *buf == '\0' || *buf == '\n'){
buf++; time[i] = '\0';
Date->speed = CharToDou(time);
break;
}
time[i] = *buf;
}
/*** 字段 8:方位角,度 ***/
for(i=0; i < 20; i++, buf++){
if(*buf == ',' || *buf == '\0' || *buf == '\n'){
buf++; time[i] = '\0';
Date->direction = CharToDou(time);
break;
}
time[i] = *buf;
}
/*** 字段 9:UTC日期,DDMMYY格式 ***/
for(i=0; i < 20; i++, buf++){
if(*buf == ',' || *buf == '\0' || *buf == '\n'){
buf++; time[i] = '\0';
GetTime(time, &(Date->utc), 1);
break;
}
time[i] = *buf;
}
/*** 字段10:磁偏角,(000 - 180)度 ***/
for(i=0; i < 20; i++, buf++){
if(*buf == ',' || *buf == '\0' || *buf == '\n'){
buf++; time[i] = '\0';
Date->declination = CharToDou(time);
break;
}
time[i] = *buf;
}
/*** 字段11:磁偏角方向,E=东,W=西 ***/
if(*buf == ','){
Date->declin_ew = 'V';
buf++;
}else{
Date->declin_ew = *buf; buf += 2;
}
/*** 字段12:模式,A=自动,D=差分,E=估测,N=数据无效 ***/
for(i=0; i < 20; i++, buf++){
if(*buf == ',' || *buf == '\0' || *buf == '\n'){
buf++; time[i] = '\0';
Date->mode = time[0];
break;
}
time[i] = *buf;
}
return 0;
}
解析GPGGA
/***********************************************************************
* 全球定位系统定位数据(GGA)GPS定位信息
* 格式
* $GPGGA,<1>,<2>,<3>,<4>,<5>,<6>,<7>,<8>,<9>,M,<10>,M,<11> ,<12> * hh
* $GPGGA,024040.00,2310.82644,N,11324.94929,E,2,06,1.30,-40.3,M,-5.2,M,,0000*5F
* 说明
* <1> UTC时间,hhmmss(时分秒)格式
* <2> 纬度ddmm.mmmm(度分)格式(前面的0也将被传输)
* <3> 纬度半球N(北半球)或S(南半球)
* <4> 经度dddmm.mmmm(度分)格式(前面的0也将被传输)
* <5> 经度半球E(东经)或W(西经)
* <6> GPS状态:0 =未定位,1 =非差分定位,2 =差分定位,6 =正在采样
* <7> 正在使用解算位置的卫星数量(00~12)(前面的0也将被传输)
* <8> HDOP水平精度因子(0.5~99.9)
* <9> 海拔高度(-9999.9~99999.9)
* <10>地球沥青球面相对大地水准面的高度
* <11>差分时间(从最近一次接收到差分信号开始的秒数,如果不是差分定位将为空)
* <12>差分站ID号0000~1023(前面的0也将被传输,如果不是差分定位将为空)
***********************************************************************/
/**
* @brief GPGGA
* @details 解算的 GPGGA
*/
typedef struct _nmeaGPGGA
{
int utc; /**< UTC时间,hhmmss(时分秒)格式 */
double lat; /**< 纬度ddmm.mmmm(度分)格式(前面的0也将被传输) */
char ns; /**< 纬度半球N(北半球)或S(南半球) */
double lon; /**< 经度dddmm.mmmm(度分)格式(前面的0也将被传输) */
char ew; /**< 经度半球E(东经)或W(西经) */
int mode; /**< GPS状态:0 =未定位,1 =非差分定位,2 =差分定位,6 =正在采样 */
int sateNum; /**< 正在使用解算位置的卫星数量(00~12)(前面的0也将被传输) */
double HDOP; /**< HDOP水平精度因子(0.5~99.9) */
double Altitude; /**< 海拔高度(-9999.9~99999.9) */
double Leveheght; /**< 地球沥青球面相对大地水准面的高度 */
int DiffTime; /**< 差分时间(从最近一次接收到差分信号开始的秒数,如果不是差分定位将为空) */
int DiffID; /**< 差分站ID号0000~1023(前面的0也将被传输,如果不是差分定位将为空) */
}nmeaGPGGA;
/**
* @brief 解算 GGA 数据
* @details 将导航定位数据解算成 GGA 数据
* @param[in] buff 需要解算的字符串内容
* @param[out] Date 解算后的 GGA 数据
* @retval 0: 解算成功
* @retval 1:没有 $GPGGA 数据
* @retval 2:无定位数据
* @par 修改日志
* fanl于2021-12-11创建
*/
int GPGGASolution(nmeaGPGGA *Date, const char *buff)
{
char *buf, time[20]={0};
int temp;
uint8_t posx;
buf = strstr(buff, "$GPGGA");
if(buf == NULL)
return 1;
posx = NMEA_Comma_Pos(buf, 1); // UTC时间,hhmmss(时分秒)格式
if(posx != 0XFF)
sscanf((const char *)(buf + posx), "%d,", &(Date->utc));
else
return 2;
posx = NMEA_Comma_Pos(buf, 2); // 纬度
if(posx != 0XFF)
sscanf((const char *)(buf + posx), "%lf,", &(Date->lat));
else
return 2;
posx = NMEA_Comma_Pos(buf, 3); // 纬度半球
if(posx != 0XFF)
Date->ns = *(buf + posx);
else
return 2;
posx = NMEA_Comma_Pos(buf, 4); // 经度
if(posx != 0XFF)
sscanf((const char *)(buf + posx), "%lf,", &(Date->lon));
else
return 2;
posx = NMEA_Comma_Pos(buf, 5); // 经度半球
if(posx != 0XFF)
Date->ew = *(buf + posx);
else
return 2;
posx = NMEA_Comma_Pos(buf, 6); // GPS状态
if(posx != 0XFF)
sscanf((const char *)(buf + posx), "%d,", &(Date->mode));
else
return 2;
posx = NMEA_Comma_Pos(buf, 7); // 正在使用解算位置的卫星数量
if(posx != 0XFF)
sscanf((const char *)(buf + posx), "%d,", &(Date->sateNum));
else
return 2;
posx = NMEA_Comma_Pos(buf, 8); // HDOP水平精度因子
if(posx != 0XFF)
sscanf((const char *)(buf + posx), "%lf,", &(Date->HDOP));
else
return 2;
posx = NMEA_Comma_Pos(buf, 9); // 海拔高度
if(posx != 0XFF)
sscanf((const char *)(buf + posx), "%lf,", &(Date->Altitude));
else
return 2;
posx = NMEA_Comma_Pos(buf, 11); // 地球沥青球面相对大地水准面的高度
if(posx != 0XFF)
sscanf((const char *)(buf + posx), "%lf,", &(Date->Leveheght));
else
return 2;
posx = NMEA_Comma_Pos(buf, 13); // 差分时间(从最近一次接收到差分信号开始的秒数,如果不是差分定位将为空)
if(posx != 0XFF)
if(*(buf + posx) == ',')
Date->DiffTime = 0;
else
sscanf((const char *)(buf + posx), "%d,", &(Date->DiffTime));
else
return 2;
posx = NMEA_Comma_Pos(buf, 14); // 差分站ID号
if(posx != 0XFF)
sscanf((const char *)(buf + posx), "%d,", &(Date->DiffID));
else
return 2;
return 0;
}
解析GPGSA
/***********************************************************************
* GPS DOP和有源卫星(GSA)最新卫星信息
* 格式
* $GPGSA,<1>,<2>,<3>,<4> 、、、、、 <12>,<13>,<14>,<15>,<16>,<17>,<18>
* $GPGSA,A,3, 18, 32, 25, 24, 23, 10,
* , , , , , , 2.86,1.30,2.55*07
* 说明
* <1>模式:M =手动,A =自动。
* <2>定位型式1 =未定位,2 =二维定位,3 =三维定位。
* <3>到<14> PRN数字:01至32表天空使用中的卫星编号,最多可接收12颗卫星信息
* <15> PDOP位置精度因子(0.5~99.9)
* <16> HDOP水平精度因子(0.5~99.9)
* <17> VDOP垂直精度因子(0.5~99.9)
* <18>校验和。
***********************************************************************/
/**
* @brief GPGSA
* @details 解算的 GPGSA
*/
typedef struct _nmeaGPGSA
{
char mode; /**< 模式:M =手动,A =自动 */
int type; /**< 定位型式1 =未定位,2 =二维定位,3 =三维定位 */
int PRNnum[12]; /**< PRN数字:01至32表天空使用中的卫星编号 */
double PDOP; /**< PDOP位置精度因子(0.5〜99.9) */
double HDOP; /**< HDOP位置精度因子(0.5〜99.9) */
double VDOP; /**< VDOP位置精度因子(0.5〜99.9) */
}nmeaGPGSA;
/**
* @brief 解算GSA数据
* @details 将导航定位数据解算成 GSA 数据
* @param[in] buff 需要解算的字符串内容
* @param[out] Date 解算后的 GSA 数据
* @retval 0: 解算成功
* @retval 1:没有 $GPGSA 数据
* @retval 2:无定位数据
* @par 修改日志
* fanl于2021-12-11创建
*/
int GPGSASolution(nmeaGPGSA *Date, const char *buff)
{
char *buf, time[100]={0};
int i, j, num=0;
buf = strstr(buff, "$GPGSA");
if(buf == NULL)
return 1;
buf += 7;
if(*buf == '*' || *buf == ',')
return 2;
/*** <1>模式:M =手动,A =自动。 ***/
Date->mode = *buf; buf += 2;
/*** <2>定位型式1 =未定位,2 =二维定位,3 =三维定位 ***/
for(i=0; i<20; i++, buf++){
if(*buf == ',' || *buf == '\0' || *buf == '\n'){
buf++;
break;
}
Date->type = *buf - '0';
}
/*** <3>到<14> PRN数字:01至32表天空使用中的卫星编号,最多可接收12颗卫星信息 ***/
for(j=0; j<12; j++){
for(i=0; i<20; i++, buf++){
if(*buf == ',' || *buf == '\0' || *buf == '\n'){
Date->PRNnum[j] = num;
num = 0; buf++;
break;
}
num = num*10 + *buf - '0';
}
}
/*** <15> PDOP位置精度因子(0.5~99.9) ***/
for(i=0; i < 20; i++, buf++){
if(*buf == ',' || *buf == '\0' || *buf == '\n'){
buf++; time[i] = '\0';
Date->PDOP = CharToDou(time);
break;
}
time[i] = *buf;
}
/*** <16> HDOP位置精度因子(0.5~99.9) ***/
for(i=0; i < 20; i++, buf++){
if(*buf == ',' || *buf == '\0' || *buf == '\n'){
buf++; time[i] = '\0';
Date->HDOP = CharToDou(time);
break;
}
time[i] = *buf;
}
/*** <17> VDOP位置精度因子(0.5~99.9) ***/
for(i=0; i < 20; i++, buf++){
if(*buf == '*' || *buf == '\0' || *buf == '\n'){
buf++; time[i] = '\0';
Date->VDOP = CharToDou(time);
break;
}
time[i] = *buf;
}
return 0;
}
解析GPGLL
/***********************************************************************
* 地理定位信息
* 格式
* $GPGLL,2310.82644,N,11324.94929,E,024040.00,A,D*68
* 说明
* 字符串0:$ GPGLL,语句ID,表明该语句为地理位置(GLL)地理定位信息
* 分段1:纬度ddmm.mmmm,度分格式(前导数值不足则补0)
* 分段2:纬度N(北纬)或S(南纬)
* 分段3:经度dddmm.mmmm,度分格式(前导数值不足则补0)区间4:经度E(东经)或W(西经)
* 分段5:UTC时间,hhmmss.sss格式
* 分段6:状态,A =定位,V =未定位
* 分段7:校正值
* ***********************************************************************/
/**
* @brief GPGLL
* @details 解算的 GPGLL
*/
typedef struct _nmeaGPGLL
{
double lat; /**< 纬度ddmm.mmmm(度分)格式(前面的0也将被传输) */
char ns; /**< 纬度半球N(北半球)或S(南半球) */
double lon; /**< 经度dddmm.mmmm(度分)格式(前面的0也将被传输) */
char ew; /**< 经度半球E(东经)或W(西经) */
int utc; /**< UTC时间,hhmmss(时分秒)格式 */
char mode; /**< 状态,A =定位,V =未定位 */
}nmeaGPGLL;
/**
* @brief 解算 GLL 数据
* @details 将导航定位数据解算成 GLL 数据
* @param[in] buff 需要解算的字符串内容
* @param[out] Date 解算后的 GLL 数据
* @retval 0: 解算成功
* @retval 1:没有 $GPGLL 数据
* @retval 2:无定位数据
* @par 修改日志
* fanl于2021-12-11创建
*/
int GPGLLSolution(nmeaGPGLL *Date, const char *buff)
{
char *buf, time[20]={0};
int temp;
uint8_t posx;
buf = strstr(buff, "$GPGLL");
if(buf == NULL)
return 1;
posx = NMEA_Comma_Pos(buf, 1); // 纬度
if(posx != 0XFF)
sscanf((const char *)(buf + posx), "%lf,", &(Date->lat));
else
return 2;
posx = NMEA_Comma_Pos(buf, 2); // 纬度半球
if(posx != 0XFF)
Date->ns = *(buf + posx);
else
return 2;
posx = NMEA_Comma_Pos(buf, 3); // 经度
if(posx != 0XFF)
sscanf((const char *)(buf + posx), "%lf,", &(Date->lon));
else
return 2;
posx = NMEA_Comma_Pos(buf, 4); // 经度半球
if(posx != 0XFF)
Date->ew = *(buf + posx);
else
return 2;
posx = NMEA_Comma_Pos(buf, 5); // UTC时间,hhmmss(时分秒)格式
if(posx != 0XFF)
sscanf((const char *)(buf + posx), "%d,", &(Date->utc));
else
return 2;
posx = NMEA_Comma_Pos(buf, 6); // 状态,A =定位,V =未定位
if(posx != 0XFF)
Date->mode = *(buf + posx);
else
return 2;
return 0;
}
解析GPVTG
/***********************************************************************
* 履带良好和地面速度(VTG)地面速度信息
* 格式
* $GPVTG,<1>,T,<2>,M,<3>,N,<4>,K,<5> * hh
* $GPVTG,,T,,M,0.010,N,0.019,K,D*2F
* 说明
* <1>以真北为参考基准的地面航向(000~359度,前面的0也将被传输)
* <2>以磁北为参考基准的地面航向(000~359度,前面的0也将被传输)
* <3>地面速率(000.0~999.9节,前面的0也将被传输)
* <4>地面速度(0000.0~1851.8公里/小时,前面的0也将被传输)
* <5>模式指示(仅NMEA0183 3.00版本输出,A =自主定位,D =差分,E =投放,N =数据无效)
***********************************************************************/
/**
* @brief GPVTG
* @details 解算的 GPVTG
*/
typedef struct _nmeaGPVTG
{
int course1; /**< 以真北为参考基准的地面航向 */
int course2; /**< 以磁北为参考基准的地面航向 */
double GrSpeed; /**< 地面速率 */
double GrSpees; /**< 地面速度 */
char mode; /**< 模式指示 */
}nmeaGPVTG;
/**
* @brief 解算 VTG 数据
* @details 将导航定位数据解算成 VTG 数据
* @param[in] buff 需要解算的字符串内容
* @param[out] Date 解算后的 VTG 数据
* @retval 0: 解算成功
* @retval 1:没有 $GPVTG 数据
* @retval 2:无定位数据
* @par 修改日志
* fanl于2021-12-11创建
*/
int GPVTGSolution(nmeaGPVTG *Date, const char *buff)
{
char *buf, time[20]={0};
int temp;
uint8_t posx;
buf = strstr(buff, "$GPVTG");
if(buf == NULL)
return 1;
posx = NMEA_Comma_Pos(buf, 1); // 以真北为参考基准的地面航向
if(posx != 0XFF)
sscanf((const char *)(buf + posx), "%d,", &(Date->course1));
else
return 2;
posx = NMEA_Comma_Pos(buf, 3); // 以磁北为参考基准的地面航向
if(posx != 0XFF)
sscanf((const char *)(buf + posx), "%d,", &(Date->course2));
else
return 2;
posx = NMEA_Comma_Pos(buf, 5); // 地面速率
if(posx != 0XFF)
sscanf((const char *)(buf + posx), "%lf,", &(Date->GrSpeed));
else
return 2;
posx = NMEA_Comma_Pos(buf, 7); // 地面速度
if(posx != 0XFF)
sscanf((const char *)(buf + posx), "%lf,", &(Date->GrSpees));
else
return 2;
posx = NMEA_Comma_Pos(buf, 9); // 模式指示
if(posx != 0XFF)
Date->mode = *(buf + posx);
else
return 2;
return 0;
}
测试函数
int main()
{
nmeaGPRMC RMCDate;
nmeaGPGSA GSADate;
nmeaGPVTG VTGDate;
nmeaGPGGA GGADate;
nmeaGPGLL GLLDate;
char buff[] = "$GPRMC,024051.00,A,2310.82644,N,11324.94929,E,0.010,,101221,,,D*71\n\
$GPVTG,,T,,M,0.010,N,0.019,K,D*2F\n\
$GPGGA,024040.00,2310.82644,N,11324.94929,E,2,06,1.30,-40.3,M,-5.2,M,,0123*5F\n\
$GPGSA,A,3,18,32,25,24,23,10,,,,,,,2.86,1.30,2.55*07\n\
$GPGSV,2,1,08,10,40,331,33,14,,,26,18,49,201,46,23,67,017,32*45\n\
$GPGSV,2,2,08,24,48,038,26,25,23,156,45,32,25,284,45,50,59,148,43*7B\n\
$GPGLL,2310.82644,N,11324.94929,E,024040.00,A,D*68\n";
printf("%s", buff);
GPRMCSolution(&RMCDate, buff);
GPVTGSolution(&VTGDate, buff);
GPGGASolution(&GGADate, buff);
GPGSASolution(&GSADate, buff);
GPGLLSolution(&GLLDate, buff);
return 0;
}