1.摘要

  GPS模块使用串口通信,那么它的的数据处理本质上还是串口通信处理,只是GPS模块的输出的有其特定的格式,需要字符串处理逻辑来解析其含义。如何高效的处理从GPS模块接收到的数据帧,是GPS驱动设计的重点,本文使用状态机的思想来处理GPS输出的串口数据流,相对于定时从串口环形bufer取数据包然后依次解析有更高的实时性并且单片机负荷更低。

2. GPS数据协议简介

常用的GPS模块大多采用NMEA-0183 协议,目前业已成了GPS导航设备统一的RTCM(Radio Technical Commission for Maritime services)标准协议。NMEA-0183 是美国国家海洋电子协会(National Marine Electronics Association)所指定的标准规格,这一标准制订所有航海电子仪器间的通讯标准,其中包含传输资料的格式以及传输资料的通讯协议。

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):帧结束,回车和换行符

可以从串口抓数据帧:

  1. $GPGSV,2,2,08,21,15,076,,23,52,270,,26,50,050,,27,52,179,*7D
    $GPRMC,132043.00,V,,,,,,,120116,,,N*7F
    $GPGSA,A,1,,,,,,,,,,,,,99.99,99.99,99.99*30
    $GPRMC,133308.00,A,3949.63002,N,11616.48641,E,1.101,,120116,,,A*70

3. GPS状态机接收

一般的应用中我们最关心的数据是GPRMC,即推荐定位信息。我们常见GPS数据接收方法主要是串口中断法,串口中断一直开着,然后定时从中断中取一包数据,解析这包数据,找到定位信息,常见的主要是找到GPRMC帧。

这有几个问题。

1. 一般而言,中断数据很快,而数据处理过程会发生丢接收中断。

2. 为了避免丢数据,可以使用双buffer来处理,读数据时候就把这个buffer锁定,然后再来了中断数据就往另外一个buffer放。

3. 如果取数据时刻刚好是一个有效定位信息,那么切换到第二个buffer后,就导致一条帧分成2份,两个buffer中该数据帧都不完整。

4.代码结构不清晰,应用层收到的数据可能不是完整的一条帧。

基于以上几点,改进方法使用状态机来接收,可以每次完整的给应用层发送一帧数据。

并且不需要关闭串口中断,接收过程一直进行。

接收到一个完整帧后就往上层送一次,上层负责解释数据的含义。

