做为现在的物联网行业,手持设备中,缺少不了的就是GPS定位功能。GPS模块和STM32的串口进行通信,将GPS的数据发送给M3的串口,由M3进行GPS协议的解码。解析出来后保存在响应的结构体中。在进行显示。
这里分别介绍2中解析协议的方法,第一种就是自己写解析协议函数,第二种便是采用别人写好的GPS解析协议库:NMEALIB库,将这个库移植到M3中,直接调用API函数,就可以解析出GPS信息,同样的也保存在一个结构体中。
下面分析一下这两种解析协议的算法,第一种,采用的是正点原子写的GPS解析算法(感谢原子哥)
u8 NMEA_Comma_Pos(u8 *buf,u8 cx){ u8 *p=buf; while(cx) { if(*buf=='*'||*buf<' '||*buf>'z')return 0XFF; if(*buf==',')cx--; buf++; } return buf-p; }
从GPS中得到的一串数据是这样的:GPRMC,083559.00,A,4717.11437,N,00833.91522,E,0.004,77.52,091202,,,A∗57因此,我们可以调用这个函数,得到第几个逗号所距离第一个字符的位置,例如:NMEACommaPos(buf,2),我们的到的是,第二个逗号距离的位置,也就是17
u32 NMEA_Pow(u8 m,u8 n){ u32 result=1; while(n--)result*=m; return result;}
这个就不用多说了,都看的懂,
int NMEA_Str2num(u8 *buf,u8*dx){ u8 *p=buf; u32 ires=0,fres=0; u8 ilen=0,flen=0,i; u8 mask=0; int res; while(1) { if(*p=='-'){mask|=0X02;p++;} if(*p==','||(*p=='*'))break; if(*p=='.'){mask|=0X01;p++;} else if(*p>'9'||(*p<'0')) { ilen=0; flen=0; break; } if(mask&0X01)flen++; else ilen++; p++; } if(mask&0X02)buf++; for(i=0;i<ilen;i++) { ires+=NMEA_Pow(10,ilen-1-i)*(buf[i]-'0'); } if(flen>5)flen=5; *dx=flen; for(i=0;i<flen;i++) { fres+=NMEA_Pow(10,flen-1-i)*(buf[ilen+1+i]-'0'); } res=ires*NMEA_Pow(10,flen)+fres; if(mask&0X02)res=-res; return res;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
这个函数便是将两个逗号之间的字符串数字,变成整数,既将字符串“235”变成int(整型)数字,235
void NMEA_GPGSV_Analysis(nmea_msg *gpsx,u8 *buf){ u8 *p,*p1,dx; u8 len,i,j,slx=0; u8 posx; p=buf; p1=(u8*)strstr((const char *)p,"$GPGSV"); len=p1[7]-'0'; posx=NMEA_Comma_Pos(p1,3); if(posx!=0XFF)gpsx->svnum=NMEA_Str2num(p1+posx,&dx); for(i=0;i<len;i++) { p1=(u8*)strstr((const char *)p,"$GPGSV"); for(j=0;j<4;j++) { posx=NMEA_Comma_Pos(p1,4+j*4); if(posx!=0XFF)gpsx->slmsg[slx].num=NMEA_Str2num(p1+posx,&dx); else break; posx=NMEA_Comma_Pos(p1,5+j*4); if(posx!=0XFF)gpsx->slmsg[slx].eledeg=NMEA_Str2num(p1+posx,&dx); else break; posx=NMEA_Comma_Pos(p1,6+j*4); if(posx!=0XFF)gpsx->slmsg[slx].azideg=NMEA_Str2num(p1+posx,&dx); else break; posx=NMEA_Comma_Pos(p1,7+j*4); if(posx!=0XFF)gpsx->slmsg[slx].sn=NMEA_Str2num(p1+posx,&dx); else break; slx++; } p=p1+1; } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
这个便是解析GPGSV信息,GPGSV协议如下:
void NMEA_GPGGA_Analysis(nmea_msg *gpsx,u8 *buf){ u8 *p1,dx; u8 posx; p1=(u8*)strstr((const char *)buf,"$GPGGA"); posx=NMEA_Comma_Pos(p1,6); if(posx!=0XFF)gpsx->gpssta=NMEA_Str2num(p1+posx,&dx); posx=NMEA_Comma_Pos(p1,7); if(posx!=0XFF)gpsx->posslnum=NMEA_Str2num(p1+posx,&dx); posx=NMEA_Comma_Pos(p1,9); if(posx!=0XFF)gpsx->altitude=NMEA_Str2num(p1+posx,&dx); }
这个是解析GPGGA信息,GPGGA协议如下:
void NMEA_GPGSA_Analysis(nmea_msg *gpsx,u8 *buf){ u8 *p1,dx; u8 posx; u8 i; p1=(u8*)strstr((const char *)buf,"$GPGSA"); posx=NMEA_Comma_Pos(p1,2); if(posx!=0XFF)gpsx->fixmode=NMEA_Str2num(p1+posx,&dx); for(i=0;i<12;i++) { posx=NMEA_Comma_Pos(p1,3+i); if(posx!=0XFF)gpsx->possl[i]=NMEA_Str2num(p1+posx,&dx); else break; } posx=NMEA_Comma_Pos(p1,15); if(posx!=0XFF)gpsx->pdop=NMEA_Str2num(p1+posx,&dx); posx=NMEA_Comma_Pos(p1,16); if(posx!=0XFF)gpsx->hdop=NMEA_Str2num(p1+posx,&dx); posx=NMEA_Comma_Pos(p1,17); if(posx!=0XFF)gpsx->vdop=NMEA_Str2num(p1+posx,&dx); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
这个是解析GPGSA信息,GPGSA协议定义如下:
接下来就是我们通常要用到的一个协议了:GPRMC信息
void NMEA_GPRMC_Analysis(nmea_msg *gpsx,u8 *buf){ u8 *p1,dx; u8 posx; u32 temp; float rs; p1=(u8*)strstr((const char *)buf,"GPRMC"); posx=NMEA_Comma_Pos(p1,1); if(posx!=0XFF) { temp=NMEA_Str2num(p1+posx,&dx)/NMEA_Pow(10,dx); gpsx->utc.hour=temp/10000; gpsx->utc.min=(temp/100)%100; gpsx->utc.sec=temp%100; } posx=NMEA_Comma_Pos(p1,3); if(posx!=0XFF) { temp=NMEA_Str2num(p1+posx,&dx); gpsx->latitude=temp/NMEA_Pow(10,dx+2); rs=temp%NMEA_Pow(10,dx+2); gpsx->latitude=gpsx->latitude*NMEA_Pow(10,5)+(rs*NMEA_Pow(10,5-dx))/60; } posx=NMEA_Comma_Pos(p1,4); if(posx!=0XFF)gpsx->nshemi=*(p1+posx); posx=NMEA_Comma_Pos(p1,5); if(posx!=0XFF) { temp=NMEA_Str2num(p1+posx,&dx); gpsx->longitude=temp/NMEA_Pow(10,dx+2); rs=temp%NMEA_Pow(10,dx+2); gpsx->longitude=gpsx->longitude*NMEA_Pow(10,5)+(rs*NMEA_Pow(10,5-dx))/60; } posx=NMEA_Comma_Pos(p1,6); if(posx!=0XFF)gpsx->ewhemi=*(p1+posx); posx=NMEA_Comma_Pos(p1,9); if(posx!=0XFF) { temp=NMEA_Str2num(p1+posx,&dx); gpsx->utc.date=temp/10000; gpsx->utc.month=(temp/100)%100; gpsx->utc.year=2000+temp%100; } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
GPRMC协议如下:
void NMEA_GPVTG_Analysis(nmea_msg *gpsx,u8 *buf){ u8 *p1,dx; u8 posx; p1=(u8*)strstr((const char *)buf,"$GPVTG"); posx=NMEA_Comma_Pos(p1,7); if(posx!=0XFF) { gpsx->speed=NMEA_Str2num(p1+posx,&dx); if(dx<3)gpsx->speed*=NMEA_Pow(10,3-dx); }}
这个是GPVTG信息解析,协议如下:
到这里,一些常用的,和我们需要的都解析出来了,
注意:这里并不是每条协议都解析,解析的是我们需要什么解析什么,,当然在实际项目中要根据自己的需求解析。
GPS信息我们是通过串口3中断接收,将接收到的数据放在一个BUF中,
vu16 USART3_RX_STA=0; void USART3_IRQHandler(void){ u8 res; if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET) { res =USART_ReceiveData(USART3); if((USART3_RX_STA&(1<<15))==0) { if(USART3_RX_STA<USART3_MAX_RECV_LEN) { TIM_SetCounter(TIM7,0); if(USART3_RX_STA==0) { TIM_Cmd(TIM7,ENABLE); } USART3_RX_BUF[USART3_RX_STA++]=res; }else { USART3_RX_STA|=1<<15; } } } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
USART3_RX_STA是原子自己定义的一个最高位标志位,当数据接收完成时,USART3_RX_STA|=1<<15
将最高位标志位置1,
这里便是定义了解析后数据保存的结构体:
__packed typedef struct { u8 num; u8 eledeg; u16 azideg; u8 sn; }nmea_slmsg; __packed typedef struct { u16 year; u8 month; u8 date; u8 hour; u8 min; u8 sec; }nmea_utc_time; __packed typedef struct { u8 svnum; nmea_slmsg slmsg[12]; nmea_utc_time utc; u32 latitude; u8 nshemi; u32 longitude; u8 ewhemi; u8 gpssta; u8 posslnum; u8 possl[12]; u8 fixmode; u16 pdop; u16 hdop; u16 vdop; int altitude; u16 speed; }nmea_msg;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
到这里,采用第一种方式解析协议已经分析完了,接下来就是采用NMEALIB库解析协议,
了解了NMEA格式有之后,我们就可以编写相应的解码程序了,而程序员Tim (xtimor@gmail.com)提供了一个非常完善的NMEA解码库,在以下网址可以下载到:http://nmea.sourceforge.net/ ,直接使用该解码库,可以避免重复发明轮子的工作。在野火提供的GPS模块资料的“NMEA0183解码库源码”文件夹中也包含了该解码库的源码,野火提供的STM32程序就是使用该库来解码NMEA语句的。
该解码库目前最新为0.5.3版本,它使用纯C语言编写,支持windows、winCE 、UNIX平台,支持解析GPGGA,GPGSA,GPGSV,GPRMC,GPVTG这五种语句(这五种语句已经提供足够多的GPS信息),解析得的GPS数据信息以结构体存储,附加了地理学相关功能,可支持导航等数据工作,除了解析NMEA语句,它还可以根据随机数产生NMEA语句,方便模拟。
将nmealib库中的src和include这两个文件夹复制到工程,在添加进工程中,包含编译的头文件,结果如下:
(这里采用的是野火所提供的例程,感谢fire)
利用nmealib解析GPS模块的输出结果大致可以分为三步,
第一步定义和初始化GPS信息结构体和解析载体结构体,
第二步调用nmea_parse函数完成解析工作,
第三步释放解析载体所占用的内存空间。
具体的代码如下注释中包含了代码的分析:
/** * @brief nmea_decode_test 解码GPS模块信息 * @param 无 * @retval 无 利用nmealib解析GPS模块的输出结果大致可以分为三步, 第一步定义和初始化GPS信息结构体和解析载体结构体, 第二步调用nmea_parse函数完成解析工作, 第三步释放解析载体所占用的内存空间。*/int nmea_decode_test(void){ nmeaINFO info; nmeaPARSER parser; uint8_t new_parse=0; nmeaTIME beiJingTime; nmea_property()->trace_func = &trace; nmea_property()->error_func = &error; nmea_zero_INFO(&info); nmea_parser_init(&parser); while(1) { if(GPS_HalfTransferEnd) { nmea_parse(&parser, (const char*)&gps_rbuff[0], HALF_GPS_RBUFF_SIZE, &info); GPS_HalfTransferEnd = 0; new_parse = 1; } else if(GPS_TransferEnd) { nmea_parse(&parser, (const char*)&gps_rbuff[HALF_GPS_RBUFF_SIZE], HALF_GPS_RBUFF_SIZE, &info); GPS_TransferEnd = 0; new_parse =1; } if(new_parse ) { GMTconvert(&info.utc,&beiJingTime,8,1); printf("\r\n时间%d,%d,%d,%d,%d,%d\r\n", beiJingTime.year+1900, beiJingTime.mon+1,beiJingTime.day,beiJingTime.hour,beiJingTime.min,beiJingTime.sec); printf("\r\n纬度:%f,经度%f\r\n",info.lat,info.lon); printf("\r\n正在使用的卫星:%d,可见卫星:%d",info.satinfo.inuse,info.satinfo.inview); printf("\r\n海拔高度:%f 米 ", info.elv); printf("\r\n速度:%f km/h ", info.speed); printf("\r\n航向:%f 度", info.direction); new_parse = 0; } } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
保存解析后的结构体:
NMEA解码库良好的封装特性使我们无需关注更深入的内部实现,只需要再了解一下nmeaINFO数据结构即可,所有GPS解码得到的结果都存储在这个结构中
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; /**< Hundredth part of second - [0,99] */} nmeaTIME;typedef struct _nmeaINFO{ int smask; /**< Mask specifying types of packages from which data have been obtained */ nmeaTIME utc; /**< UTC of position */ int sig; /**< GPS quality indicator (0 = Invalid; 1 = Fix; 2 = Differential, 3 = Sensitive) */ int fix; /**< Operating mode, used for navigation (1 = Fix not available; 2 = 2D; 3 = 3D) */ double PDOP; /**< Position Dilution Of Precision */ double HDOP; /**< Horizontal Dilution Of Precision */ double VDOP; /**< Vertical Dilution Of Precision */ double lat; /**< Latitude in NDEG - +/-[degree][min].[sec/60] */ double lon; /**< Longitude in NDEG - +/-[degree][min].[sec/60] */ double elv; /**< Antenna altitude above/below mean sea level (geoid) in meters */ double speed; /**< Speed over the ground in kilometers/hour */ double direction; /**< Track angle in degrees True */ double declination; /**< Magnetic variation degrees (Easterly var. subtracts from true course) */ nmeaSATINFO satinfo; /**< Satellites information */} nmeaINFO;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
结构体的具体含义,
typedef struct _nmeaPARSER{ void *top_node; void *end_node; unsigned char *buffer; int buff_size; int buff_use;} nmeaPARSER;
可以看到,nmeaPARSER是一个链表,在解码时,NMEA库会把输入的GPS原始数据压入到nmeaPARSER结构的链表中,便于对数据管理及解码。在使用该结构前,我们调用了nmea_parser_init函数分配动态空间,而解码结束时,调用了nmea_parser_destroy函数释放分配的空间
当然最重要的还是要:分配堆栈空间
由于NMEA解码库在进行解码时需要动态分配较大的堆空间,所以我们需要在STM32的启动文件startup_stm32f10x_hd.s文件中对堆空间进行修改,本工程中设置的堆空间大小设置为0x0000 1000,
Stack_Size EQU 0x00000400 AREA STACK, NOINIT, READWRITE, ALIGN=3Stack_Mem SPACE Stack_Size__initial_sp; <h> Heap Configuration; <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>; </h>Heap_Size EQU 0x00001000 AREA HEAP, NOINIT, READWRITE, ALIGN=3__heap_baseHeap_Mem SPACE Heap_Size__heap_limit PRESERVE8 THUMB
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
当然,这里也是通过串口接收数据保存在这个数组中,
uint8_t gps_rbuff[GPS_RBUFF_SIZE];
详情了解nmealib库的可以参考这个博客:
http://blog.csdn.net/xukai871105/article/details/12834421
到这里,GPS协议的解析相信你应该懂了不少,
0 0