转载:如何利用strstr和sscanf解析GPS信息
直接复制:作者:杨硕,华清远见嵌入式学院讲师。
做一个GPS导航的项目,需要读取GPS模块以ASCII码的形式发送过来的数据,然后对这些数据进行处理,提取我们需要的信息。这就涉及到很多操作字符串的问题。下面就以此为例,利用strstr函数和sscanf函数解析GPS数据。
关于sscanf函数请见http://www.91linux.com/html/article/program/cpp/20081130/14121.html
GPS输出的数据格式如下:
$GPGGA,121252.000,3937.3032,N,11611.*6,E,1,05,2.0,45.9,M,-5.7,M,,0000*77
$GPRMC,121252.000,A,3958.3032,N,11629.*6,E,15.15,359.95,070306,,,A*54
$GPVTG,359.95,T,,M,15.15,N,28.0,K,A*04
$GPGGA,121253.000,3937.3090,N,11611.6057,E,1,06,1.2,44.6,M,-5.7,M,,0000*72
$GPGSA,A,3,14,15,05,22,18,26,,,,,,,2.1,1.2,1.7*3D
$GPGSV,3,1,10,18,84,067,23,09,67,067,27,22,49,312,28,15,47,231,30*70
$GPGSV,3,2,10,21,32,199,23,14,25,272,24,05,21,140,32,26,14,070,20*7E
$GPGSV,3,3,10,29,07,074,,30,07,163,28*7D
可以看到,GPS模块发送过来的原始数据有很多,但是通常我们只需要其中的一部分信息就够用了,比如对于导航的功能,我们只需要以$GPRMC开头,以换行符结束的一行信息就够了。即:
$GPRMC,121252.000,A,3958.3032,N,11629.*6,E,15.15,359.95,070306,,,A*54
因此我们需要做的就是从读取的数据中截取以$GPRMC开头的一行信息,然后从中解析出经纬度、日期时间等有效信息即可。
假设从串口读取的数据存放在一个字符串指针char *raw_buf指向的内存单元里,首先我们通过ANSI C提供的strstr()函数找到以$GPRMC开头以换行符’\n’结束的字符串:
/* find "$GPRMC" from raw_buf */
if ((wellhandled_string = strstr(raw_buf, “$GPRMC”)) != NULL)
{
for (i=0; i<strlen(wellhandled_string); i++)
{
if (wellhandled_string == '\n')
{
wellhandled_string = '\0'; //replace ‘\n’ with null
}
}
}
strstr()函数的原型是这样声明的:
char *strstr(const char *haystack, const char *needle);
strstr()函数可以在字符串haystack中搜索字符串needle第一次出现的位置,并且返回指向字符串needle首地址的指针,如果没有搜索到则返回NULL。因此上面的代码为我们在读取的原始数据raw_buf里搜索$GPRMC第一次出现的位置,并将返回的指针赋给wellhandled_string,这样如果搜索成功,则wellhandled_string就会指向以$GPRMC开始的字符串,接下来通过一个for循环找到换行符’\n’,将其替换为’\0’,即字符串结束符。这样就得到了一个指向有效数据的字符串指针wellhandled_string。
然后要做的工作就是从wellhandled_string中提取出经纬度、日期时间等信息。这个工作就可以交给强大的sscanf函数来实现。sscanf函数的原型如下:
int sscanf(const char *str, const char *format, ...);
我们都比较熟悉scanf这个函数,scanf可以从标准输入流读取与指定格式相符的数据。sscanf则是从const char *str中读取。它的强大之处在于可以方便地从字符串中取出整数、浮点数和字符串等各种类型的数据,而且它还具有类似于正则表达式的匹配功能,sscanf默认是以空格分隔字符串的,如果不是以空格来分割的话,就可以使用%[ ]来指定分割的条件。如%表示读取a到z的所有字符,%[^a-z]表示过滤a-z之间的所有字符,即只要遇到a到z之间的任意字符,转换立刻停止。比如:
sscanf(“abcdefABCDEF”, “%[^A-Z]”, str);
printf(“%s\n”, str);
result is: abcdef
%[^A-Z]这样的匹配格式为我们取遇到大写字母为止的字符串。利用这种匹配方式,我们就可以灵活的操作字符串,得到我们想要的结果。
现在我们需要从下面的字符串中提取有效信息:
$GPRMC,121252.000,A,3958.3032,N,11629.*6,E,15.15,359.95,070306,,,A*54
GPRMC每个字段的含义如下:
$GPRMC,<1>,<2>,<3>,<4>,<5>,<6>,<7>,<8>,<9>,<10>,<11>,<12>*hh<CR><LF>
<1> UTC时间,hhmmss(时分秒)格式
<2> 定位状态,A=有效定位,V=无效定位
<3> 纬度ddmm.mmmm(度分)格式(前面的0也将被传输)
<4> 纬度半球N(北半球)或S(南半球)
<5> 经度dddmm.mmmm(度分)格式(前面的0也将被传输)
<6> 经度半球E(东经)或W(西经)
<7> 地面速率(000.0~999.9节,前面的0也将被传输)
<8> 地面航向(000.0~359.9度,以真北为参考基准,前面的0也将被传输)
<9> UTC日期,ddmmyy(日月年)格式
<10> 磁偏角(000.0~180.0度,前面的0也将被传输)
<11> 磁偏角方向,E(东)或W(西)
<12> 模式指示(仅NMEA0183 3.00版本输出,A=自主定位,D=差分,E=估算,N=数据无效)
我们提取1~9九条信息。用一个结构体存放这些信息:
typedef struct gps_info
{
char utc_time;
char status;
float latitude_value;
char latitude;
float longtitude_value;
char longtitude;
float speed;
float azimuth_angle;
char utc_data;
}GPS_INFO;
因为每一个字段之间都是以逗号间隔开的,所以我们可以利用%[^,]来分割字符串,这样用sscanf函数就可以实现对有效信息的提取:
sscanf(wellhandled_string,"$GPRMC,%[^,],%c,%f,%c,%f,%c,%f,%f,%[^,]",
rmc_info->utc_time,\
&(rmc_info->status),&(rmc_info->latitude_value),&(rmc_info->latitude),\
&(rmc_info->longtitude_value),&(rmc_info->longtitude),&(rmc_info->speed),\
&(rmc_info->azimuth_angle),\
rmc_info->utc_data );
这个函数执行后,打印出的保存在struct gps_info结构体里的信息如下所示:
utc_time: 024813.640
status: A
latitude: N latitude value: 3158.460693
longtitude: E longtitude value: 11848.374023
speed: 10.050000
azimuth_angle: 324.269989
utc_data: 150706
可见,利用好sscanf函数,可以让我们可以很高效的处理字符串。
“本文由华清远见http://www.embedu.org/index.htm提供” 好贴,受教了 MARK mark MARK Mark 马克 mark 标记下,谢谢楼主。 非常精彩! mark 很好~~ mark 这个思路好 mark了 mark 好东西 受教了………… mark 非常简明 学习!! sscanf 高效个P....PC机上罢了,8位机上开销太大 真简洁 mark 回【19楼】 snoopyzz
我是在arm上使用这个函数,一般不必考虑这个问题,当有效率需求时可以自己写个专用函数解决这个问题,这只是其中一个方法而已。
有时候我们考虑跟多的是可读性与通用性。 MARK mark mark mark mark mark M 不错 这种解析方法,在遇到数据不完整时候会崩溃,有没有更好的解析方法? mark 留个脚印。以后好找。 mark mark 我是先开辟一个大的缓冲区,接收完GPS发过来的数据后超时判断再处理数据,自己写了个查找字符串的函数.... mark,转载:如何利用strstr和sscanf解析GPS信息 mark Mark 我的方法是找字符"R"
因为整个GPS信息中,只有GPRMC才有“R”,其它地方都没有。。。 INT16U Find_Str(INT8U *str, INT8U *ptr)
{
INT16U index = 0;
INT8U *s_temp;
INT8U *m_temp;
INT8U *t_temp;
if(str == 0 || ptr == 0)
return 0;
for(s_temp = str; *s_temp != '\0'; s_temp++)
{
index++;
m_temp = s_temp;
for(t_temp = ptr; *t_temp == *m_temp; t_temp++, m_temp++){};
if(*t_temp == '\0')
return index;
}
return 0;
}
我改写的字符串比较函数,直接在缓冲区内查找“GPRMC",返回偏移量,提取其它的GPS数据也一样 memset(&GPS_GSV_Data, 0x00, sizeof(GPS_GSV_Data));// 每次处理前将结构体清零
index = Find_Str(Uart1_RcvBuf, "$GPGSV,");
if(index)
{
....
}
这样就行了,另外如19楼snoopyzz所说....我也基本不用printf.... mark 回复【41楼】amazing030
-----------------------------------------------------------------------
愣是没看懂,望稍微解释一下,看见指针就晕的人.... for(s_temp = str; *s_temp != '\0'; s_temp++)// 遍历所查找的字符串
{
index++; // 当前偏移量递增
m_temp = s_temp; // 指向当前字符串指针
for(t_temp = ptr; *t_temp == *m_temp; t_temp++, m_temp++){}; // 逐个比较当前字符串指针后的字符是否与要比较
// 的字符串相同
if(*t_temp == '\0') // 如果该指针为空字符则说明上面已比较完而且都相同
return index; // 此时返回偏移量index
}
return 0; 好,正好在ST上刚好用到。 扫描回复【40楼】fsclub绿林好汉
我的方法是找字符"r"
因为整个gps信息中,只有gprmc才有“r”,其它地方都没有。。。
-----------------------------------------------------------------------
我也是查找GPRMC信息,C3-370模块,因为其中只有这里会出现‘C’,所以扫描到‘C’才开始将数据装入缓冲区。
GPRMC信息格式非常规整,所以用结构体去筛选信息:
struct GPS_INFO{
unsigned char NOTE0;
unsigned char TIME; //时间
unsigned char NOTE1;
unsigned char DAV; //信息有效
unsigned char NOTE2;
unsigned char Latitude; //纬度
unsigned char NOTE3;
unsigned char NS; //NS标识
unsigned char NOTE4;
unsigned char Longitude; //经度
unsigned char NOTE5;
unsigned char EW; //EW标识
unsigned char NOTE6; //-------------------------
unsigned char SPD; //速度
unsigned char NOTE7;
unsigned char DIR; //速度
unsigned char NOTE8;
unsigned char DATE; //时间
unsigned char OTH; //其他
};
union GPS_DATA{
unsigned char rx_buffer1;
struct GPS_INFO GI;
};
union GPS_DATA RCV;
如果DAV=‘A’表示坐标信息有效,否则丢弃。
串口接收数据就非常简单了,查找'C'开头的信息,然后话开始接收数据,最后检查到星号'*'时结束,就让主程序处理了:
unsigned char rx_wr_index1;
bit start,end;
interrupt void usart1_rx_isr(void)
{
char status,data;
status=UCSR1A;
data=UDR1;
if ((status & (FRAMING_ERROR | PARITY_ERROR | DATA_OVERRUN))==0)
{
if(data=='C')
{
start=1;
rx_wr_index1=0x00;
return;
}
if((start==1)&&(data=='*'))
{
start=0;
end=1;
return;
}
if(start==1)
{
RCV.rx_buffer1=data;
rx_wr_index1++;
return;
}
}
}
标志位end=1时表示收到了一串完整的GPRMC信息,然后在主程序中读取GPS_DATA共用体中成员的数据即可。 回复【47楼】XA144F
-----------------------------------------------------------------------
不错,受教了!! 回复【47楼】XA144F
扫描回复【40楼】fsclub绿林好汉
我的方法是找字符"r"
因为整个gps信息中,只有gprmc才有“r”,其它地方都没有。。。
-----------------------------------------------------------------------
我也是查找gprmc信息,c3-370模块,因为其中只有这里会出现‘c’,所以扫描到‘c’才开始将数据装入缓冲区。
gprmc信息格式非常规整,所以用结构体去筛选信息:
struct gps_info{
unsigned char note0;
unsigned char time; //时间
......
-----------------------------------------------------------------------
我收到'R'之后就连存50个收到字符进BUFFER,然后读特定位是不是“A”,是的话定位成功。要什么就用指针去BUFFER读什么。 回复【49楼】fsclub绿林好汉
-----------------------------------------------------------------------
我的也一样啊,在主程序中,判断RCV.GI.DAV的值是V还是A就行了。 请教楼上几位,我将NEMA语句过滤成功后得到"RMC,063610.000,A,2421.7385,N,10233.3632,E,0.00,,170810,,,A*76"这样的语句,请教如何将其赋值给结构体呢?我使用for语句分段循环赋值,结果很不理想.谢谢.我写的程序如下.
#include <stc12c5a60s2.h>
#include <uinit5a60.h>
#include <stdio.h>
#include <stdlib.h>
#include "N5110.h"
#define delay_time 25767
#define uchar unsigned char
#define uint unsigned int
unsigned char flag,counter,mark=0,a;
uchar xdata cache;
void main()
{
uchar time;
uchar weidu;
uchar jingdu;
uchar i=0,c,h,e;
LCD_init(); //初始化液晶
LCD_clear();
uinit();
while(1)
{
if(flag==1&counter==1&mark==0)
{
flag=0;
cache=SBUF;
i++;
}
if(mark==1)
{
mark=0;
h=0;
i=0;
ES=0;
for(c=4;c<15;c++)
{
time=cache;
h++;
}
h=0;
for(c=17;c<26;c++)
{
weidu=cache;
h++;
}
h=0;
for(c=28;c<40;c++)
{
jingdu=cache;
h++;
}
h=0;
//从此往下为串口输出
for(e=0;e<11;e++)
{
SBUF=time;
while(!TI);
TI=0;
}
SBUF=0x0a;
while(!TI);
TI=0;
for(e=0;e<9;e++)
{
SBUF=weidu;
while(!TI);
TI=0;
}
SBUF=0x0a;
while(!TI);
TI=0;
for(e=0;e<12;e++)
{
SBUF=jingdu;
while(!TI);
TI=0;
}
SBUF=0x0a;
while(!TI);
TI=0;
ES=1;
// LCD_write_english_string(0,0,"GPS ");
//LCD_clear();
LCD_write_english_string(0,0,time);//输出time到5110
// LCD_write_english_string(0,2,"N");
// LCD_write_english_string(0,3,"L");
// LCD_write_english_string(1,4,"______________");
// LCD_write_english_string(1,5,"123ABE");
}
}
}
void ser() interrupt 4
{
RI=0;
a=SBUF;
flag=1;
if(a==0x52) //过滤R
{
counter=1;
}
if(counter==1&a==0x0a)
{
counter=0;
mark=1;
}
}
我将time值输出到5110液晶上,有一些乱码,而且从乱码当中竟然可以看到很多不应该有的值,比如",E,0.00,,170810,,,A*76 "都显示出来,但是time的长度最多只有10啊!?.如果在液晶上输出"jingdu"却又是正常的,如果输出"weidu"也乱码,真是百思不得其解.
在串口输出的结果为
063610.000,2421.7385,10233.3632,
063610.000,2421.7385,10233.3632,
063610.000,2421.7385,10233.3632,
063610.000,2421.7385,10233.3632,
很奇怪,这三个值之间的换行符没有了
真是不知道为什么,我甚至怀疑我的程序是不是到处是错误.
.............................................................分割线...........................................................
后来发现问题出'\0'上,由于我没有将逗号换成\0,导致5110的驱动不断将指针加1,当超过数组的边界后就全是乱码了.顺便请教\0的十六进制是0x0a吗? 51L:在串口接收字符的中断里就要对结构体字符串赋值,但前提是要找到开始。我用的结构体中NOTEx表示的是字符串中存在的逗号。 当信息不完整的时候,这样提取数据好像不理想啊
比如GPS还没定位的时候,输出都是一串逗号,没数据 回复【52楼】XA144F
-----------------------------------------------------------------------
请问用什么函数能对结构体整体赋值呢?比如对你的这个结构体赋值,难道要一个个赋值吗?不可以直接将过滤出来的RMC语句整体赋给结构体吗?谢谢.
struct GPS_INFO{
unsigned char NOTE0;
unsigned char TIME; //时间
unsigned char NOTE1;
unsigned char DAV; //信息有效
unsigned char NOTE2;
unsigned char Latitude; //纬度
unsigned char NOTE3;
unsigned char NS; //NS标识
unsigned char NOTE4;
unsigned char Longitude; //经度
unsigned char NOTE5;
unsigned char EW; //EW标识
unsigned char NOTE6; //-------------------------
unsigned char SPD; //速度
unsigned char NOTE7;
unsigned char DIR; //速度
unsigned char NOTE8;
unsigned char DATE; //时间
unsigned char OTH; //其他
}; 54L:看清楚:我是用了共用体:
union GPS_DATA{
unsigned char rx_buffer1;
struct GPS_INFO GI;
};
union GPS_DATA RCV;
就像压面条一样,面团(rx_buffer1[])压入机器(共用体)之后,出来的是一根根的面条(结构体中的数据)。 这是我的解析函数
typedef struct
{
INT8U Block;
INT8U BlockIndex;
INT8U UTCTime; // hhmmss.mmm
INT8U Status; // A- 有效定位 V-无效定位
INT8U Latitude; // ddmm.mmmm
INT8U NS; // N/S
INT8U Longitude; // dddmm.mmmm
INT8U EW; // E/W
INT8U Speed; // 速率000.0~999.9节
INT8U Course; // 航向000.0~359.9度
INT8U UTCDate; // ddmmyy
}stru_GPSRMC;
INT8U GPRMC_Receive(void)
{
INT8U buf;
INT8U sbuf;
INT8U *ptr;
INT16U index;
memset(&GPS_RMC_Data, 0x00, sizeof(GPS_RMC_Data));// 每次处理前将结构体清零
index = Find_Str(Uart1_RcvBuf, "$GPRMC,");
if(index)
{
ptr = Uart1_RcvBuf+ index + 6;
do
{
sbuf = *ptr++;
switch(sbuf)
{
case ',': // 接收到数据段分隔符','
GPS_RMC_Data.Block++; // 数据段标志
GPS_RMC_Data.BlockIndex = 0; // 数据段数据计数指针
break;
default: // 接收到数据
switch(GPS_RMC_Data.Block)
{
case 0:GPS_RMC_Data.UTCTime = sbuf;break;
case 1:GPS_RMC_Data.Status = sbuf; break;
case 2:GPS_RMC_Data.Latitude = sbuf; break;
case 3:GPS_RMC_Data.NS = sbuf; break;
case 4:GPS_RMC_Data.Longitude = sbuf;break;
case 5:GPS_RMC_Data.EW = sbuf; break;
case 6:GPS_RMC_Data.Speed = sbuf; break;
case 7:GPS_RMC_Data.Course = sbuf; break;
case 8:GPS_RMC_Data.UTCDate = sbuf;break;
default: break;
}
GPS_RMC_Data.BlockIndex++;
break;
}
}while(sbuf != '*');
memset(buf, 0x00, sizeof(buf));
buf = GPS_RMC_Data.NS;
buf = ' ';
buf = ' ';
memcpy(buf + 3, &GPS_RMC_Data.Latitude, 9);
LCD_ShowString(8, 20, buf, 0);
memset(buf, 0x00, sizeof(buf));
buf = GPS_RMC_Data.EW;
buf = ' ';
memcpy(buf + 2, &GPS_RMC_Data.Longitude, 10);
LCD_ShowString(120, 20, buf, 0);
memset(buf, 0x00, sizeof(buf));
buf = 'T';
buf = ' ';
memcpy(buf + 2, &GPS_RMC_Data.UTCTime, 10);
LCD_ShowString(8, 40, buf, 0);
/*
memset(buf, 0x00, sizeof(buf));
buf = 'S';
buf = ' ';
memcpy(buf + 2, &GPS_RMC_Data.Speed, 5);
LCD_ShowString(8,100, buf, 0);
memset(buf, 0x00, sizeof(buf));
buf = 'C';
buf = ' ';
memcpy(buf + 2, &GPS_RMC_Data.Course, 5);
LCD_ShowString(8,120, buf, 0);
*/
}
return 0;
} ... mark,转载:如何利用strstr和sscanf解析GPS信息 markmarkmark
有没有人英飞凌C166的板子做过GPS C语言的好东东 杰西GPS 太好了。。。 C语言的好东东,学习了! 好,太好了~~~ 学习 仔细看看。 标记 灰常好! mark, 标记下慢慢看 好方法,标记下,留着以后用。不过在不支持浮点运算的CPU里还是尽量避免浮点的好 这里是GPS数据算法学习的一贴。 mark,如何利用strstr和sscanf解析GPS信息 学习一下 马克,好资料学习了! 好贴!!!!! 准备弄GPS协议解释.标记一下. 很好,学习了。 这个还真是处理方便,以前一直用的呆板的处理方法! 基本不用printf个scanf家族的函数。我是串口接受数据,扔进fifo,主程序从fifo中读取数据,当读到$时开始往buf中写数据,读到回车时结束,然后校验,正确后将指针传到解析函数中解析。 好贴啊,标记! guoduan mark goodsssssss
mark,转载:如何利用strstr和sscanf解析GPS信息,,............好贴,GPS数据算法的精华总结 MARK, 字符串处理函数,sscanf,strstr,解析gps数据 {:smile:}{:smile:}谢谢分享。 学习了 高手啊!谢谢分享! 必须mark一下。 很详细的解释
页:
[1]