下面是在stm32平台上的接收GPS数据的处理过程。

  1. //gps receive gps_state machine.
  2. #define Start 0// $
  3. #define G 1
  4. #define P 2
  5. #define R 3
  6. #define M 4
  7. #define C 5
  8. #define Data 6
  9. #define Check0 7 // *
  10. #define Check1 8// *
  11. void UART4_IRQHandler(void)
  12. {
  13. static uint8_t len = ;
  14. static uint8_t crc = ;
  15. static GPS_MSG_T GpsMsg;
  16. uint8_t data = ;
  17. uint8_t tmp_flg = ;
  18. //$GPRMC,144601.00,A,3916.72973,N,11706.60267,E,0.719,,180117,,,A*76
  19. if (USART_GetITStatus(GPS_UART, USART_IT_RXNE) != RESET)
  20. {
  21. data = (uint8_t)USART_ReceiveData(GPS_UART);
  22.  
  23. switch(gps_state) // find GPRMC
  24. {
  25. case Start:
  26. if(data == '$') {
  27. gps_state = G;
  28. len = ;
  29. GpsMsg.maxLen = MaxGPSMsgLen;
  30. memset(GpsMsg.buffer, , GpsMsg.maxLen);
  31. GpsMsg.buffer[len++] = data;
  32. crc = ;
  33. }
  34. else gps_state = Start;
  35. break;
  36. case G:
  37. if(data == 'G'){
  38. gps_state = P;
  39. GpsMsg.buffer[len++] = data;
  40. crc ^= data;
  41. }
  42. else gps_state = Start;
  43. break;
  44. case P:
  45. if(data == 'P'){
  46. gps_state = R;
  47. GpsMsg.buffer[len++] = data;
  48. crc ^= data;
  49. }
  50. else gps_state = Start;
  51. break;
  52. case R:
  53. if(data == 'R'){
  54. gps_state = M;
  55. GpsMsg.buffer[len++] = data;
  56. crc ^= data;
  57. }
  58. else gps_state = Start;
  59. break;
  60. case M:
  61. if(data == 'M'){
  62. gps_state = C;
  63. GpsMsg.buffer[len++] = data;
  64. crc ^= data;
  65. }
  66. else gps_state = Start;
  67. break;
  68. case C:
  69. if(data == 'C'){
  70. gps_state = Data;
  71. GpsMsg.buffer[len++] = data;
  72. crc ^= data;
  73. }
  74. else gps_state = Start;
  75. break;
  76. case Data:
  77. if(data == '*'){
  78. gps_state = Check0;
  79. GpsMsg.buffer[len++] = data;
  80. }
  81. else{
  82. gps_state = Data;
  83. GpsMsg.buffer[len++] = data;
  84. crc ^= data;
  85. if(len>GpsMsg.maxLen) gps_state = Start;
  86. }
  87. break;
  88.  
  89. case Check0:
  90. gps_state = Check1;
  91. GpsMsg.buffer[len++] = data;
  92. break;
  93. case Check1: //*hh
  94. gps_state = Start;
  95. GpsMsg.buffer[len++] = data;
  96. if(crc == ((GpsMsg.buffer[len-]-'')* + (GpsMsg.buffer[len-]-'')))
  97. {
  98. GpsMsg.buffer[len++] = '\r';
  99. GpsMsg.buffer[len] = '\n';
  100. GpsMsg.length = len;
  101. //send to gps task
  102. xQueueSendFromISR(GpsQueue, (void *) &GpsMsg, );
  103. }
  104. }
  105.  
  106. USART_ClearITPendingBit(GPS_UART, USART_IT_RXNE);
  107. }
    }

  这段代码完成4个功能。1)串口接收数据;2)状态机切换;3)数据校验;4)把通过校验的数据发给应用层。

