26. GPS模块

本章旨在帮助用户正确构建 BH-ATGM332D模块的使用环境,引导用户快速使用该模块。

26.1. BH-ATGM332D 简介

BH-ATGM332D 是野火设计的高性能、低功耗 GPS、北斗双模定位模块。 它采用中科微电子公司的 ATGM332D-5N-31模组方案, 可以通过串口向单片机系统和电脑输出 GPS及北斗定位信息,使用简单方便,其外观见图。

ATGM332D001

ATGM332D001

BH-ATGM332D 模块引脚说明如下:

ATGM332D002

ATGM332D002

BH-ATGM332D 模块的资源如下:

ATGM332D003

ATGM332D003

BH-ATGM332D 模块及其配套的有源天线如下:

ATGM332D004

ATGM332D004

26.2. 测试 BH-ATGM332D

26.2.1. 硬件接线

BH-ATGM332D 模块的硬件资源介绍完毕了,可以在Debian上使用它,我们开发板的左上角有预留了EBF Module 模块接口,直接将 BH-ATGM332D 插在模块接口上即可(需要注意引脚位置)。

如图所示:

ATGM332D005

ATGM332D005

26.2.2. 使能USART3

  1. 开发板串口3默认没有使能,打开/boot/uEnv.txt文件启用串口3设备树插件。

1
dtoverlay=/usr/lib/linux-image-4.19.35-imx6/overlays/imx-fire-uart3.dtbo
  1. 重启开发板。

  2. 重启之后查看dev目录下是否存在串口3设备:

1
 ls /dev/ttymxc2

当存在/dev/ttymxc2的时候表示已经打开串口3设备。

26.2.3. 验证模块是否工作

实现要验证模块是否工作了,我们在将模块插入开发板的 EBF Module 模块接口后,BH-ATGM332D 模块的LED灯会闪烁,如果没有则检查是否正常供电了。

然后在linux的终端中运行以下命令:

1
 cat /dev/ttymxc2

输出了以下内容则表示模块正常工作:

 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
 $GPTXT,01,01,01,ANTENNA OK*35

 $GNGGA,034101.000,2253.72536,N,11350.70190,E,1,22,0.7,48.8,M,0.0,M,,*4C

 $GNGLL,2253.72536,N,11350.70190,E,034101.000,A,A*4A

 $GPGSA,A,3,01,04,07,08,09,11,16,22,27,30,33,34,1.1,0.7,0.8*38

 $BDGSA,A,3,03,06,08,10,11,12,13,16,,,,,1.1,0.7,0.8*26

 $GPGSV,4,1,16,01,60,154,43,04,12,190,23,07,63,308,21,08,41,026,20*74

 $GPGSV,4,2,16,09,21,224,15,11,75,044,31,16,12,089,21,17,09,242,*7F

 $GPGSV,4,3,16,22,12,139,16,23,,,16,27,13,044,24,28,09,300,*41

 $GPGSV,4,4,16,30,30,318,23,33,12,150,13,34,64,095,39,35,63,049,35*7B

 $BDGSV,2,1,08,03,62,188,37,06,19,189,20,08,55,161,37,10,52,337,21*65

 $BDGSV,2,2,08,11,70,264,18,12,46,028,21,13,35,187,38,16,21,195,39*64

 $GNRMC,034101.000,A,2253.72536,N,11350.70190,E,0.00,315.42,220520,,,A*7B

 $GNVTG,315.42,T,,M,0.00,N,0.00,K,A*22

 $GNZDA,034101.000,22,05,2020,00,00*4A

注意看输入的内容 “$GPTXT,01,01,01,ANTENNA OK*35” ,它表示模块正常运行,当然也可能是其他情况,比如:

ATGM332D007

ATGM332D007

图中的三种状态分别为开路(OPEN) 、 SHORT(短路) 及 OK(正常) , 测试时请确保天线处于 OK 状态

注意:如果模块上电后输出的数据长期处于第一种状态(天线开路或者没连接天线),则应考虑转移一下定位模块天线的位置,一般在室内卫星信号会比较差,可到室外空旷的地方测试(如楼顶、阳台、窗边)。

