《Linux – Linux高级编程 – 第一部分 标准IO及文件IO》第6章 GPS使用

6.1 GPS简介

GPS模块使用的是NMEA-0183 协议,NMEA-0183 是美国国家海洋电子协会(National MarineElectronics Association)所指定的标准规格,这一标准制订所有航海电子仪器间的通讯标准,其中包含传输资料的格式以及传输资料的通讯协议。所以通常情况下,只需要通过串口读取信息,通过字符串解析的方式把需要的数据分离出来就可以得到GPS数据。

在具体的的字符串解析中,实际只需要解析GPGGA、GPRMC两个语句即可获得我们所需要的全部内容,包括经纬度,时间,搜星状态,卫星数量,高度,速度以及其他信号等,不同数据之间在获取的字符串中是使用逗号隔开的,而相对位置固定,因此整体的思路就是读取字符串,通过逗号位置判别对应数据,实现读取分析。

6.2 GPS数据解析

GPS接收机上电后,会自动通过串口或USB口发送NMEA0183格式的数据包,它是一组包含有各种地理位置信息的字符串,字符串格式为:

信息类型[xxx,xxx,xxx,xxx,xxx,xxx,xxx,]

每行开头的字符都是‘$’,接着是信息类型,后面是数据,以逗号分隔开。一行完整的数据如下:

$GPRMC,063102.00,A,2932.293196,N,10636.147385,E,0.0,45.5,250818,2.3,W,A*10

信息类型为:
GPVTG:地面速度信息
GPRMC:推荐最小定位信息
GPGSA:当前卫星信息
GPGGA:GPS定位信息
GPGSV:可见卫星信息

这里我们只解析GPRMC和GPGGA的信息。

 GPRMC数据详解:

$GPRMC,<1>,<2>,<3>,<4>,<5>,<6>,<7>,<8>,<9>,<10>,<11>,<12>*hh

UTC时间,hhmmss(时分秒)格式
定位状态,A=有效定位,V=无效定位
纬度ddmm.mmmm(度分)格式(前面的0也将被传输)
纬度半球N(北半球)或S(南半球)
经度dddmm.mmmm(度分)格式(前面的0也将被传输)
经度半球E(东经)或W(西经)
地面速率( 000.0 ~ 999.9节,前面的0也将被传输)
地面航向(000.0 ~ 359.9度,以真北为参考基准,前面的0也将被传输)
UTC日期,ddmmyy(日月年)格式
磁偏角(000.0 ~ 180.0度,前面的0也将被传输)
磁偏角方向,E(东)或W(西)
模式指示(仅NMEA0183 3.00版本输出,A=自主定位,D=差分,E=估算,N=数据无效)

解析内容:
1.时间,这个是格林威治时间,是世界时间(UTC),我们需要把它转换成北京时间(BTC),BTC和UTC差了8个小时,要在这个时间基础上加8个小时。
2.定位状态,在接收到有效数据前,这个位是‘V’,后面的数据都为空,接到有效数据后,这个位是‘A’,后面才开始有数据。
3.纬度,我们需要把它转换成度分秒的格式,计算方法:
如接收到的纬度是:4546.40891
4546.40891 / 100 = 45.4640891 可以直接读出45度
4546.40891–45 * 100 = 46.40891 可以直接读出46分
46.40891–46 = 0.40891 * 60 = 24.5346 读出24秒
所以纬度是:45度46分24秒。
4.南北纬,这个位有两种值‘N’(北纬)和‘S’(南纬)
5.经度的计算方法和纬度的计算方法一样
6.东西经,这个位有两种值‘E’(东经)和‘W’(西经)
7.速率,这个速率值是 海里/时,单位是节,要把它转换成千米/时,根据:1海里 = 1.85公里,把得到的速率乘以1.85。
8.航向,指的是偏离正北的角度
9.日期,这个日期是准确的,如:200818表示2018年08月20日,这个日期是准确的,不需要转换。