4. GPS数据解析

  应用层已经收到数据了,剩下的工作就是字符串解析了。如果只关注GPRMC信息的话,上面已经做了校验,出错的概率极小,那么应用层就可以直接从收到的数据帧里提取经纬度了。

  如果希望数据全部都处理,那么在串口接收部分就不能只保留GPRMC信息,应该全部都保留然后发给应用层,应用层解析数据帧。这里给出一个开源的例子,其中使用了多个c标准库字符处理函数,优点是通用性强功能完备,当然在嵌入式中可能比较占内存,如果资源紧张可以自己写该部分处理逻辑。

  1.  
  1. /*! \file tok.h */
  2.  
  3. //#include "nmea/tok.h"
  4. #include "tok.h"
  5. #include <stdarg.h>
  6. #include <stdlib.h>
  7. #include <stdio.h>
  8. #include <ctype.h>
  9. #include <string.h>
  10. #include <limits.h>
  11. //#include "config.h"
  12.  
  13. #define NMEA_TOKS_COMPARE (1)
  14. #define NMEA_TOKS_PERCENT (2)
  15. #define NMEA_TOKS_WIDTH (3)
  16. #define NMEA_TOKS_TYPE (4)
  17.  
  18. /**
  19. * \brief Calculate control sum of binary buffer
  20. */
  21. int nmea_calc_crc(const char *buff, int buff_sz)
  22. {
  23. int chsum = ,
  24. it;
  25.  
  26. for(it = ; it < buff_sz; ++it)
  27. chsum ^= (int)buff[it];
  28.  
  29. return chsum;
  30. }
  31.  
  32. /**
  33. * \brief Convert string to number
  34. */
  35. int nmea_atoi(const char *str, int str_sz, int radix)
  36. {
  37. char *tmp_ptr;
  38. char buff[NMEA_CONVSTR_BUF];
  39. int res = ;
  40.  
  41. if(str_sz < NMEA_CONVSTR_BUF)
  42. {
  43. memcpy(&buff[], str, str_sz);
  44. buff[str_sz] = '\0';
  45. res = strtol(&buff[], &tmp_ptr, radix);
  46. }
  47.  
  48. return res;
  49. }
  50.  
  51. /**
  52. * \brief Convert string to fraction number
  53. */
  54. double nmea_atof(const char *str, int str_sz)
  55. {
  56. char *tmp_ptr;
  57. char buff[NMEA_CONVSTR_BUF];
  58. double res = ;
  59.  
  60. if(str_sz < NMEA_CONVSTR_BUF)
  61. {
  62. memcpy(&buff[], str, str_sz);
  63. buff[str_sz] = '\0';
  64. res = strtod(&buff[], &tmp_ptr);
  65. }
  66.  
  67. return res;
  68. }
  69.  
  70. /**
  71. * \brief Analyse string (specificate for NMEA sentences)
  72. */
  73. int nmea_scanf(const char *buff, int buff_sz, const char *format, ...)
  74. {
  75. const char *beg_tok;
  76. const char *end_buf = buff + buff_sz;
  77.  
  78. va_list arg_ptr;
  79. int tok_type = NMEA_TOKS_COMPARE;
  80. int width = ;
  81. const char *beg_fmt = ;
  82. int snum = , unum = ;
  83.  
  84. int tok_count = ;
  85. void *parg_target;
  86.  
  87. va_start(arg_ptr, format);
  88.  
  89. for(; *format && buff < end_buf; ++format)
  90. {
  91. switch(tok_type)
  92. {
  93. case NMEA_TOKS_COMPARE:
  94. if('%' == *format)
  95. tok_type = NMEA_TOKS_PERCENT;
  96. else if(*buff++ != *format)
  97. goto fail;
  98. break;
  99. case NMEA_TOKS_PERCENT:
  100. width = ;
  101. beg_fmt = format;
  102. tok_type = NMEA_TOKS_WIDTH;
  103. case NMEA_TOKS_WIDTH:
  104. if(isdigit(*format))
  105. break;
  106. {
  107. tok_type = NMEA_TOKS_TYPE;
  108. if(format > beg_fmt)
  109. width = nmea_atoi(beg_fmt, (int)(format - beg_fmt), );
  110. }
  111. case NMEA_TOKS_TYPE:
  112. beg_tok = buff;
  113.  
  114. if(!width && ('c' == *format || 'C' == *format) && *buff != format[])
  115. width = ;
  116.  
  117. if(width)
  118. {
  119. if(buff + width <= end_buf)
  120. buff += width;
  121. else
  122. goto fail;
  123. }
  124. else
  125. {
  126. if(!format[] || ( == (buff = (char *)memchr(buff, format[], end_buf - buff))))
  127. buff = end_buf;
  128. }
  129.  
  130. if(buff > end_buf)
  131. goto fail;
  132.  
  133. tok_type = NMEA_TOKS_COMPARE;
  134. tok_count++;
  135.  
  136. parg_target = ; width = (int)(buff - beg_tok);
  137.  
  138. switch(*format)
  139. {
  140. case 'c':
  141. case 'C':
  142. parg_target = (void *)va_arg(arg_ptr, char *);
  143. if(width && != (parg_target))
  144. *((char *)parg_target) = *beg_tok;
  145. break;
  146. case 's':
  147. case 'S':
  148. parg_target = (void *)va_arg(arg_ptr, char *);
  149. if(width && != (parg_target))
  150. {
  151. memcpy(parg_target, beg_tok, width);
  152. ((char *)parg_target)[width] = '\0';
  153. }
  154. break;
  155. case 'f':
  156. case 'g':
  157. case 'G':
  158. case 'e':
  159. case 'E':
  160. parg_target = (void *)va_arg(arg_ptr, double *);
  161. if(width && != (parg_target))
  162. *((double *)parg_target) = nmea_atof(beg_tok, width);
  163. break;
  164. };
  165.  
  166. if(parg_target)
  167. break;
  168. if( == (parg_target = (void *)va_arg(arg_ptr, int *)))
  169. break;
  170. if(!width)
  171. break;
  172.  
  173. switch(*format)
  174. {
  175. case 'd':
  176. case 'i':
  177. snum = nmea_atoi(beg_tok, width, );
  178. memcpy(parg_target, &snum, sizeof(int));
  179. break;
  180. case 'u':
  181. unum = nmea_atoi(beg_tok, width, );
  182. memcpy(parg_target, &unum, sizeof(unsigned int));
  183. break;
  184. case 'x':
  185. case 'X':
  186. unum = nmea_atoi(beg_tok, width, );
  187. memcpy(parg_target, &unum, sizeof(unsigned int));
  188. break;
  189. case 'o':
  190. unum = nmea_atoi(beg_tok, width, );
  191. memcpy(parg_target, &unum, sizeof(unsigned int));
  192. break;
  193. default:
  194. goto fail;
  195. };
  196.  
  197. break;
  198. };
  199. }
  200.  
  201. fail:
  202.  
  203. va_end(arg_ptr);
  204.  
  205. return tok_count;
  206. }
  1.  
  1. #ifndef __TOK_H__
  2. #define __TOK_H__
  3.  
  4. //#include "config.h"
  5.  
  6. #ifdef __cplusplus
  7. extern "C" {
  8. #endif
  9.  
  10. #define NMEA_CONVSTR_BUF (256)
  11.  
  12. int nmea_calc_crc(const char *buff, int buff_sz);
  13. int nmea_atoi(const char *str, int str_sz, int radix);
  14. double nmea_atof(const char *str, int str_sz);
  15. int nmea_printf(char *buff, int buff_sz, const char *format, ...);
  16. int nmea_scanf(const char *buff, int buff_sz, const char *format, ...);
  17.  
  18. #ifdef __cplusplus
  19. }
  20. #endif
  21.  
  22. #endif /* __NMEA_TOK_H__ */
  1.  