26.3. NMEA-0183

26.3.1. NMEA-0183 协议简介

BH-ATGM332D 模块通过 TTL 串口输出定位数据信息,这些信息默认采用 NMEA-0183 4.0 协议, 输出的信息如前面的日志所示。

NMEA 是美国国家海洋电子协会(National Marine Electronics Association )为海用电子设备制定的标准格式,目前已经成为了 GPS 导航设备统一的 RTCM 标准协议, 本模块使用的 NMEA 4.0 版本协议支持 GPS、北斗、海格纳斯等定位系统。NMEA-0183 是一套定义接收机输出的标准信息,有几种不同的格式,每种都是独立相关的 ASCII 格式, 使用逗号隔开数据,数据流长度从 30-100 字符不等,通常以每秒间隔选择输出,最常用的格式为“GGA”,它包含了定位时间,纬度,经度,高度,定位所用的卫星数, DOP 值, 差分状态和校正时段等,其他的有速度,跟踪,日期等。 NMEA 实际上已成为所有的定位接收机中最通用的数据输出格式。

26.3.2. NMEA 解码库

了解了 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语句,方便模拟。 在该解码库之上, 野火扩展了其对 NMEA-0183 4.0 版本的支持。

26.4. 在 Debian 上使用 BH-ATGM332D 模块

26.4.1. 更新软件源

为了确保我们的软件包版本是最新版本,让我们使用apt命令更新本地apt包索引和升级系统:

1
 sudo apt update

26.4.2. 手动安装相关的依赖包

首先安装git make相关的依赖

1
 sudo apt install git make gcc

26.4.3. 拉取 nmealib 仓库

nmealib 仓库已经发布在github与gitee上,野火对改库进行了修改,以支持解析 NMEA-0183 4.0 协议,

在github拉取 nmealib 仓库

1
 git clone https://github.com/Embedfire/nmealib.git

或者在gitee仓库拉取。

1
 git clone https://gitee.com/Embedfire/nmealib.git

26.4.4. 示例

野火发布的 nmealib 仓库已经提供了相关的demo例程,在samples目录下有5个文件夹,分别是:generate 、 generator 、 math 、parse 、 parse_ebf_module。他们都对应不同的例程:

  • generate/generator :这两个demo是产生随机 NMEA-0183 标准信息的,可以用来做调试使用。

  • math:数学库,用来将这些 NMEA-0183 标准信息转换为数学相关的数据,比如经纬度、方位角等。

  • parse:解析 NMEA-0183 标准信息的demo,这些 NMEA-0183 标准信息信息写在一个数组中。

  • parse_ebf_module :解析野火北斗定位模块的 NMEA-0183 标准信息的demo,它通过读取串口3设备得到 BH-ATGM332D 的数据,然后调用nmealib库去解析它。

26.4.5. 编译 & 运行

我们进入nmealib目录下,直接运行make命令去编译:

1
 root@npi:/home/debian/nmealib# make

