5. GPS模块

GPS定位采用中科微电子公司的ATGM332D-5N-31模组方案。

5.1. BH-ATGM332D简介

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

详细GPS模块资料请参考:https://doc.embedfire.com/products/link/zh/latest/module/gps/beidou_atgm332d.html?highlight=gps

GPS模块

5.2. BH-ATGM332D模块引脚说明

BH-ATGM332D模块的引脚说明如下,

GPS_GPIO
BH-ATGM332D模块引脚说明

编号

名称

说明

1

VCC

电源线,正常电压范围为:3.3~5V

2

GND

地线

3

TXD

串口数据发送信号线,使用TTL电平

4

RXD

串口数据接收信号线,使用TTL电平

5

PPS

时间脉冲信号线,模块接收到GPS时间信息后,输出可调节的脉冲信号,默认为1Hz,脉冲上升沿与UTC时间对齐

5.3. GPS_Decode_USART例程介绍及实验现象

5.3.1. 实验现象

为了方便用户调试及使用BH-ATGM332D模块,野火提供了多功能调试助手软件。配合BH-ATGM332D模块,该软件能显示定位模块通过USB转TTL线传回的原始信息,并对这些信息进行解码,得到时间、经纬度、海拔等数据,并根据解码得的结果在百度地图上标注实时位置,使应用定位模块变得更简单直观。

该软件是绿色免安装的,直接打开即可使用,如果无法打开,请先安装Windows系统组件.Net Framework4.0。

软件中使用的百度地图需要联网使用,在没有网络的情况下,软件中的百度地图部分会加载异常。软件正常运行界面见图,

GPS_1

在模块配套资料包的“配套软件”文件夹可找到野火多功能调试助手,打开该软件后,选择到GPS调试功能界面,把BH-ATGM332D模块通过USB转串口TTL线连接到电脑,见图 4 4,该软件即可检测到新的COM口(若没有检测到COM口,请检查CH340驱动程序是否正确安装),选择USB转串口TTL线所用的COM口,及默认波特率9600,然后点击“打开串口”按钮,即可接收到定位模块传回的信息,见下图,

GPS_2

使用野火多功能调试助手可方便地测试BH-ATGM332D模块是否正常,测试步骤如下,

  1. 确保开发环境正常,检查是否正常安装好CH340 USB转串口TTL驱动,使用GPS日志文件检验野火多功能调试助手是否正常运行;

  2. 使用USB转串口TTL线连接电脑与BH-ATGM332D模块,并给模块连接上有源天线。正常时,模块上的红色时间脉冲指示灯亮,在调试助手软件上打开USB转串口TTL线对应的串口,它的数据输出窗口会输出定位的原始数据(这些原始数据使用NMEA0183协议格式)。输出的数据一般会出现两种情况,见下图,

GPS_3

这两图的区别是,上图中的GPS数据信息数据间有很多连续的“逗号”,而下图中逗号与逗号之间一般是有数字的,它们分别对应了GPS信号差与GPS信号良好的状况。

如果模块上电后输出的数据长期处于第一种状态,应考虑转移一下定位模块天线的位置,一般在室内卫星信号会比较差,可到室外空旷的地方测试(如楼顶、阳台、窗边),另外,野火多功能调试助手加载的百度地图是需要在联网的时候才能正常使用的,所以在室外无网络的地方,测试时可把定位数据以文件格式保存起来,在联网的情况下再加载定位日志文件进行解码。

实际上,当调试助手的信息窗口显示接收到连续的以$GPXXX、$BDXXX、$GNXXX开头的数据时,已经说明GPS模块正常了,当然,软件在地图上标注出当前地点才是我们追求的目标!

特别地,BH-ATGM332D模块还会输出当前的天线连接状态,天线存在下图中的三种状态,该数据形如“$GPTXT,01,01,01,ANTENNA 状态*无关数字”的格式。

GPS_4

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

5.3.2. 例程介绍

GPS_Decode_USART例程使用与定位模块连接的USART串口获取GPS模块输出的原始信息,并把解码结果使用USART1输出。 在控制器在处理数据的同时,串口会源源不断地接收GPS数据,因此需要协调好接收数据和解码数据的关系,野火例程使用DMA串口缓冲区方案,解决了这个问题。

