08、GPS 模块应用
一、GPS 工作原理
GPS(全球定位系统)是通过卫星提供精准定位和时间服务的系统。它由太空中的卫星群和地面控制中心共同运作。
工作原理很简单:GPS设备通过接收至少四颗卫星的信号,根据信号传输时间计算出与每颗卫星的距离,再通过这些距离数据综合确定自身所在位置。卫星上装有超精准的原子钟,设备能据此同步时间,误差不到百万分之一秒。
GPS应用非常广泛,比如汽车导航、手机定位、飞机航线规划、灾害救援定位等。它能帮你快速找到目的地,或在紧急情况下告知救援人员具体位置。
需要注意的是:
信号容易被遮挡:高楼、隧道或室内可能影响定位精度
美国拥有系统控制权,理论上可以暂停服务
其他国家也有同类系统:
- 俄罗斯:格洛纳斯(GLONASS)
- 欧盟:伽利略(Galileo)
- 中国:北斗导航系统
简单来说,GPS就像天上24小时工作的"定位管家",但遇到信号遮挡时会"犯困",好在现在多个国家都建了自己的"定位管家团队",让定位服务更可靠。
二、硬件连接
GPS模块通过串口与外部控制器通信,具体参数如下:
- 波特率设置为9600
- 使用1位停止位
- 不启用校验功能
- 不使用流量控制
默认情况下,系统会每秒钟发送一次标准格式的数据包。
三、GPS 模块的数据格式
GPS使用的通用数据格式
目前最常用的GNSS(全球导航卫星系统)数据格式是NMEA0183。它把GPS接收机的二进制定位数据转换成统一的文本格式,方便不同设备读取。这些数据通过逗号分隔的ASCII字符传输,每秒发送一次,长度通常在30到100个字符之间。
NMEA0183主要面向民用设备(如手机、车载导航),与专业设备使用的RTCM等格式不同。它可以通过USB或串口(COM口)稳定传输数据,兼容性很好。
我们重点关注的数据类型:GPGGA
在GPS接收机通过串口发送的数据中,包含多种类型的信息(如定位、速度、卫星状态等),但这里只分析**$GPGGA**(全球定位系统定位数据)。它包含了关键的定位信息,比如经纬度、定位质量、海拔高度等。
GPGGA数据格式详解
GPGGA的完整数据行格式如下:$GPGGA, <1>, <2>, <3>, <4>, <5>, <6>, <7>, <8>, M, <9>, M, <10>, <11>*hh<CR><LF>
每个字段的含义:
UTC时间(如
123519
表示12:35:19)。纬度数值(如
4025.6804
,需转换为度分或十进制)。纬度方向(N或S,表示北纬或南纬)。
经度数值(如
07400.3601
,同上)。经度方向(E或W,表示东经或西经)。
定位模式(如数字1表示GPS定位成功)。
可见卫星数量(0-99颗)。
水平精度(HDOP)(数值越小,定位越准)。
海拔高度(单位:米)。
大地高程基准面高度(单位:米)。
定位质量指标(如00=无定位,01=GPS定位)。
不同前缀代表的卫星系统
GPGGA的开头字母组合表示使用的卫星系统:
- GPGGA:仅GPS卫星
- BDGGA:仅北斗卫星
- GLGGA:仅格洛纳斯(GLONASS)卫星
- GNGGA:多系统联合定位(如GPS+北斗混合)
::: 总结
GPGGA是GPS数据中最核心的部分,能直接获取设备的实时位置、精度和环境信息,适用于大多数导航和定位需求。 :::
四、GPS 使用代码
下面代码是一个使用串口读取GPS数据并解析的示例。它包含了串口的配置、打开和读取数据的功能,以及解析GPRMC格式的GPS数据。
#include <stdio.h> /*标准输入输出定义*/
#include <stdlib.h> /*标准函数库定义*/
#include <unistd.h> /*Unix 标准函数定义*/
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <termios.h>
#define BUFF_SIZE 512
#define MAX_COM_NUM 3 int SectionID=0,i=0;
struct data
{
char GPS_time[20]; //UTC时间
char GPS_sv; //使用卫星
char GPS_wd[12]; //纬度
char GPS_jd[12]; //经度 //
char GPS_warn; //定位警告
char GPS_speed[5]; //速度
char GPS_date[8]; //UTC日期
}GPS_DATA;
int set_com_config(int fd,int baud_rate,int data_bits,char parity,int stop_bits)
{
struct termios new_cfg,old_cfg;
int speed;
//保存并测试现有串口参数设置,在这里如果串口号出错,会有相关的出错信息
if(tcgetattr(fd,&old_cfg)!=0)
{
perror("tcgetattr");
return -1;
}
tcflush(fd, TCIOFLUSH);
new_cfg=old_cfg;
cfmakeraw(&new_cfg);//配置为原始模式
new_cfg.c_cflag&=~CSIZE;
//设置波特率
switch(baud_rate)
{
case 2400:
{ speed = B2400;
break;
}
case 4800:
{ speed = B4800;
break;
}
case 9600:
{ speed = B9600;
break;
}
case 19200:
{ speed = B19200;
break;
}
case 38400:
{ speed = B38400;
break;
}
case 115200:
{ speed = B115200;
break;
}
}
cfsetispeed(&new_cfg,speed);
cfsetospeed(&new_cfg,speed);
//设置数据位 switch(data_bits)
{
case 7:
{ new_cfg.c_cflag|=CS7;
break;
}
case 8:
{ new_cfg.c_cflag|=CS8;
break;
}
}
//设置停止位
switch(stop_bits)
{
case 1:
{ new_cfg.c_cflag &=~CSTOPB;
break;
}
case 2:
{ new_cfg.c_cflag |=CSTOPB;
break;
}
}
//设置奇偶校验位
switch(parity)
{
case 'o':
case 'O':
{ new_cfg.c_cflag|=(PARODD|PARENB); new_cfg.c_iflag|=(INPCK |ISTRIP);
break;
}
case 'e':
case 'E':
{ new_cfg.c_cflag |=PARENB; new_cfg.c_cflag &=~PARODD; new_cfg.c_iflag |=(INPCK | ISTRIP);
break;
}
case 's':
case 'S':
{ new_cfg.c_cflag &=~PARENB; new_cfg.c_cflag &=~CSTOPB;
break;
}
case 'n':
case 'N':
{ new_cfg.c_cflag &=~PARENB; new_cfg.c_iflag &=~INPCK;
break;
}
}
new_cfg.c_cc[VTIME] =10; new_cfg.c_cc[VMIN] =5;
//处理未接收字符
tcflush(fd,TCIFLUSH);
if((tcsetattr(fd,,&new_cfg))!=0)
{
perror("tcsetattr");
return -1;
}
return 0;
}
//打开串口函数
int open_port(char* com_port)
{
int fd;
char* dev = com_port;
//打开串口 if((fd=open(dev,O_RDWR|O_NOCTTY|O_NDELAY))<0)
{
perror("open serial port");
return -1;
}
//恢复串口为堵塞状态 if(fcntl(fd,F_SETFL,0) <0 )
{
perror("fcntl F_SETFL\n");
return -1;
}
//测试是否为终端设备 if(isatty(STDIN_FILENO)==0)
{
perror("standard input is not a terminal device");
}
return fd;
}
void print_info(void)
{
//打印选择界面,即引导的字符号 printf("Now the receive time is %s\n",GPS_DATA.GPS_time);
printf("The star is %c 3\n",GPS_DATA.GPS_sv);
printf("The earth latitude is :%s\n",GPS_DATA.GPS_wd);
printf("The earth longitude is :%s\n",GPS_DATA.GPS_jd);
printf("The train speed is:%s km/h\n",GPS_DATA.GPS_speed);
printf("The date is:%s\n",GPS_DATA.GPS_date);
}
void GPS_resolve_GPRMC(char data)
{
//$GPRMC,092427.604,V,4002.1531,N,11618.3097,E,0.000,0.00,280812,,E,N*08
if(data==',')
{
++SectionID;
i=0;
}
else
{
switch(SectionID)
{
case 1://02:48:13
GPS_DATA.GPS_time[i++]=data;
if(i==2 || i==5)
{ GPS_DATA.GPS_time[i++]=':';
} GPS_DATA.GPS_time[8]='\0';
break;
case 2:
if(data=='A') GPS_DATA.GPS_sv='>';
else GPS_DATA.GPS_sv='<';
break;
case 3://3158.4608
GPS_DATA.GPS_wd[++i]=data; GPS_DATA.GPS_wd[12]='\0';
break;
case 4:
if(data=='N') G
PS_DATA.GPS_wd[0]='N';
else if(data=='S')
GPS_DATA.GPS_wd[0]='S';
break;
case 5://11848.3737,E
GPS_DATA.GPS_jd[++i]=data;
GPS_DATA.GPS_jd[12]='\0';
break;
case 6:
if(data=='E')
GPS_DATA.GPS_jd[0]='E';
else if(data=='W')
GPS_DATA.GPS_jd[0]='W';
break;
case 7://10.05
GPS_DATA.GPS_speed[i++]=data;
GPS_DATA.GPS_speed[4]='\0';
break;
case 9://15-07-06 -> 06-07-15
GPS_DATA.GPS_date[i++]=data;
if(i==2 || i==5)
{ GPS_DATA.GPS_date[i++]='-';
} GPS_DATA.GPS_date[8]='\0';
break;
}
}
}
void read_data(int fd)
{
char buffer[BUFF_SIZE],dest[1024];
char array[10]="$GPRMC";
int res,i=0,j=0,k;
int data=1,len=0;
memset(dest,0,sizeof(dest));
do
{
memset(buffer,0,sizeof(buffer));
//$GPRMC,024813.640,A,3158.4608,N,11848.3737,E,10.05,324.27,150706,,,A*50
if(res=read(fd,buffer,1)>0)
{
//此处源源不断传入参数,一次读到数据可能为($GPRMC,024),res为读到长度,现在把每一位传入函数处理; strcat(dest,buffer);
if(buffer[0]=='\n')
{ i=0;
if(strncmp(dest,array,6)==0)
{
printf("%s",dest);
len=strlen(dest);
for(k=0;k<len;k++)
{
GPS_resolve_GPRMC(dest[k]);
}
SectionID=0;
print_info();
}
bzero(dest,sizeof(dest));
}
}
}while(1);
close(fd);
}
int main(int argc,char*argv[])
{
int fd=0;
char* HOST_COM_PORT = argv[1];
if(2 != argc){printf("Usage: gps_test [uart port]\r\n");return;}
fd=open_port(HOST_COM_PORT);
if(fd<0)
{
perror("open fail!");
}
printf("open sucess!\n");
if((set_com_config(fd,9600,8,'N',1))<0)
{
perror("set_com_config fail!\n");
}
printf("The received worlds are:\n");
read_data(fd);
return 0;
}
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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <stdlib.h>
/* set_opt(fd,115200,8,'N',1) */
int set_opt(int fd,int nSpeed, int nBits, char nEvent, int nStop)
{
struct termios newtio,oldtio;
if ( tcgetattr( fd,&oldtio) != 0) {
perror("SetupSerial 1");
return -1;
}
bzero( &newtio, sizeof( newtio ) );
newtio.c_cflag |= CLOCAL | CREAD;
newtio.c_cflag &= ~CSIZE;
newtio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); /*Input*/
newtio.c_oflag &= ~OPOST; /*Output*/
switch( nBits )
{
case 7:
newtio.c_cflag |= CS7;
break;
case 8:
newtio.c_cflag |= CS8;
break;
}
switch( nEvent )
{
case 'O':
newtio.c_cflag |= PARENB;
newtio.c_cflag |= PARODD;
newtio.c_iflag |= (INPCK | ISTRIP);
break;
case 'E':
newtio.c_iflag |= (INPCK | ISTRIP);
newtio.c_cflag |= PARENB;
newtio.c_cflag &= ~PARODD;
break;
case 'N':
newtio.c_cflag &= ~PARENB;
break;
}
switch( nSpeed )
{
case 2400:
cfsetispeed(&newtio, B2400);
cfsetospeed(&newtio, B2400);
break;
case 4800:
cfsetispeed(&newtio, B4800);
cfsetospeed(&newtio, B4800);
break;
case 9600:
cfsetispeed(&newtio, B9600);
cfsetospeed(&newtio, B9600);
break;
case 115200:
cfsetispeed(&newtio, B115200);
cfsetospeed(&newtio, B115200);
break;
default:
cfsetispeed(&newtio, B9600);
cfsetospeed(&newtio, B9600);
break;
}
if( nStop == 1 )
newtio.c_cflag &= ~CSTOPB;
else if ( nStop == 2 )
newtio.c_cflag |= CSTOPB;
newtio.c_cc[VMIN] = 1; /* 读数据时的最小字节数: 没读到这些数据我就不返回! */
newtio.c_cc[VTIME] = 0; /* 等待第1个数据的时间:
* 比如VMIN设为10表示至少读到10个数据才返回,
* 但是没有数据总不能一直等吧? 可以设置VTIME(单位是10秒)
* 假设VTIME=1,表示:
* 10秒内一个数据都没有的话就返回
* 如果10秒内至少读到了1个字节,那就继续等待,完全读到VMIN个数据再返回
*/
tcflush(fd,TCIFLUSH);
if((tcsetattr(fd,TCSANOW,&newtio))!=0)
{
perror("com set error");
return -1;
}
//printf("set done!\n");
return 0;
}
int open_port(char *com)
{
int fd;
//fd = open(com, O_RDWR|O_NOCTTY|O_NDELAY);
fd = open(com, O_RDWR|O_NOCTTY);
if (-1 == fd){
return(-1);
}
if(fcntl(fd, F_SETFL, 0)<0) /* 设置串口为阻塞状态*/
{
printf("fcntl failed!\n");
return -1;
}
return fd;
}
int read_gps_raw_data(int fd, char *buf)
{
int i = 0;
int iRet;
char c;
int start = 0;
while (1)
{
iRet = read(fd, &c, 1);
if (iRet == 1)
{
if (c == '$')
start = 1;
if (start)
{
buf[i++] = c;
}
if (c == '\n' || c == '\r')
return 0;
}
else
{
return -1;
}
}
}
/* eg. $GPGGA,082559.00,4005.22599,N,11632.58234,E,1,04,3.08,14.6,M,-5.6,M,,*76"<CR><LF> */
int parse_gps_raw_data(char *buf, char *time, char *lat, char *ns, char *lng, char *ew)
{
char tmp[10];
if (buf[0] != '$')
return -1;
else if (strncmp(buf+3, "GGA", 3) != 0)
return -1;
else if (strstr(buf, ",,,,,"))
{
printf("Place the GPS to open area\n");
return -1;
}
else {
//printf("raw data: %s\n", buf);
sscanf(buf, "%[^,],%[^,],%[^,],%[^,],%[^,],%[^,]", tmp, time, lat, ns, lng, ew);
return 0;
}
}
/*
* ./serial_send_recv <dev>
*/
int main(int argc, char **argv)
{
int fd;
int iRet;
char c;
char buf[1000];
char time[100];
char Lat[100];
char ns[100];
char Lng[100];
char ew[100];
float fLat, fLng;
/* 1. open */
/* 2. setup
* 115200,8N1
* RAW mode
* return data immediately
*/
/* 3. write and read */
if (argc != 2)
{
printf("Usage: \n");
printf("%s </dev/ttySAC1 or other>\n", argv[0]);
return -1;
}
fd = open_port(argv[1]);
if (fd < 0)
{
printf("open %s err!\n", argv[1]);
return -1;
}
iRet = set_opt(fd, 9600, 8, 'N', 1);
if (iRet)
{
printf("set port err!\n");
return -1;
}
while (1)
{
/* eg. $GPGGA,082559.00,4005.22599,N,11632.58234,E,1,04,3.08,14.6,M,-5.6,M,,*76"<CR><LF>*/
/* read line */
iRet = read_gps_raw_data(fd, buf);
/* parse line */
if (iRet == 0)
{
iRet = parse_gps_raw_data(buf, time, Lat, ns, Lng, ew);
}
/* printf */
if (iRet == 0)
{
printf("Time : %s\n", time);
printf("ns : %s\n", ns);
printf("ew : %s\n", ew);
printf("Lat : %s\n", Lat);
printf("Lng : %s\n", Lng);
/* 纬度格式: ddmm.mmmm */
sscanf(Lat+2, "%f", &fLat);
fLat = fLat / 60;
fLat += (Lat[0] - '0')*10 + (Lat[1] - '0');
/* 经度格式: dddmm.mmmm */
sscanf(Lng+3, "%f", &fLng);
fLng = fLng / 60;
fLng += (Lng[0] - '0')*100 + (Lng[1] - '0')*10 + (Lng[2] - '0');
printf("Lng,Lat: %.06f,%.06f\n", fLng, fLat);
}
}
return 0;
}
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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254