zoukankan      html  css  js  c++  java
  • U-BLOX GPS 模块及GPRMC指令解析

          受朋友所托,调试一款GPS模块,该模块是UBLOX的NEO-6M GPS模组。想到用这款GPS的人较多,自己日后也有可能在用到这个模块,就写下这份笔记。

    1. 介绍

    基本信息如下:

    1, 模块采用U-BLOX NEO-6M模组,体积小巧,性能优异。

    2, 模块增加放大电路,有利于无缘陶瓷天线快速搜星。

    3, 模块可通过串口进行各种参数设置,并可保存在EEPROM,使用方便。

    4, 模块自带SMA接口,可以连接各种有源天线,适应能力强。

    5, 模块兼容3.3V/5V电平,方便连接各种单片机系统。

    6, 模块自带可充电后备电池,可以掉电保持星历数据。

    看到这里,就可以知道,这个模块是高度集成的,有点类似于西门子华为等公司的GPRS模块,基本上就是一个小的系统,用户只需要用AT命令通过串口通信就可以完成所有工作。那么这款模块的使用,其实就是字符串的解析工作了。

     

    2. 通信协议

    GPS模块采用NMEA 0183协议,NMEA 0183是美国国家海洋电子协会(National Marine Electronics Association)为海用电子设备制定的标准格式。目前业已成了GPS导航设备统一的RTCM(Radio Technical Commission for Maritime services)标准协议。

    NMEA-0183协议采用ASCII码来传递GPS定位信息,我们称之为帧。

    帧格式形如:$aaccc,ddd,ddd,„,ddd*hh(CR)(LF)

    1、“$”:帧命令起始位

    2、aaccc:地址域,前两位为识别符(aa),后三位为语句名(ccc)

    3、ddd„ddd:数据

    4、“*”:校验和前缀(也可以作为语句数据结束的标志)

    5、hh:校验和,$与*之间所有字符ASCII码的校验和(各字节做异或运算,得到

    校验和后,再转换16进制格式的ASCII字符)

    6、(CR)(LF):帧结束,回车和换行符

    在一般的项目中,最常用的指令是第4个,即$GPRMC ,推荐定位信息,长度70字节。$GPRMC(推荐定位信息,Recommended Minimum Specific GPS/Transit Data),$GPRMC语句的基本格式如下:

     $GPRMC,(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12)*hh(CR)(LF)

    (1) UTC时间,hhmmss(时分秒)

    (2) 定位状态,A=有效定位,V=无效定位

    (3) 纬度ddmm.mmmmm(度分)

    (4) 纬度半球N(北半球)或S(南半球)

    (5) 经度dddmm.mmmmm(度分)

    (6) 经度半球E(东经)或W(西经)

    (7) 地面速率(000.0~999.9节)

    (8) 地面航向(000.0~359.9度,以真北方为参考基准)

    (9) UTC日期,ddmmyy(日月年)

    (10)磁偏角(000.0~180.0度,前导位数不足则补0)

    (11) 磁偏角方向,E(东)或W(西)

    (12) 模式指示(A=自主定位,D=差分,E=估算,N=数据无效)

    举例如下:

    $GPRMC,023543.00,A,2308.28715,N,11322.09875,E,0.195,,240213,,,A*78 

    3. PC端显示数据

    GPS模块有一个PC配置软件,叫做u-Center,可以对模块进行参数设置,然后保存到EEPROM,其实也可以通过单片机串口通信进行设置,但是PC端设置更加人性化,可以立刻看到结果。

    打开GPS模块之后,接上u-Center软件,可以看到如下数据。

     1   $GPGSV,2,2,08,21,15,076,,23,52,270,,26,50,050,,27,52,179,*7D
     2   $GPRMC,132043.00,V,,,,,,,120116,,,N*7F
     3   $GPVTG,,,,,,,,,N*30
     4   $GPGSA,A,1,,,,,,,,,,,,,99.99,99.99,99.99*30
     5   $GPGSV,2,1,08,03,00,230,,07,02,301,,08,24,198,,16,72,000,*7B
     6   $GPGSV,2,2,08,21,15,076,,23,52,270,,26,50,050,,27,52,179,*7D
     7   $GPRMC,132044.00,V,,,,,,,120116,,,N*78
     8   $GPVTG,,,,,,,,,N*30
     9   $GPGSA,A,1,,,,,,,,,,,,,99.99,99.99,99.99*30
    10   $GPGSV,2,1,08,03,00,230,,07,02,301,,08,24,198,,16,72,000,*7B
    11   $GPGSV,2,2,08,21,15,076,,23,52,270,25,26,50,050,,27,52,179,*7A
    12   $GPRMC,132045.00,V,,,,,,,120116,,,N*79
    13   $GPVTG,,,,,,,,,N*30
    14   $GPGSA,A,1,,,,,,,,,,,,,99.99,99.99,99.99*30
    15   $GPGSV,2,1,08,03,00,230,,07,02,301,,08,24,198,,16,72,000,*7B
    16   $GPGSV,2,2,08,21,15,076,,23,52,270,,26,50,050,,27,52,179,*7D
    17   $GPRMC,132046.00,V,,,,,,,120116,,,N*7A
    18   $GPVTG,,,,,,,,,N*30
    19   $GPGSV,2,1,08,03,00,230,,07,02,301,,08,24,198,,16,72,000,*7B
    20   $GPGSV,2,2,08,21,15,076,07,23,52,270,20,26,50,050,,27,52,179,*78
    21   $GPRMC,132049.00,V,,,,,,,120116,,,N*75
    22   $GPRMC,133300.00,V,,,,,,,120116,,,N*7A
    23   $GPRMC,133301.00,V,,,,,,,120116,,,N*7B
    24   $GPRMC,133302.00,V,,,,,,,120116,,,N*78
    25   $GPRMC,133303.00,V,,,,,,,120116,,,N*79
    26   $GPRMC,133304.00,V,,,,,,,120116,,,N*7E
    27   $GPRMC,133305.00,V,,,,,,,120116,,,N*7F
    28   $GPRMC,133306.00,A,3949.63075,N,11616.48616,E,0.513,,120116,,,A*7A
    29   $GPRMC,133307.00,A,3949.63025,N,11616.48614,E,1.053,,120116,,,A*7C
    30   $GPRMC,133308.00,A,3949.63002,N,11616.48641,E,1.101,,120116,,,A*70
    31   …………

    4. PC端软件配置

          GPS有7种指令数据输出,如果我们只需要当前经纬度的话,可以屏蔽其他的数据。如果不屏蔽的话,MCU处理的时候,会不停的收到不需要的指令,降低MCU效率。

    使用软件配置,[Config]-->[Configuration], 可以选择显示哪些项目,这里只保留GPRMC指令信息输出,然后保存。界面显示如下:

    5. 数据解析

          相对于MCU的处理速度,GPS定位数据更新几乎可以认为是缓慢变化的信号,每秒都会输出推荐位置信息,但是即使丢掉几个也不会影响定位准确性。因此可以使用中断循环buffer来接收GPS输出的串口数据,然后在需要的地方读取buffer,对数据实现一次检索,找到一个有用的GPS定位数据。

    /*******************************************************************************
    1.中断负责把GPS串口数据保存到GPS_Uart_Rcv_Buf,在合适的地方调用此函数开始解析。
    2.调用本函数会自动关闭接收,然后处理,期间的GPS数据可以忽略。
    3.在合适的地方打开接收。
    *******************************************************************************/
    uint8_t get_gps_useful_data(uint8_t *weidu, uint8_t *jingdu)
    { 
         uint8_t *p_frame_start = NULL;
         uint8_t *p_useful_frame_start = NULL;
         uint8_t *p_useful_frame_end = NULL;
         int16_t frame_len = 0;
         int16_t rcv_buf_data_len = 0;
         int16_t index = 0;
         int16_t frame_start_point = 0;
         int16_t remain_data_len = 0;
         uint8_t rtn =0;
        
         gps_rcv_enable(FALSE); 
         rcv_buf_data_len = GPS_Rev_Buf_Size;
         p_frame_start = &GPS_Uart_Rcv_Buf[0];
            
        /* data example:
                $GPVTG,,,,,,,,,N*30 
                $GPRMC,132234.00,V,,,,,,,120116,,,N*7D 
                $GPRMC,133735.00,A,3949.63893,N,11616.48419,E,0.296,,120116,,,A*79
        */    
         for(index = 0; index < rcv_buf_data_len; )
         {     
             p_frame_start=strstr(GPS_Uart_Rcv_Buf + index, "$GPRMC,"); //1. find start, "$GPRMC,"
             //$GPRMC,132234.00,V,,,,,,,120116,,,N*7D $GPRMC,133735.00,A,3949.63893,N,11616.48419,E,0.296,,120116,,,A*79
             if(p_frame_start) 
             {
                  index = p_frame_start-GPS_Uart_Rcv_Buf;
                  if(index>0)
                    {
                        memset(GPS_Uart_Rcv_Buf, 0 , index);
                    }
    
                  index = index + 7;
                  p_useful_frame_start = strstr(GPS_Uart_Rcv_Buf + index, ",A,");//2 find useful data, ",A,"
                  frame_start_point = p_useful_frame_start - p_frame_start;
                  if((frame_start_point > 20)||(frame_start_point <= 0))//not find useful data
                    {
                        continue;
                    }
                    frame_start_point = p_useful_frame_start-GPS_Uart_Rcv_Buf;
                    p_useful_frame_end = strstr(GPS_Uart_Rcv_Buf + frame_start_point, ",A*");//3 find useful data end, ",A*"
                    frame_len = p_useful_frame_end - p_frame_start + 1; 
                    if((frame_len > GPS_Frame_Buf_Size)||(frame_len <= 50)) // not found frame end
                    {
                        continue;
                    }
                    
                    //$GPRMC,133735.00,A,3949.63893,N,11616.48419,E,0.296,,120116,,,A*79
                    frame_start_point = p_frame_start - GPS_Uart_Rcv_Buf + 1;
                    memset(GPS_Frame_Buf, 0, GPS_Frame_Buf_Size);
                    memcpy(GPS_Frame_Buf, GPS_Uart_Rcv_Buf + frame_start_point , frame_len);
    //                 if(check_frame_xor(GPS_Frame_Buf) != TRUE) 
    //                 {
    //                     continue;
    //                 }
                    //the frame is correct
                    rtn = read_gps_data(GPS_Frame_Buf, remain_data_len, weidu, jingdu);
                    if(1 == rtn)
                    {
                        break; //found weidu,jingdu data
                    }
                    else
                    {
                         continue;
                    }
                }
                else
                {
                   break;
                } 
            }
            g_gps_data_cnt = 0;
            memset(GPS_Uart_Rcv_Buf, 0, GPS_Rev_Buf_Size);
           return rtn;
    }

         上面处理的基本思想是:找到"$GPRMC,"帧头,然后找到代表定位信息有效的字符",A,",最后找到帧尾",A*"。

         因为实际收到的数据有不完整的,或者无效的定位信息,也有串口接收循环buffer造成的覆盖数据,因此上面还校验了数据长度,以及一个标志字符串到另一个字符串直接的长度。

         实际使用中,发现没有加上校验也可以很有效的工作,但是为了保证数据安全,最后的异或校验还有应该有的。

    /*******************************************************************************
    * Function Name  : 
    * Description    : 对GPRMC数据包进行解析,找到经纬度数据
    * Input          :  
    * Output         :  
    * Return         :  
    *******************************************************************************/
    uint8_t read_gps_data(uint8_t *gps_buf, uint8_t frame_len, uint8_t *weidu, uint8_t *jindu)
     {
         uint8_t *weidu_s = NULL;
         uint8_t *weidu_o = NULL;
         uint8_t *jingdu_o = NULL;
         uint8_t rtn =0;
         //GPRMC,133735.00,A,3949.63893,N,11616.48419,E,0.296,,120116,,,A*79
         weidu_s  = strstr(GPS_Frame_Buf, ",A,");
         weidu_o  = strstr(GPS_Frame_Buf, ",N,");
         jingdu_o = strstr(GPS_Frame_Buf, ",E,");
         if((weidu_s == NULL) || (weidu_o == NULL) ||(jingdu_o == NULL) )
         {
             rtn = FALSE;
         }
         else
         {
              memset(weidu_buf,0,sizeof(weidu_buf));
              memset(jindu_buf,0,sizeof(jindu_buf));
              memcpy(weidu_buf, weidu_s+3, (weidu_o-weidu_s-3));
              memcpy(jindu_buf, weidu_o+3, (jingdu_o-weidu_o-3));
              printf("
    ---------------------------------------");
              printf("
    Get GPS Frame:
    %s
    ", GPS_Frame_Buf);
              printf("
    ---------------------------------------");
              rtn = TRUE;
         }
        return rtn;
    }

    6. 总结

          本文总结了GPS模块的基本使用方法,可以得到经度和纬度信息。对于此类的模块产品,主要工作有两大部分:

          1:模块的熟悉,包括配置和指令的使用,一般可以一边读文档一边跑下demo体验效果;

          2:字符串的解析。

          同样的经验可以应用于很多串口模块。

          例如GPRS模块,蓝牙模块,zigbee模块,TCP模块,CAN模块,485模块。

          这几种产品我都使用过,其基本思路是一样的,调试时候可以先用串口助手模拟MCU来发数据,其实大部分模块都有自己的PC端工具,可以很快的看到效果。

          使用模块可以极大地提升项目效率,但是灵活性和成本上会有所牺牲。另外,由于模块的封闭性,一般都要写很多的异常保护处理来保证产品的正常工作。

  • 相关阅读:
    redis发布订阅
    redis学习笔记(面试题)
    redis安全 (error) NOAUTH Authentication required
    HDU3001 Travelling —— 状压DP(三进制)
    POJ3616 Milking Time —— DP
    POJ3186 Treats for the Cows —— DP
    HDU1074 Doing Homework —— 状压DP
    POJ1661 Help Jimmy —— DP
    HDU1260 Tickets —— DP
    HDU1176 免费馅饼 —— DP
  • 原文地址:https://www.cnblogs.com/pingwen/p/5225461.html
Copyright © 2011-2022 走看看