NMEA-0183协议是GPS接收机应当遵守的标准协议,也是目前GPS接收机上使用最广泛的协议,大多数常见的GPS接收机、GPS数据处理软件、导航软件都遵守或者至少兼容这个协议。
NMEA-0183协议定义的语句非常多,但是常用的或者说兼容性最广的语句只有$GPGGA、$GPGSA、$GPGSV、$GPRMC、$GPVTG、$GPGLL等。
其中$GPRMC语句的格式如下:
$GPRMC,024813.640,A,3158.4608,N,11848.3737,E,10.05,324.27,150706,,,A*50
这里整条语句是一个文本行,行中以逗号“,”隔开各个字段,每个字段的大小(长度)不一,这里的示例只是一种可能,并不能认为字段的大小就如上述例句一样。
字段1:UTC时间,hhmmss.sss格式
字段2:状态,A=定位,V=未定位
字段3:纬度ddmm.mmmm,度分格式(前导位数不足则补0)
字段4:纬度N(北纬)或S(南纬)
字段5:经度dddmm.mmmm,度分格式(前导位数不足则补0)
字段6:经度E(东经)或W(西经)
字段7:速度,节,Knots
字段8:方位角,度
字段9:UTC日期,DDMMYY格式
字段10:磁偏角,(000 - 180)度(前导位数不足则补0)
字段11:磁偏角方向,E=东W=西
字段16:校验值
这里,“*”为校验和识别符,其后面的两位数为校验和,代表了“$”和“*”之间所有字符(不包括这两个字符)的异或值的十六进制值。上面这条例句的校验和是十六进制的50,也就是十进制的80。
提示:^运算符的作用是异或。将$和*之间所有的字符做^运算(第一个字符和第二个字符异或,结果再和第三个字符异或,依此类推)之后的值对65536取余后的结果,应该和*后面的两个十六进制数字的值相等,否则的话说明这条语句在传输中发生了错误。注意这个十六进制值中是会出现A-F的大写字母的。
现在,你的程序要读入一系列GPS输出,其中包含$GPRMC,也包含其他语句。在数据的最后,有一行单独的
END
表示数据的结束。
你的程序要从中找出$GPRMC语句,计算校验和,找出其中校验正确,并且字段2表示已定位的语句,从中计算出时间,换算成北京时间。一次数据中会包含多条$GPRMC语句,以最后一条语句得到的北京时间作为结果输出。
输入格式:多条GPS语句,每条均以回车换行结束。最后一行是END三个大写字母。
输出格式:6位数时间,表达为:
hh:mm:ss
其中,hh是两位数的小时,不足两位时前面补0;mm是两位数的分钟,不足两位时前面补0;ss是两位数的秒,不足两位时前面补0。
输入样例:
$GPRMC,024813.640,A,3158.4608,N,11848.3737,E,10.05,324.27,150706,,,A*50
END
输出样例:
10:48:13
源程序(仅供参考):
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <malloc.h>
#define ARRAY_SIZE 10
#define ARRAY_LEN 150
#define CLEAR(x) memset(&(x), 0, sizeof(x))
/*
********************************************
函数:将一个字符串的第m位到第n位拷贝到另一个字符串中(m<n)
参数:
@source:原始字符串
@des:目标字符串
@start:从第m位开始
@end:到第n位结束
返回值:第m位的地址
********************************************
*/
char* copyStrFromTo(char* source, char* des, int start, int end)
{
int i=0;
if(!(source+start) || !(source+end) || (start >= end)) return NULL;
for(i = 0; i <= (end - start); i++)
{
*(des+i) = *(source + start + i);
}
return (source+start);
}
int main(int argc, char *argv[])
{
int i=0, k=0;
int right_verify = 0;
char verify[2]; //record verify hex data, use sprintf to change
char *s_start, *s_end, s_verify[2], *xor_verify, *pstr;
char UTC_time[10], *BJ_time = NULL;
char s_hh[2]={'\0', '\0'}, s_mm[2]={'\0', '\0'}, s_ss[2]={'\0', '\0'}; //hhmmss.sss
int d_hh, d_mm, d_ss;
char *res = NULL;
char *inputs[ARRAY_SIZE];
while(1) //read the line context is "END" or not
{
inputs[i] = (char *) malloc(ARRAY_LEN * sizeof(char));
res = fgets(inputs[i], 500, stdin); //read every line
if(strstr(inputs[i], "END") == 0) //find string "END" from the input
{
i++;
continue;
}
else
break;
}
putchar('\n');
for(int j=0; j<i; j++) //verify every input
{
fputs(inputs[j], stdout);
s_start = strstr(inputs[j], "$");
s_end = strstr(inputs[j], "*");
s_verify[0] = *(s_end+1) ;
s_verify[1] = *(s_end+2) ;
xor_verify = s_start+1;
for(pstr = s_start+2; pstr < s_end; pstr++)
{
*xor_verify ^= *pstr; //xor the key information
}
printf("xor_verify = %x\n", *xor_verify);
sprintf(verify, "%x", (int)*xor_verify); //sprintf hex to char
if((atoi(verify) == atoi(s_verify)) && (strstr(inputs[j], ",A,")))
{
printf("verify right !\n");
while(',' != *(res = inputs[j]++)); //query the first character ','
for(k=0, pstr = res+1; pstr <= res+10; pstr++) //choose the next 10 char
{
UTC_time[k++] = *pstr;
}
right_verify++; //record the right input
}
else
{
printf("verify error !\n");
}
putchar('\n');
}
printf("UTC_time = %s\n", UTC_time);
res = copyStrFromTo(UTC_time, s_hh, 0, 1); //check the result of res
res = copyStrFromTo(UTC_time, s_mm, 2, 3);
res = copyStrFromTo(UTC_time, s_ss, 4, 5);
d_hh = atoi(s_hh) + 8;
if(d_hh >= 24) d_hh = d_hh - 24;
if(d_hh < 10)
sprintf(s_hh, "0%d", d_hh);
else
sprintf(s_hh, "%d", d_hh);
d_mm = atoi(s_mm);
if(d_mm < 10)
sprintf(s_mm, "0%d", d_mm);
else
sprintf(s_mm, "%d", d_mm);
d_ss = atoi(s_ss);
if(d_ss < 10)
sprintf(s_ss, "0%d", d_ss);
else
sprintf(s_ss, "%d", d_ss);
// printf("%d:%d:%d\n\n", d_hh, d_mm, d_ss);
printf("BeiJing_Time = %s:%s:%s\n\n", s_hh, s_mm, s_ss);
return 0;
}
运行结果:
分析:
(1)题目要求是多条GPS语句输入,每条语句以回车换行结束,最后一行是END三个大写字母,因此,我们可以使用fgets函数来接受标准输入。(#include <stdio.h>)
char * fgets(char* s,int size,FILE * stream);
说明:文件指针stream,对于标准输入输出分别取stdin和stdout
返回值:若成功则返回s指针,返回NULL则表示有错误发生。
(2)对于单行字符串的存储,我们首先会想到字符数组(char a[]),或者是字符型指针变量(char*),对于多行字符串数据,当然可以选用二维数组,那么就需要预先假设这个数组空间很大,在这里我们不用这个办法,选用指针数组(char *a[])存储,[]的优先级比*高,因此,这表示一个数组,数组中的每个元素类型都为char*型,需要存储字符串时,就使用malloc函数分配一块空间,用a[i]指向这一块存储空间。可见,在内存中,每个字符串存储空间之间并不是连续的。(#include <malloc.h>)
(3)判断是否输入结束,我们还需要判断每个字符串的内容,最后一行是END三个大写字母,可以使用strstr函数查找或者是strcmp函数比较。(#include <string.h>)
int strcmp(const char *s1,const char *s2);
返回值:若参数s1和s2字符串相同则返回0;
s1若大于s2则返回大于0的值;
s1若小于s2则返回小于0 的值。
char *strstr(const char *haystack,const char *needle);
返回值:返回指定字符串第一次出现的地址,否则返回0。
(4)验证校验和:我们需要查找字符'$'和'*',这里就有多种方法,1.已知字符'$'是第0个,'*'倒数第3个,直接就可以查找;2.利用for循环遍历每个字符,标记位置即可;3.用strchr或strstr查找函数,同样标记'$'和'*'出现的位置。找到了字符串开始和结束位置,依次对每个字符进行异或就可以得到校验和,我们自己计算出来的校验和可以是十进制或者16进制,而GPS语句最后两位数表示的检验和为16进制,因此这里可以使用格式化函数sprintf将字符转换成16进制格式化字符,再使用atoi函数将字符串转换成整数来比较,即可验证校验和。(#include <stdlib.h>)
char *strchr(const char *s, int c);
返回值:如果找到指定的字符则返回该字符所在地址,否则返回0。
int sprintf( char *str,const char * format,.........);
返回值:成功则返回参数str 字符串长度,失败则返回-1。
int atoi(const char *nptr);
返回值:返回转换后的整型数。
使用这两个函数之前自己最好简单测试下,看看适用情况
(5)提取每个字段的信息:GPS语句行中以逗号","隔开各个字段,每个字段的大小(长度)不一,我们要获得每个字段的信息,只有标记前后两个','字符。题目中要求查找字段1的时间信息,代码中使用的方法就是查找第一个字符',',而且字段0与字段1的长度都是一定的,因此设计了一个子函数来提取时间信息,将GPS语句中的字段1信息拷贝出来。
(6)UTC时间与北京时间的转换:本地时间 = UTC + 时区差