在之前一篇文章:嵌入式Linux系列第21篇:应用程序之开篇闲聊 里,当时给自己定了一个小目标,要实现如下功能的小项目: 1) 串口1实时读取GPS数据,同时转发到串口2输出 2) 将获取到的经纬度信息,通过网口UDP方式发送到电脑端,电脑端通过上位机软件实时显示设备的位置信息。 3) 安卓手机可以通过WIFI连接到板子,手机APP也可以显示设备的位置信息。 4) 设备通过4G将位置信息传输到云平台,在任何一个可以上网的电脑上通过浏览器可以实时显示设备的位置信息。 今天这篇文章要完成的功能是串口读取并解析GPS数据。 GPS数据解析的核心问题可以归结为如何解析以逗号作为分隔符的字符串问题。看似很简单的一个功能,真正实现起来也那不是那么容易,在调试的过程中,我就遇到了很多的小问题,在此做个完整的记录与总结,希望对大家有帮助。 char *strtok(char *str, const char *delim),功能是分解字符串str 为一组字符串,delim为分隔符。 该函数返回被分解的第一个子字符串,如果没有可检索的字符串,则返回一个空指针。 我们看一下这个函数的使用例子, #include <stdio.h> #include <stdlib.h> #include <string.h> int main(void) { char str[] ="Apple,Pear,Potato,11"; char* tokens = strtok (str,","); //iterate over tokens.. . while (tokens!= NULL) { printf ("%s",tokens); tokens = strtok (NULL,","); } return 0; } 它的输出结果为: Apple Pear Potato 11 上述代码,有一个地方,不知道大家注意到没有,第一次调用strtok的时候,第一个参数为str,后面每次调用时参数都是NULL。The first call to strtok must pass the C string to tokenize, and subsequent calls must specify NULL as the first argument, which tells the function to continue tokenizing the string you passed in first. 如果逗号之间为空,情况会是什么样子呢?看一下下面的例子: #include <stdio.h> #include <stdlib.h> #include <string.h> int main(void) { char str[] ="Apple,Pear,,Potato,11"; char *tokens = strtok (str,","); //iterate over tokens.. . while (tokens!= NULL) { printf ("%s",tokens); tokens = strtok (NULL,","); } return 0; } 输出结果如下: Apple Pear Potato 11 和第一个程序输出的结果完全一致,起初我对这个结果很不理解,我本能的以为第一次调用strtok的返回值是”Apple”,第二次调用strtok的返回值为”Pear”,第三次调用后,由于2个逗号之间是空的,我以为返回值会是NULL,然后在第四次调用后,得到”Potato”。 #include <stdio.h> #include <stdlib.h> #include <string.h> char* strrpl(char *str, char* find, char *replace) { int i; char *pt = strstr(str, find); char *firstStr; if(pt == NULL){ printf("cannot find string "); return NULL; } firstStr = (char* )malloc(100 * sizeof(char)); // copy just until i find what i need to replace // i tried to specify the length of firstStr just with pt - str strncpy(firstStr, str, strlen(str) - strlen(pt)); strcat(firstStr, replace); strcat(firstStr, pt + strlen(find)); for(i = 0; i < strlen(firstStr); i++) str[i] = firstStr[i]; return str; } int main(void) { char str[] ="Apple,Pear,,Potato,11"; strrpl(str,",,",",@,"); printf ("%s",str); char *tokens = strtok (str,","); //iterate over tokens.. . while (tokens!= NULL) { printf ("%s",tokens); tokens = strtok (NULL,","); } return 0; } 输出的结果是: 这样就实现了两个逗号替换的功能,如果字符串是下面这个呢? 该字符串中间出现了连续3个逗号,并且后面还有一次连续2个逗号, char str[] ="Apple,Pear,,,Potato,,11"; 运行一下,我们看看结果 结果是只替换了第一个连续逗号的地方,如何实现让字符串里所有的连续逗号都被替换呢?重复的做一件事,只需要加一个循环即可,修改后的代码如下: #include <stdio.h> #include <stdlib.h> #include <string.h> char* strrpl(char *str, char* find, char *replace) { int i; char *pt = strstr(str, find); char *firstStr; if(pt == NULL){ printf("cannot find string "); return NULL; } firstStr = (char* )malloc(100 * sizeof(char)); // copy just until i find what i need to replace // i tried to specify the length of firstStr just with pt - str strncpy(firstStr, str, strlen(str) - strlen(pt)); strcat(firstStr, replace); strcat(firstStr, pt + strlen(find)); for(i = 0; i < strlen(firstStr); i++) str[i] = firstStr[i]; return str; } int main(void) { char str[] ="Apple,Pear,,,Potato,,11"; while (strstr(str, ",,")) strrpl(str, ",,", ",@,"); printf("%s",str); char *tokens = strtok (str,","); //iterate over tokens.. . while (tokens!= NULL) { printf ("%s",tokens); tokens = strtok (NULL,","); } return 0; } 改进后的代码如下: #include <stdio.h> #include <stdlib.h> #include <string.h> char* strrpl(char *str, char* find, char *replace) { int i; char *pt = strstr(str, find); char *firstStr; if(pt == NULL){ printf("cannot find string "); return NULL; } firstStr = (char* )malloc(100 * sizeof(char)); // copy just until i find what i need to replace // i tried to specify the length of firstStr just with pt - str strncpy(firstStr, str, strlen(str) - strlen(pt)); strcat(firstStr, replace); strcat(firstStr, pt + strlen(find)); for(i = 0; i < strlen(firstStr); i++) str[i] = firstStr[i]; return str; } int main(void) { char str[] ="Apple,Pear,,,Potato,,11"; char *buff; buff = malloc(sizeof(str)+100); memset(buff, 0, sizeof(str)+100); memcpy(buff, str, sizeof(str)); while (strstr(buff, ",,")) strrpl(buff, ",,", ",@,"); printf("%s",buff); char *tokens = strtok (buff,","); //iterate over tokens.. . while (tokens!= NULL) { printf ("%s",tokens); tokens = strtok (NULL,","); } free(buff); return 0; } 输出结果如下: 经过修改了的这份代码是不是就没有问题了呢?答案是否!如果我将str数组变长,变成下面的这一串内容, char str[] = "$GNRMC,051035.00,A,4000.74054,N,11628.03344,E,0.253,,020320,6.91,W,D*23 $GNVTG,,T,,M,0.253,N,0.468,K,D*36 $GNGGA,051035.00,4000.74054,N,11628.03344,E,2,08,2.08,3.3,M,-8.3,M,,0000*5D $GNGSA,A,3,29,14,27,42,03,,,,,,,,3.33,2.08,2.60*1F $GNGSA,A,3,87,66,67,,,,,,,,,,3.33,2.08,2.60*1F $GPGSV,5,1,17,03,15,250,28,04,47,302,17,08,03,196,09,09,16,318,13*7B $GPGSV,5,2,17,14,23,157,32,16,72,264,19,21,08,092,20,22,07,230,34*77 $GPGSV,5,3,17,23,41,303,,26,72,027,21,27,29,179,28,29,15,039,30*77 $GPGSV,5,4,17,31,47,089,15,40,13,251,,41,32,226,31,42,35,140,31*7D $GPGSV,5,5,17,50,42,164,34*48 $GLGSV,3,1,10,66,12,192,26,67,44,240,28,68,34,310,,76,25,063,*6E $GLGSV,3,2,10,77,58,357,,78,29,287,,85,01,012,,86,30,057,*60 $GLGSV,3,3,10,87,26,128,32,88,00,163,*61 $GNGLL,4000.74054,N,11628.03344,E,051035.00,A,D*7A"; 其他代码不变,运行结果会是: 在出现这个问题之前,我都没有仔细的阅读直接拷贝过来strrpl函数的内部实现细节,这时就得好好看看了,经过很长时间调试,找到问题出在下面这句话上面, firstStr = (char* )malloc(100 * sizeof(char)); 和这句话相关,有3个非常重要的值得大家注意的地方: #include <stdio.h> #include <stdlib.h> #include <string.h> char* strrpl(char *str, char* find, char *replace) { int i; char *pt = strstr(str, find); char *firstStr; if(pt == NULL){ printf("cannot find string "); return NULL; } int len = strlen(str)+1+strlen(replace)-strlen(find); firstStr = (char* )malloc(len); memset(firstStr,0,len); // copy just until i find what i need to replace // i tried to specify the length of firstStr just with pt - str strncpy(firstStr, str, strlen(str) - strlen(pt)); strcat(firstStr, replace); strcat(firstStr, pt + strlen(find)); for(i = 0; i < strlen(firstStr); i++) str[i] = firstStr[i]; free(firstStr); return str; } int main(void) { char str[] = "$GNRMC,051035.00,A,4000.74054,N,11628.03344,E,0.253,,020320,6.91,W,D*23 $GNVTG,,T,,M,0.253,N,0.468,K,D*36 $GNGGA,051035.00,4000.74054,N,11628.03344,E,2,08,2.08,3.3,M,-8.3,M,,0000*5D $GNGSA,A,3,29,14,27,42,03,,,,,,,,3.33,2.08,2.60*1F $GNGSA,A,3,87,66,67,,,,,,,,,,3.33,2.08,2.60*1F $GPGSV,5,1,17,03,15,250,28,04,47,302,17,08,03,196,09,09,16,318,13*7B $GPGSV,5,2,17,14,23,157,32,16,72,264,19,21,08,092,20,22,07,230,34*77 $GPGSV,5,3,17,23,41,303,,26,72,027,21,27,29,179,28,29,15,039,30*77 $GPGSV,5,4,17,31,47,089,15,40,13,251,,41,32,226,31,42,35,140,31*7D $GPGSV,5,5,17,50,42,164,34*48 $GLGSV,3,1,10,66,12,192,26,67,44,240,28,68,34,310,,76,25,063,*6E $GLGSV,3,2,10,77,58,357,,78,29,287,,85,01,012,,86,30,057,*60 $GLGSV,3,3,10,87,26,128,32,88,00,163,*61 $GNGLL,4000.74054,N,11628.03344,E,051035.00,A,D*7A"; char *buff; buff = malloc(sizeof(str)+100); memset(buff, 0, sizeof(str)+100); memcpy(buff, str, sizeof(str)); while (strstr(buff, ",,")) strrpl(buff, ",,", ",@,"); printf("%s",buff); char *tokens = strtok (buff,","); //iterate over tokens.. . while (tokens!= NULL) { printf ("%s",tokens); tokens = strtok (NULL,","); } free(buff); return 0; } 这样再次运行代码,就可以得到正确的结果了。 运行后,会输出如下信息: 上述代码中重点是gnss.c文件中的gps_analyse函数,大家可以好好看看, int gps_analyse(char *buff,int buff_len,GNSS *gps_data) { char *ptr = NULL; if(strlen(buff)<10) { return -1; } /* 如果buff字符串中包含字符"$GPRMC"则将$GPRMC的地址赋值给ptr */ if( NULL==(ptr=strstr(buff,"$GPRMC")) && NULL==(ptr=strstr(buff,"$GNRMC")) ) { return -2; } if(check_nmea_message(ptr, 0, buff_len) <0 ) { printf("check error!"); return -3; } char *tmpbuf; tmpbuf = (char *)malloc(strlen(ptr)+100); memset(tmpbuf, 0, strlen(ptr)+100); memcpy(tmpbuf, ptr, strlen(ptr)); while (strstr(tmpbuf, ",,")) strrpl(tmpbuf, ",,", ",@,"); printf("tmpbuf:%s ",tmpbuf); char* pch = strtok(tmpbuf, ","); // 1 time pch = strtok(NULL, ","); nmea_get_time(pch, &gps_data->time); // 2 status pch = strtok(NULL, ","); gps_data->pos_state = *pch; //3 latitude pch = strtok(NULL, ","); nmea_lat_long_to_double(&gps_data->latitude, pch, strlen(pch)); //4 latitude direction pch = strtok(NULL, ","); gps_data->NS = *pch; //5 longitude pch = strtok(NULL, ","); nmea_lat_long_to_double(&gps_data->longitude, pch, strlen(pch)); //6 long direct pch = strtok(NULL, ","); gps_data->EW = *pch; //7 speed pch = strtok(NULL, ","); gps_data->speed = 1.852 * strtof(pch, (char **) NULL ) / 3.6; //8 direction pch = strtok(NULL, ","); gps_data->direction = strtof(pch, (char**)NULL); //9 date pch = strtok(NULL, ","); nmea_get_date(pch, &gps_data->time); //10 不处理 pch = strtok(NULL, ","); //11 不处理 pch = strtok(NULL, ","); //12 mode pch = strtok(NULL, ","); gps_data->pos_mode = *pch; free(tmpbuf); return 0; } 我在调试过程中遇到了很多的问题,通过自己实际动手搬运、修改、调试代码收获了很多知识,主要有以下几点: |
|
来自: TopSemic嵌入式 > 《待分类》