** GPGGA数据详解**
“`
$GPGGA,<1>,<2>,<3>,<4>,<5>,<6>,<7>,<8>,<9>,M,<10>,M,<11>,<12>*xx
“`
$GPGGA:起始引导符及语句格式说明(本句为GPS定位数据);
UTC时间,格式为hhmmss.sss;
纬度,格式为ddmm.mmmm(第一位是零也将传送);
纬度半球,N或S(北纬或南纬)
经度,格式为dddmm.mmmm(第一位零也将传送);
经度半球,E或W(东经或西经)
定位质量指示,0=定位无效,1=定位有效;
使用卫星数量,从00到12(第一个零也将传送)
水平精确度,0.5到99.9
天线离海平面的高度,-9999.9到9999.9米 M 指单位米
大地水准面高度,-9999.9到9999.9米 M 指单位米
差分GPS数据期限(RTCM SC-104),最后设立RTCM传送的秒数量
差分参考基站标号,从0000到1023(首位0也将传送)。

## 6.3 GPS编程
GPS解析主要有两个部分,一个是USB转串口的配置,另外一个就是GPS的解析。下面就一一介绍。
### 6.3.1.串口配置
由于我是通过串口来进行数据传输,也就是把GPS定位的信息,通过串口最后输出到我们的终端设备上。

**【uart_GPS_config.c】**

“`c
/**
******************************************************************************
* @file uart_GPS_config.c
* @author Bruceou
* @version V1.0
* @date 2018.03.10
* @brief 串口设置
******************************************************************************
*/
/**Includes*********************************************************************/
#include "uart_GPS_config.h"

/**
* @brief 串口设置函数
* @param fd
baud_rate
data_bits
parity
stop_bits
* @retval int
*/
int set_GPS_com_config(int fd,int baud_rate,int data_bits, char parity, int stop_bits)
{
struct termios new_cfg;
int speed;

/* 保存并测试现有串口参数设置,在这里如果串口号等出错,会有相关的出错信息 */

if (tcgetattr(fd, &new_cfg) != 0)
{
perror("tcgetattr save");
return -1;
}

//修改控制模式,保证程序不会占用串口
new_cfg.c_cflag |= CLOCAL;
//修改控制模式,使得能够从串口中读取输入数据
new_cfg.c_cflag |= CREAD;
new_cfg.c_oflag &= ~(ONLCR | OCRNL);
new_cfg.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
new_cfg.c_iflag &= ~(ICRNL | INLCR);
new_cfg.c_iflag &= ~(IXON | IXOFF | IXANY);

/* 设置波特率 */
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;
default:
case 115200:
{
speed = B115200;
}
break;
}

cfsetispeed(&new_cfg, speed);//输入波特率
cfsetospeed(&new_cfg, speed);//输出波特率
switch (data_bits) /* 设置数据位 */
{
case 7:
{
new_cfg.c_cflag |= CS7;
}
break;
default:
case 8:
{
new_cfg.c_cflag |= CS8;
}
break;
}

switch (parity) /* 设置奇偶校验位 */
{
default:
case &#039;n&#039;:
case &#039;N&#039;:
{
new_cfg.c_cflag &= ~PARENB;
new_cfg.c_iflag &= ~INPCK;
}
break;
case &#039;o&#039;:
case &#039;O&#039;:
{
new_cfg.c_cflag |= (PARODD | PARENB);
new_cfg.c_iflag |= INPCK;
}
break;
case &#039;e&#039;:
case &#039;E&#039;:
{
new_cfg.c_cflag |= PARENB;
new_cfg.c_cflag &= ~PARODD;
new_cfg.c_iflag |= INPCK;

}
break;
case &#039;s&#039;: /* as no parity */
case &#039;S&#039;:
{
new_cfg.c_cflag &= ~PARENB;
new_cfg.c_cflag &= ~CSTOPB;
}
break;
}
switch (stop_bits) /* 设置停止位 */
{
default:
case 1:
{
new_cfg.c_cflag &= ~CSTOPB;
}
break;
case 2:
{
new_cfg.c_cflag |= CSTOPB;
}
}
//修改输出模式,原始数据输出
new_cfg.c_oflag &= ~OPOST;
new_cfg.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);//我加的
new_cfg.c_lflag &= ~(ISIG | ICANON);

//设置等待时间和最小接收字符
new_cfg.c_cc[VTIME] = 0; /* 读取一个字符等待0*(0/10)s */
new_cfg.c_cc[VMIN] = 1; /* 读取字符的最少个数为0 */

//如果发生数据溢出,接收数据,但是不再读取 刷新收到的数据但是不读
tcflush(fd, TCIFLUSH); /* 处理未接收字符 */
if ((tcsetattr(fd, TCSANOW, &new_cfg)) != 0) /* 激活新配置 */
{
perror("tcsetattr action");
return -1;
}
//printf("serial set success\n");
return 0;
}

/**
* @brief 打开串口函数
* @param com
* @retval int
*/
int open_GPS_port(const char *com_port)
{
int fd;

/*分别为com1,com2, com3对应 ttyS0 ttyS1 ttyS2 */
fd = open( com_port, O_RDWR|O_NOCTTY|O_NDELAY);
if (fd < 0)
{
perror("Can&#039;t Open Serial Port");
return -1;
}
/*恢复串口为阻塞状态*/
if (fcntl(fd,F_SETFL,0)<0)
{
perror("fcntl F_SETFL\n");
}
/*测试是否为终端设备*/
if(isatty(STDIN_FILENO) == 0)
{
perror("standard input is not a terminal device");
}
return fd;
}

/**
* @brief 串口初始化函数
* @param com_port
* @retval int
*/
int init_GPS_port(const char *com_port)
{
int fd;

if ((fd = open_GPS_port(com_port)) < 0 )
{
perror("open_port");
return -1;
}

if(set_GPS_com_config(fd,9600,8,&#039;N&#039;,1) < 0)
{
perror("set_com_config");
return -1;
}
return fd;
}

“`

