• 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端工具,可以很快的看到效果。

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

  • 相关阅读:
    【算法】三角形最小路径债务
    【阿米巴】债务
    【JTA】JTA允许应用程序执行分布式事务处理
    【算法】代码面试最常用的10大算法
    【Git 】$ ./gradlew idea 构建一个idea的项目
    【git】切换分支获取代码
    【springmvc Request】 springmvc请求接收参数的几种方法
    【gradle】 入门
    项目经理眼中优秀开发人员的标准
    MAC系统介绍
  • 原文地址:https://www.cnblogs.com/pingwen/p/5225461.html
Copyright © 2020-2023  润新知