编译信息如下所示

 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
 mkdir lib
 mkdir -p build/nmea_gcc
 gcc  -I include  -c src/generate.c -o build/nmea_gcc/generate.o
 gcc  -I include  -c src/generator.c -o build/nmea_gcc/generator.o
 gcc  -I include  -c src/parse.c -o build/nmea_gcc/parse.o
 gcc  -I include  -c src/parser.c -o build/nmea_gcc/parser.o
 gcc  -I include  -c src/tok.c -o build/nmea_gcc/tok.o
 gcc  -I include  -c src/context.c -o build/nmea_gcc/context.o
 gcc  -I include  -c src/time.c -o build/nmea_gcc/time.o
 gcc  -I include  -c src/info.c -o build/nmea_gcc/info.o
 gcc  -I include  -c src/gmath.c -o build/nmea_gcc/gmath.o
 gcc  -I include  -c src/sentence.c -o build/nmea_gcc/sentence.o
 ar rsc lib/libnmea.a build/nmea_gcc/generate.o build/nmea_gcc/generator.o build/nmea_gcc/parse.o build/nmea_gcc/parser.o build/nmea_gcc/tok.o build/nmea_gcc/context.o build/nmea_gcc/time.o build/nmea_gcc/info.o build/nmea_gcc/gmath.o build/nmea_gcc/sentence.o
 ranlib lib/libnmea.a
 gcc  -I include  -c samples/generate/main.c -o samples/generate/main.o
 gcc  samples/generate/main.o -lm -Llib -lnmea -o build/samples_generate
 gcc  -I include  -c samples/generator/main.c -o samples/generator/main.o
 gcc  samples/generator/main.o -lm -Llib -lnmea -o build/samples_generator
 gcc  -I include  -c samples/parse/main.c -o samples/parse/main.o
 gcc  samples/parse/main.o -lm -Llib -lnmea -o build/samples_parse
 gcc  -I include  -c samples/parse_ebf_module/main.c -o samples/parse_ebf_module/main.o
 gcc  samples/parse_ebf_module/main.o -lm -Llib -lnmea -o build/samples_parse_ebf_module
 gcc  -I include  -c samples/math/main.c -o samples/math/main.o
 gcc  samples/math/main.o -lm -Llib -lnmea -o build/samples_math
 rm samples/math/main.o samples/parse_ebf_module/main.o samples/generator/main.o samples/parse/main.o samples/generate/main.o
 root@npi:/home/debian/nmealib#

可以看到编译生成了libnmea.a库文件,并且将所有的samples都编译了,输出的结果在 build/ 目录下,生成了对应的可执行文件,我们直接运行 build/samples_parse_ebf_module 试一试:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
 root@npi:/home/debian/nmealib# ./build/samples_parse_ebf_module

 时间:2021-03-02,15:58:12

 纬度:22.895364,经度113.845206

 海拔高度:54.200000 米
 速度:0.150000 km/h
 航向:272.030000 度
 正在使用的GPS卫星:8,可见GPS卫星:16
 正在使用的北斗卫星:8,可见北斗卫星:16
 PDOP:1.800000,HDOP:1.200000,VDOP:1.400000

可以看到已经输出了北斗定位相关的信息。

26.4.6. 示例代码

实例的代码比较简单,代码路径为 nmealib/samples/parse_ebf_module/main.c 主要流程是从串口3设备读取数据,读取数据的流程基本如下:open打开设备, 配置读取信息的参数,如波特率、停止位、校验位等,然后read读取数据,缓存到一个buff中; 然后调用nmea_parse() 函数去解析数据,并将其打印出来。