**【uart_GPS_config.h】**

“`c
#ifndef _UART_GPS_CONFIG_H_
#define _UART_GPS_CONFIG_H_

#include<errno.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include <termios.h>
#include <fcntl.h>
#include <time.h>
#include <unistd.h>
#include <ctype.h>

#define BUFFER_SIZE 36

#define DEVICE_DATA_GPS "/dev/ttyUSB1"

int set_GPS_com_config(int fd,int baud_rate, int data_bits,char parity,int stop_bits);
int open_GPS_port(const char *com_port);
int init_GPS_port(const char *com_port);

#endif

“`

### 6.3.2 GPS数据分析
**【GPS.c】**
“`c
/**
******************************************************************************
* @file GPS.c
* @author Bruceou
* @version V1.0
* @date 2021.03.10
* @brief GPS 解析
******************************************************************************
*/
/**Includes*********************************************************************/
#include "GPS.h"

char *buff = NULL;

GPRMC_t gprmc;
GPGGA_t gpgga;

/**
* @brief GPS解析函数
* @param AT_fd:串口文件描述符
* @ DATA_fd:串口文件描述符
* @retval Nono
*/
void GPS_Analysis(int AT_fd,int DATA_fd)
{
int nread,nwrite;
char send_buff[16];
char recv_buff[512];
char *ptr = NULL;
char AT_Buff[16];
int ret = 0;
#if 0
uint32_t time =0;
uint32_t lat =0 ;
uint32_t lon =0;
uint32_t speed =0;
uint32_t head =0;
uint32_t alt =0;
#endif

//接收数据
while(1)
{
memset(&gprmc, 0, sizeof(gprmc));
memset(&gpgga, 0, sizeof(gpgga));
memset(recv_buff,0,sizeof(recv_buff));
nread = read(DATA_fd,recv_buff,sizeof(recv_buff));

#ifdef DEBUG_GPS
printf("nread=%d,%s\n",nread,recv_buff);
printf("=================2===================\n");
#endif
//strcpy(buff, recv_buff);
//保存数据
ptr = strstr(recv_buff, "$GPRMC");
ret = sscanf(ptr, "$GPRMC,%f,%c,%f,%*c,%f,%*c,%f,%f,%d,%f,%*c,%*c*",
&gprmc.time,&gprmc.state, &gprmc.lat, &gprmc.lon,
&gprmc.speed, &gprmc.head, &gprmc.date,&gprmc.dec);

ptr = strstr(recv_buff, "$GPGGA");
ret = sscanf(ptr, "$GPGGA,%f,%f,N,%f,E,%d,%d,%f,%f,M,%f,M,,*",
&gpgga.time, &gpgga.lat, &gpgga.lon,
&gpgga.state, &gpgga.num, &gpgga.hdop, &gpgga.alt, &gpgga.geoid);
#if 0
time = (int)(gprmc.time*100) % 100 + ((int)gprmc.time % 100) * 100 +\
((int)gprmc.time%10000/100) *60 * 100 + ((int)gprmc.time/10000)* 3600 * 100;

lat = ((int)(gprmc.lat/100) + (gprmc.lat-(int)gprmc.lat/100*100)/60)*10000000;
lon = ((int)(gprmc.lon/100) + (gprmc.lon-(int)gprmc.lon/100*100)/60)*10000000;
speed = gprmc.speed* 1.852;
head = gprmc.head;

alt = gpgga.alt;
#endif
//打印数据
print_GPS_RMC(&gprmc);
print_GPS_GGA(&gpgga);
}
}

/**
* @brief GPS-RMC信息打印
* @param gprmc_data:
* @retval Nono
*/
void print_GPS_RMC(GPRMC_t *gprmc_data)
{
printf(" \n");
printf("===========================================================\n");
printf("== 全球GPS定位导航模块 ==\n");
printf("== Author:BruceOu ==\n");
printf("== Email:bruceoxl@qq.com ==\n");
printf("================RMC信息====================================\n");
printf("===========================================================\n");
printf("== GPS state bit : %c [A:有效状态 V:无效状态] \n",gprmc_data->state);
printf("== Date : 20%02d-%02d-%02d \n",gprmc_data->date%100,(gprmc_data->date%10000)/100,gprmc_data->date/10000);
printf("== 纬度 : 北纬:%d度%d分%d秒 \n",((int)gprmc_data->lat) / 100, (int)(gprmc_data->lat – ((int)gprmc_data->lat / 100 * 100)), (int)(((gprmc_data->lat – ((int)gprmc_data->lat / 100 * 100)) – ((int)gprmc_data->lat – ((int)gprmc_data->lat / 100 * 100))) * 60.0));
printf("== 经度 : 东经:%d度%d分%d秒 \n",((int)gprmc_data->lon) / 100, (int)(gprmc_data->lon – ((int)gprmc_data->lon / 100 * 100)), (int)(((gprmc_data->lon – ((int)gprmc_data->lon / 100 * 100)) – ((int)gprmc_data->lon – ((int)gprmc_data->lon / 100 * 100))) * 60.0));
printf("== 速度 : %.3f m/s \n",gprmc_data->speed * 1.852);
printf("== 航向 : %.3f 度 \n",gprmc_data->head );
printf("===========================================================\n");

}

/**
* @brief GPS-GGA信息打印
* @param gpgga_data:
* @retval Nono
*/
void print_GPS_GGA(GPGGA_t *gpgga_data)
{
printf(" \n");
printf("===========================================================\n");
printf("== 全球GPS定位导航模块 ==\n");
printf("== Author:BruceOu ==\n");
printf("== Email:bruceoxl@qq.com ==\n");
printf("================GGA信息====================================\n");
printf("===========================================================\n");
printf("== 纬度 : 北纬:%d度%d分%d秒 \n",((int)gpgga_data->lat) / 100, (int)(gpgga_data->lat – ((int)gpgga_data->lat / 100 * 100)), (int)(((gpgga_data->lat – ((int)gpgga_data->lat / 100 * 100)) – ((int)gpgga_data->lat – ((int)gpgga_data->lat / 100 * 100))) * 60.0));
printf("== 经度 : 东经:%d度%d分%d秒 \n",((int)gpgga_data->lon) / 100, (int)(gpgga_data->lon – ((int)gpgga_data->lon / 100 * 100)), (int)(((gpgga_data->lon – ((int)gpgga_data->lon / 100 * 100)) – ((int)gpgga_data->lon – ((int)gpgga_data->lon / 100 * 100))) * 60.0));
printf("== 数量 : %d 颗 \n",gpgga_data->num);
printf("== 精度 : %.3f \n",gpgga_data->hdop);
printf("== 海拔 : %.3f m \n",gpgga_data->alt);
printf("===========================================================\n");
}

“`

