这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 嵌入式开发 » MCU » 利用strstr和sscanf解析GPS信息

共1条 1/1 1 跳转至

利用strstr和sscanf解析GPS信息

助工
2014-10-28 19:39:45     打赏
常用NMEA-0183语句字段定义解释

    NMEA协议是为了在不同的GPS(全球定位系统)导航设备中建立统一的BTCM(海事无线电技术委员会)标准,由美国国家海洋电子协会(NMEA-The National Marine Electronics Associa-tion)制定的一套通讯协议。GPS接收机根据NMEA-0183协议的标准规范,将位置、速度等信息通过串口传送到PC机、PDA等设备。


 


    NMEA-0183协议是GPS接收机应当遵守的标准协议,也是目前GPS接收机上使用最广泛的协议,大多数常见的GPS接收机、GPS数据处理软件、导航软件都遵守或者至少兼容这个协议。


 


    不过,也有少数厂商的设备使用自行约定的协议比如GARMIN的GPS设备(部分GARMIN设备也可以输出兼容NMEA-0183协议的数据)。软件方面,我们熟知的Google Earth目前也不支持NMEA-0183协议,但Google Earth已经声明会尽快实现对NMEA-0183协议的兼容。呵呵,除非你确实强壮到可以和工业标准分庭抗礼,否则你就得服从工业标准。


 


NMEA-0183协议定义的语句非常多,但是常用的或者说兼容性最广的语句只有$GPGGA、$GPGSA、$GPGSV、$GPRMC、$GPVTG、$GPGLL等。下面给出这些常用NMEA-0183语句的字段定义解释。


 


 


 


$GPRMC


 


例:$GPRMC,024813.640,A,3158.4608,N,11848.3737,E,10.05,324.27,150706,,,A*50


 


字段0:$GPRMC,语句ID,表明该语句为Recommended Minimum Specific GPS/TRANSIT Data(RMC)推荐最小定位信息


 


字段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:校验值


 


 


 


$GPVTG


 


例:$GPVTG,89.68,T,,M,0.00,N,0.0,K*5F


 


字段0:$GPVTG,语句ID,表明该语句为Track Made Good and Ground Speed(VTG)地面速度信息


 


字段1:运动角度,000 - 359,(前导位数不足则补0)


 


字段2:T=真北参照系


 


字段3:运动角度,000 - 359,(前导位数不足则补0)


 


字段4:M=磁北参照系


 


字段5:水平运动速度(0.00)(前导位数不足则补0)


 


字段6:N=节,Knots


 


字段7:水平运动速度(0.00)(前导位数不足则补0)


 


字段8:K=公里/时,km/h


 


字段9:校验值


 


 


 


$GPGLL


 


例:$GPGLL,4250.5589,S,14718.5084,E,092204.999,A*2D


 


字段0:$GPGLL,语句ID,表明该语句为Geographic Position(GLL)地理定位信息


 


字段1:纬度ddmm.mmmm,度分格式(前导位数不足则补0)


 


字段2:纬度N(北纬)或S(南纬)


 


字段3:经度dddmm.mmmm,度分格式(前导位数不足则补0)


 


字段4:经度E(东经)或W(西经)


 


字段5:UTC时间,hhmmss.sss格式


 


字段6:状态,A=定位,V=未定位


 


字段7:校验值


利用strstr和sscanf解析GPS信息


0
推荐

 


作者:杨硕,华清远见嵌入式学院讲师。


考察C程序员是否合格的一个重要标准就是看他操作字符串的能力,一个合格的C程序员应该可以熟练的对字符串进行拆分、组合、格式转换以及搜索定位,从一堆数据中提取出有效信息。


比如说我们要做一个GPS导航的项目,需要读取GPS模块以ASCII码的形式发送过来的数据,然后对这些数据进行处理,提取我们需要的信息。这就涉及到很多操作字符串的问题。下面就以此为例,利用strstr函数和sscanf函数解析GPS数据。