先来阅读GPS_Decode_USART例程的main文件,看下面的main函数代码, GPS_Config函数对定位模块连接的USART串口进行初始化,以便于接收GPS模块的信息。

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
 #include "stm32f10x.h"
 #include "./usart/bsp_usart.h"
 #include "./led/bsp_led.h"
 #include "./gps/gps_config.h"

 extern void nmea_decode_test(void);

 /**
   * @brief  主函数
    * @param  无
    * @retval 无
    */
 int main(void)
 {
     /* LED 端口初始化 */
     LED_GPIO_Config();
     LED_BLUE;

     /*串口初始化*/
     USART_Config();

     GPS_Config();

     printf("\r\n野火 GPS模块测试例程\r\n");

     printf("\r\n本程序对GPS模块串口传回的数据解码,");
     printf("实验时请给开发板接入GPS模块 \r\n");

     /* GPS解码测试 */
     nmea_decode_test();

     while (1);
 }

而GPS_Config函数又分别调用了GPS_USART_INIT和GPS_DMA_Config函数,分别初始了串口及串口配套的DMA模式,代码如下。

GPS_Config函数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
  /**
    * @brief  GPS_Config gps 初始化
    * @param  无
    * @retval 无
    */
 void GPS_Config(void)
 {
     GPS_USART_INIT();
     GPS_DMA_Config();
 }

其中的GPS_USART_INIT函数主要是对stm32与定位模块连接的USART串口外设作了基本的初始化,除了要注意把波特率配置为9600,其它跟普通串口配置无异。本例程重点在串口DMA的配置,GPS_DMA_Config函数定义如下。

GPS_DMA_Config函数定义
 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
  #define GPS_DR_Base                  (USART2_BASE+0x04)     // 串口的数据寄存器地址
  #define GPS_DATA_ADDR                GPS_DR_Base            //GPS使用的串口的数据寄存器地址
  #define GPS_RBUFF_SIZE               512                    //串口接收缓冲区大小
  #define HALF_GPS_RBUFF_SIZE          (GPS_RBUFF_SIZE/2)     //串口接收缓冲区一半

  /************************************************************/
  #define GPS_DMA                      DMA1
  #define GPS_DMA_CLK                  RCC_AHBPeriph_DMA1
  #define GPS_DMA_CHANNEL              DMA1_Channel6
  #define GPS_DMA_IRQn                 DMA1_Channel6_IRQn      //GPS中断源

  /* 外设标志 */
  #define GPS_DMA_FLAG_TC              DMA1_FLAG_TC6
  #define GPS_DMA_FLAG_TE              DMA1_FLAG_TE6
  #define GPS_DMA_FLAG_HT              DMA1_FLAG_HT6
  #define GPS_DMA_FLAG_GL              DMA1_FLAG_GL6
  #define GPS_DMA_IT_HT                DMA1_IT_HT6
  #define GPS_DMA_IT_TC                DMA1_IT_TC6

  /* 中断函数 */   //GPS使用的DMA中断服务函数
  #define GPS_DMA_IRQHANDLER           DMA1_Channel6_IRQHandler
  /**
    * @brief  GPS_DMA_Config gps dma接收配置
    * @param  无
    * @retval 无
    */
  static void GPS_DMA_Config(void)
  {
      DMA_InitTypeDef DMA_InitStructure;

      /* 开启DMA时钟 */
      RCC_AHBPeriphClockCmd(GPS_DMA_CLK, ENABLE);

      /* 设置DMA源:串口数据寄存器地址 */
      DMA_InitStructure.DMA_PeripheralBaseAddr = GPS_DATA_ADDR;

      /* 内存地址(要传输的变量的指针) */
      DMA_InitStructure.DMA_MemoryBaseAddr = (u32)gps_rbuff;

      /* 方向:从内存到外设 */
      DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;

      /* 传输大小DMA_BufferSize=SENDBUFF_SIZE */
      DMA_InitStructure.DMA_BufferSize = GPS_RBUFF_SIZE;

      /* 外设地址不增 */
      DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;

      /* 内存地址自增 */
      DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;

      /* 外设数据单位 */
      DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;

      /* 内存数据单位 8bit */
      DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;

      /* DMA模式:不断循环 */
      DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;

      /* 优先级:中 */
      DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;

      /* 禁止内存到内存的传输 */
      DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;

      /* 配置DMA的通道 */
      DMA_Init(GPS_DMA_CHANNEL, &DMA_InitStructure);

      GPS_Interrupt_Config();
      //配置DMA发送完成后产生中断
      DMA_ITConfig(GPS_DMA_CHANNEL,DMA_IT_HT|DMA_IT_TC,ENABLE);

      /* 使能DMA */
      DMA_Cmd (GPS_DMA_CHANNEL,ENABLE);

      /* 配置串口 向 DMA发出TX请求 */
      USART_DMACmd(GPS_USART, USART_DMAReq_Rx, ENABLE);
  }