**【GPS.h】**
“`c
#ifndef _GPS_H_
#define _GPS_H_

#include <termios.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <assert.h>
#include <iconv.h>
#include <errno.h>
#include <math.h>
#include <stdio.h>
#include "uart_GPS_config.h"

#define DEBUG_GPS

typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;

typedef struct _GPRMC
{
float time;//UTC时间,hhmmss(时分秒)格式/* gps定位时间 */
uint8_t state;/*gps状态位*//*A=有效定位,V=无效定位*/
float lat;/*纬度 */
float lon;/* 经度 */
float speed;/*速度 */
float head;/*航向 */
uint32_t date;/*日期*/
float dec;/* 磁偏角 */
uint8_t dd;/*磁偏角方向*/
uint8_t mode;/*GPS模式位*//*A=自主定位,D=差分,E=估算,N=数据无效*/
}GPRMC_t;

typedef struct _GPGGA
{
float time;//UTC时间,hhmmss(时分秒)格式/* gps定位时间 */
float lat;/*纬度 */
float lon;/* 经度 */
uint32_t state;/*gps状态位*//*0=定位无效,1=定位有效;*/
uint32_t num;/*使用卫星数量,从00到12(第一个零也将传送)*/
float hdop;/*水平精确度,0.5到99.9*/
float alt;/*天线离海平面的高度,-9999.9到9999.9米*/
float geoid;
}GPGGA_t;

void GPS_Analysis(int AT_fd,int DATA_fd);
void print_GPS_RMC(GPRMC_t *gprmc_data);
void print_GPS_GGA(GPGGA_t *gpgga_data);

#endif

“`