nmealib/samples/parse_ebf_module/main.c
  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
 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
 #include <nmea/nmea.h>

 #include <string.h>
 #include <stdio.h>
 #include <unistd.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
 #include <stdlib.h>
 #include <stdint.h>
 #include <termios.h>
 #include <sys/ioctl.h>

 void GMTconvert(nmeaTIME *SourceTime, nmeaTIME *ConvertTime, uint8_t GMT,uint8_t AREA);

 void error(const char *str, int str_size)
 {
     printf("Error: ");
     write(1, str, str_size);
     printf("\n");
 }

 int main()
 {
     int fd;
     int len;

     nmeaINFO info;
     nmeaPARSER parser;
     nmeaTIME beiJingTime;    //北京时间
     double deg_lat;          //转换成[degree].[degree]格式的纬度
     double deg_lon;          //转换成[degree].[degree]格式的经度

     char tmp_buf[200];
     char buff[4096];

     int size, it = 0;
     nmeaPOS dpos;

     fd = open("/dev/ttymxc2", O_RDONLY);

     printf("fopen %d\n", fd);

     if(!fd)
         return -1;

     // nmea_property()->trace_func = &trace;
     nmea_property()->error_func = &error;

     nmea_zero_INFO(&info);
     nmea_parser_init(&parser);

     struct termios opt;

     //清空串口接收缓冲区
     tcflush(fd, TCIOFLUSH);
     // 获取串口参数opt
     tcgetattr(fd, &opt);

     //设置串口输出波特率
     cfsetospeed(&opt, B9600);
     //设置串口输入波特率
     cfsetispeed(&opt, B9600);
     //设置数据位数
     opt.c_cflag &= ~CSIZE;
     opt.c_cflag |= CS8;
     //校验位
     opt.c_cflag &= ~PARENB;
     opt.c_iflag &= ~INPCK;
     //设置停止位
     opt.c_cflag &= ~CSTOPB;

     //更新配置
     tcsetattr(fd, TCSANOW, &opt);

     while(1)
     {
         memset(buff, 0, 4096);
         size = 0;
         len = 0;
         nmea_zero_INFO(&info);
         nmea_parser_init(&parser);
         for(it = 0; it < 32; it++) {
             memset(tmp_buf, 0, 100);
             size = (int)read(fd, tmp_buf, 100);
             if (size > 1) {
                 memcpy(buff + len, tmp_buf, size);
                 len += size;
                 memcpy(buff + len - 1, “\r\n”, 2);
                 len += 1;
             }

         }

         nmea_parse(&parser, buff, len, &info);

          // info.lat lon中的格式为[degree][min].[sec/60],使用以下函数转换成[degree].[degree]格式
         deg_lat = nmea_ndeg2degree(info.lat);
         deg_lon = nmea_ndeg2degree(info.lon);

         GMTconvert(&info.utc,&beiJingTime,8,1);

         // // /* 输出解码得到的信息 */
         printf("\r\n时间:%d-%02d-%02d,%d:%d:%d\r\n", beiJingTime.year+1900, beiJingTime.mon,beiJingTime.day,beiJingTime.hour,beiJingTime.min,beiJingTime.sec);

         printf("\r\n纬度:%f,经度%f\r\n",deg_lat,deg_lon);
         printf("\r\n海拔高度:%f 米 ", info.elv);
         printf("\r\n速度:%f km/h ", info.speed);
         printf("\r\n航向:%f 度", info.direction);

         printf("\r\n正在使用的GPS卫星:%d,可见GPS卫星:%d",info.satinfo.inuse,info.satinfo.inview);

         printf("\r\n正在使用的北斗卫星:%d,可见北斗卫星:%d",info.satinfo.inuse,info.satinfo.inview);
         printf("\r\nPDOP:%f,HDOP:%f,VDOP:%f\n",info.PDOP,info.HDOP,info.VDOP);
     }

     nmea_parser_destroy(&parser);
     close(fd);

     return 0;
 }

26.5. nmea库的简单介绍

上面的代码用到了nmea库的很多数据结构,所以要介绍一下相关的知识:

26.5.1. 结构体 nmeaPARSER 和 nmeaINFO

上述参数中的 parser 及 info 变量的数据类型 nmeaPARSER 和 nmeaINFO 则是 NMEA解码库特有的数据结构,具体如下:

  • nmeaPARSER

nmealib/include/nmea/parser.h
1
2
3
4
5
6
7
8
9
 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() 函数释放分配的空间。

  • nmeaINFO则是 NMEA 解码库良好的封装特性使我们无需关注更深入的内部实现,只需要再了解一下nmeaINFO 数据结构即可,所有 GPS 解码得到的结果都存储在这个结构中,其结构体定义如下:

nmealib/include/nmea/parser.h
 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
 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  sog;        /**< 数值 对地速度,单位为节 */
     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) */
     char    mode;       /**< 字符 定位模式标志 (A = 自主模式, D = 差分模式, E = 估算模式, N = 数据无效) */
     nmeaSATINFO satinfo; /**< Satellites information */
     nmeaSATINFO BDsatinfo; /**北斗卫星信息*/

         int txt_level;
         char *txt;

 } nmeaINFO;

这些结构体的说明如下:

ATGM332D008

ATGM332D008

在调用了 nmea_parse() 函数解析完成之后,直接查询 nmeaINFO 结构的数据即可得到解码的结果。