本函数中使用到比较多的宏,部分定义见错误!未找到引用源。。GPS_DMA_Config函数主要工作如下:设置了外设地址为USART的数据寄存器, 并把数据传输方向设置为从USART数据寄存器传输到内存变量gps_rbuff中,该缓冲区数组大小为512字节。最关键的位置是第74行, 它设置了DMA半传输结束中断及全传输结束中断,所以它实际把缓冲区分为成了大小相等的A/B两部分,每次DMA接收了半个缓冲区大小的数据时(本程序为256字节),就会引起中断。

得益于这个机制,可以设计程序当DMA使用缓冲区A存储数据时,控制 CPU使用B中的数据进行GPS解码,当DMA使用B存储时,控制CPU使用A进行解码, 只要缓冲区的大小设置合适,即可避免前面说到的数据丢失问题,这种处理方式也称“乒乓缓冲”,得名于它像打乒乓球一样,你来我往。

当DMA的半传输中断或全传输中断产生时,进入的中断服务函数调用了GPS_ProcessDMAIRQ函数,代码如下。

GPS_ProcessDMAIRQ函数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
 /**
   * @brief  GPS_ProcessDMAIRQ GPS DMA中断服务函数
   * @param  None.
   * @retval None.
   */
  void GPS_ProcessDMAIRQ(void)
  {

     if (DMA_GetITStatus(GPS_DMA_IT_HT) ) {      /* DMA 半传输完成 */
          GPS_HalfTransferEnd = 1;                //设置半传输完成标志位
          DMA_ClearFlag(GPS_DMA_FLAG_HT);
      } else if (DMA_GetITStatus(GPS_DMA_IT_TC)) { /* DMA 传输完成 */
          GPS_TransferEnd = 1;                    //设置传输完成标志位
          DMA_ClearFlag(GPS_DMA_FLAG_TC);

      }
  }

在这个函数处理中,主要是在半传输和全传输结束引起中断时,对GPS_HalfTransferEnd和GPS_TransferEnd标志位进行标记, 在解码流程中根据这两个标志使用不同的缓冲区进行处理,处理过程代码如下。

GPS_ProcessDMAIRQ函数
 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
 /*gps_config.h文件*/
  //定义这个宏,对SD卡上的gpslog.txt文件进行解码;
  //不定义的话使用串口接收GPS信息解码
  //#define __GPS_LOG_FILE

  /*nmea_decode_test.c文件*/
  //对SD卡上的gpslog.txt文件进行解码;
  //(需要在sd卡上存放gpslog.txt文件)
  #ifdef __GPS_LOG_FILE
  /*此处省略SD卡日志文件解码部分...*/
  #else       //对GPS模块传回的信息进行解码

  /**
    * @brief  nmea_decode_test 解码GPS模块信息
    * @param  无
    * @retval 无
    */
  int nmea_decode_test(void)
  {
      double deg_lat;//转换成[degree].[degree]格式的纬度
      double deg_lon;//转换成[degree].[degree]格式的经度

      nmeaINFO info;          //GPS解码后得到的信息
      nmeaPARSER parser;      //解码时使用的数据结构
      uint8_t new_parse=0;    //是否有新的解码数据标志

      nmeaTIME beiJingTime;    //北京时间

      /* 设置用于输出调试信息的函数 */
      nmea_property()->trace_func = &trace;
      nmea_property()->error_func = &error;
      nmea_property()->info_func = &gps_info;

      /* 初始化GPS数据结构 */
      nmea_zero_INFO(&info);
      nmea_parser_init(&parser);

      while (1) {
          if (GPS_HalfTransferEnd) {  /* 接收到GPS_RBUFF_SIZE一半的数据 */
              /* 进行nmea格式解码 */
              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-%02d-%02d,%d:%d:%d\r\n",
                beiJingTime.year+1900, beiJingTime.mon,beiJingTime.day,
                     beiJingTime.hour,beiJingTime.min,beiJingTime.sec);

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

              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.BDsatinfo.inuse,info.BDsatinfo.inview);
              printf("\r\nPDOP:%f,HDOP:%f,VDOP:%f",
                     info.PDOP,info.HDOP,info.VDOP);

              new_parse = 0;
          }

      }

      /* 释放GPS数据结构 */
      // nmea_parser_destroy(&parser);
      //  return 0;
  }

  #endif