【注意】
1.由于我直接获取的是格林威治时间即世界时间(UTC),所以要把它转换成北京时间(BTC),也就是在这个时间基础上加8个小时。

2.经纬度,GPRMC返回的纬度数据位ddmm.mmmm格式即度分格式,我们把它转换成常见的度分秒的格式,计算方法:如接收到的纬度是:3029.60430

3029.60430/100=30.2960430可以直接读出30度,
3029.60430–30\*100=29.60430, 可以直接读出29分
(29.60430–29)\*60 =0.60430*60=36.258读出36秒,
所以纬度是:30度29分36秒。

3.GPRMC返回的速率值是海里/时,单位是节,把它转换成千米/时,换算为:1海里=1.85公里,把得到的速率乘以1.85。

4.航向指的是偏离正北的角度。

5.GPRMC的日期格式为:ddmmyy,如:200818表示2018年08月20日,这个日期是准确的,不需要转换。

### 6.3.3主函数打开串口设备读操作

**【main.c】**

“`c
/**
******************************************************************************
* @file main.c
* @author Bruceou
* @version V1.0
* @date 2021.03.010
* @brief
******************************************************************************
*/
/**Includes*********************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include "uart_GPS_config.h"
#include "GPS.h"

#define DEBUG_GPS

int GPS_DATA_fd = -1; //GPS_DATA描述符

/**
* @brief main函数
* @param Nono
* @retval Nono
*/
int main(int argc, char **argv)
{
GPS_DATA_fd = init_GPS_port(DEVICE_DATA_GPS); //初始化DATA GPS

if(GPS_AT_fd < 0)
{
printf("open GPS_DATA_port failed!\n");
}
else
{
printf("init GPS_port success!\n");
GPS_Analysis(GPS_AT_fd,GPS_DATA_fd);
}

return 0;
}

“`

### 6.3.4测试
编译完成后,执行程序,会有下面的主要信息,可以看到GPS的解析数据。

![6YnP00.jpg](https://s3.ax1x.com/2021/03/10/6YnP00.jpg)

![6YnkkT.jpg](https://s3.ax1x.com/2021/03/10/6YnkkT.jpg)

![6YnZp4.jpg](https://s3.ax1x.com/2021/03/10/6YnZp4.jpg)

Related posts

Leave a Comment