GPS输出的数据格式如下:
$GPGGA,121252.000,3937.3032,N,11611.6046,E,1,05,2.0,45.9,M,-5.7,M,,0000*77 
$GPRMC,121252.000,A,3958.3032,N,11629.6046,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.6046,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之间的所有字符,即只要遇到a到z之间的任意字符,转换立刻停止。比如:
sscanf(“abcdefABCDEF”, “%[^A-Z]”, str);
printf(“%s\n”, str);
result is: abcdef
%[^A-Z]这样的匹配格式为我们取遇到大写字母为止的字符串。利用这种匹配方式,我们就可以灵活的操作字符串,得到我们想要的结果。


现在我们需要从下面的字符串中提取有效信息:
$GPRMC,121252.000,A,3958.3032,N,11629.6046,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[BUF_SIZE];
        char status;
        float latitude_value;
        char latitude;
        float longtitude_value;
        char longtitude;
        float speed;
        float azimuth_angle;
        char utc_data[BUF_SIZE];
}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函数,可以让我们可以很高效的处理字符串。 
---------------------------------------------------------------------------------------------------------------




$GPRMC,053322.682,A,2502.6538,N,12121.4838,E,0.00,315.00,080905,,,A*6F

這一個command是GPS Recommanded GNSS data

053322.682


是UTC Time : 格式是hhmmss.sss所以是5:33:22.682

A


代表data是valid (如果找不到衛星,就會是V)

2502.6538


是緯度,格式是degree * 100 + minutes。但是minutes是100進位,所以要/100 * 60轉為degree,轉換後就是25'02'39.228''.

N


是代表緯度是北緯

12121.4838


是經度,格式是degree * 100 + minutes。minutes一樣要做/100*60的轉換,轉換後就是121'21'29.02''.

E


代表經度是東經

0.00


是速度,因為GPS天線沒動,所以是0.00

315.00


是方向

080905


是目前的日期,格式是ddmmyy,所以是05年9月8日

- -


接著有兩個欄位沒有用,所以空著

A


是Autonomous ?

*6F


是checksum

*** 所以用google earth就知道我把天線放在哪了****









格式是:
1.一律以 $ 符號開頭
2.$ 後是Message id. 5個ASCII Code.
3.一連串以','分開的欄位.
4.Checksum,checkum以'*'開始,後面是兩個ASCII code.
5.

$GPGSV,1,1,02,14,,,37,25,,,46,,,,,,,,*7F

$GPGSV : Satellites in View

Message ID : $GPGSV
Number of Messages : 1
Message number : 1
Satellites in view : 02 目看到的衛星數
Satellite Id : 14 Satellite vehicle 以下是第一個看到的衛星,編號14
Elevation : - Elevation of satellite in degree
Azimuth : - Azimuth of satellite in defree
SNR : 37 Signal to Noise ration in dbHz
Satellite id : 25 Satellite vehicle,以下是第二個看到的衛星,邊號25






NMEA Protocol中checksum的算法:
不包含開頭的'$',一直計算到'*'之前。一個byte一個byte的作XOR.

data++;   // skip the heading '$' mark
sum = *data;
while(*data!='*') {
sum ^= *data;
data++;
}


所以可以知道,連command間的','符號也加入計算。






一般NMEA的report data中,用






有些擴充協定,用來設定,例如
$PNMRX103, NMEA report rate control
可以指定NMEA report資料的頻率
GGA,GLL,GSA,GSV,RMC,VTG,ZDA,ALL
例如:
$PNMRX103,ALL,0*1A
所有的report都停止。
$PNMRX103,RMC,2*02
每2 sec送出一次RMC report






$PNMRX100 設定baud rate
$PNMRX100,0,4800,0*48
設定
Protocol : 0 : NMEA Mode, 1: Bindary Mode (不要用這一個)
baud rate: 4800 其他可以設1200,2400,4800,9600,19200,38400,57600.
Parity : 0 : None, 其他1 2代表Odd, 2代表 Even.
所以上面的example代表:使用NMEA Mode, 4800, None Parity






這些設定在reset後都消失......






雖然Manual中說明support message有GGA,GLL,GSA,GSV,RMC,VTG,ZDA但是用ALL command開啟後,發現只有report GGA,GSA,RMC,VTG.


共1条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]