GPS数据解析的更多相关文章

  1. GPS数据包格式及数据包解析

    GPS数据包解析 GPS数据包解析 目的 GPS数据类型及格式 数据格式 数据解释 解析代码 结构体定义 GPRMC解析函数 GPGGA解析函数 测试样例输出 gps数据包格式 gps数据解析 车联网 ...

  2. 4.4 使用STM32控制MC20进行GPS帧数据解析

    需要准备的硬件 MC20开发板 1个 https://item.taobao.com/item.htm?id=562661881042 GSM/GPRS天线 1根 https://item.taoba ...

  3. 2.4 使用ARDUINO控制MC20进行GPS数据的获取和解析

    需要准备的硬件 MC20开发板 1个 https://item.taobao.com/item.htm?id=562661881042 GSM/GPRS天线 1根 https://item.taoba ...

  4. selenuim自动化爬取汽车在线谷米爱车网车辆GPS数据爬虫

    #为了实时获取车辆信息,以及为了后面进行行使轨迹绘图,写了一个基于selelnium的爬虫爬取了车辆gps数据. #在这里发现selenium可以很好的实现网页解析和处理js处理 #导包 import ...

  5. 大数据学习day39----数据仓库02------1. log4j 2. 父子maven工程(子spring项目的创建)3.项目开发(埋点日志预处理-json数据解析、清洗过滤、数据集成实现、uid回补)

    1. log4j(具体见log4j文档) log4j是一个java系统中用于输出日志信息的工具.log4j可以将日志定义成多种级别:ERROR  /  WARN  /  INFO  /  DEBUG ...

  6. C#-正则,常用几种数据解析-端午快乐

    在等待几个小时就是端午节了,这里预祝各位节日快乐. 这里分享的是几个在C#中常用的正则解析数据写法,其实就是Regex类,至于正则的匹配格式,请仔细阅读正则的api文档,此处不具体说明,谢谢. 开始吧 ...

  7. 通读AFN①--从创建manager到数据解析完毕

    流程梳理 今天开始会写几篇关于AFN源码解读的一些Blog,首先要梳理一下AFN的整体结构(主要是讨论2.x版本的Session访问模块): 我们先看看我们最常用的一段代码: AFHTTPSessio ...

  8. GPS数据读取与处理

    GPS数据读取与处理 GPS模块简介 SiRF芯片在2004年发布的最新的第三代芯片SiRFstar III(GSW 3.0/3.1),使得民用GPS芯片在性能方面登上了一个顶峰,灵敏度比以前的产品大 ...

  9. android基础(五)网络数据解析方法

    在网络上传输数据时最常用的方法有两种:XML和JSON,下面就对这两种类型的数据解析进行讲解. 一.XML数据解析 在Android中,常见的XML解析器分别为SAX解析器.DOM解析器和PULL解析 ...

随机推荐

  1. Nginx虚拟主机配置教程

    说明:配置之前先把域名解析到服务器IP地址上 站点1:bbs.osyunwei.com  程序所在目录/data/osyunwei/bbs 站点2:sns.osyunwei.com  程序所在目录/d ...

  2. Android 使用Spinner实现下拉列表

    课程目标1.了解Spinner下拉列表的使用和功能2.学会使用系统默认的Spinner3.学会使用自定义样式的Spinner 执行步骤第一步:添加一个下拉列表项的list,这里添加的项就是下拉列表的菜 ...

  3. 广义表操作 (ava实现)——广义表深度、广义表长度、打印广义表信息

    广义表是对线性表的扩展——线性表存储的所有的数据都是原子的(一个数或者不可分割的结构),且所有的数据类型相同.而广义表是允许线性表容纳自身结构的数据结构. 广义表定义: 广义表是由n个元素组成的序列: ...

  4. LeetCode——Single Number II

    Description: Given an array of integers, every element appears three times except for one. Find that ...

  5. FlipClock.js时钟,计数,3D翻转插件

    1.FlipClock.js能够自动定义计数,时钟的翻牌效果,调用简单,下面简单记录下用法 2.官网地址:http://www.flipclockjs.com/ 3.调用2个文件 <link h ...

  6. 【BZOJ5102】[POI2018]Prawnicy 堆

    [BZOJ5102][POI2018]Prawnicy Description 定义一个区间(l,r)的长度为r-l,空区间的长度为0. 给定数轴上n个区间,请选择其中恰好k个区间,使得交集的长度最大 ...

  7. 【深拷贝VS浅拷贝】------【巷子】

    1.回顾 数据传递的方法: 值传递:基本数据类型的数据不会发改变,因为基本数据类型一般存放在栈里面,值传递只是将数据拷贝了一份给另一个变量 引用传递:会改变内存中的数据,因为引用类型的数据都存放在堆里 ...

  8. 【vue】---Object.defineProperty基本使用---【巷子】

    1.object.defineProperty 给一个对象定义一个新的属性或者在修改一个对象现有的属性,并返回这个对象 语法: Object.defineProperty(参数1,参数2,参数3) 参 ...

  9. MVC认识

    1.ASP.NET两种开发模式的简单比较(WebForm和MVC) (1)WebForm开发模式 当用户输入网址https://i.cnblogs.com/EditPosts.aspx?opt=1进行 ...

  10. 容斥原理解决某个区间[1,n]闭区间与m互质数数量问题

    首先贴出代码(闭区间[1,n]范围内和m互质的数) 代码: int solve(II n,II m){ vector<II>p; ;i*i<=m;i++){ ){ p.push_ba ...