干货 | 分享一种有趣的数据解析方法

原创 嵌入式大杂烩 2021-06-10 22:30

点击上方「嵌入式大杂烩」,选择「置顶公众号」第一时间查看嵌入式笔记!

本片笔记是一篇开发小结,总结GPS数据的接收、解析示例,以实例为基础分享一些思考过程:

GPS数据协议

常用的GPS模块大多采用NMEA-0183 协议,目前业已成了GPS导航设备统一的RTCM(Radio Technical Commission for Maritime services)标准协议。

NMEA-0183 是美国国家海洋电子协会(National Marine Electronics Association)所指定的标准规格,这一标准制订所有航海电子仪器间的通讯标准,其中包含传输资料的格式以及传输资料的通讯协议。

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

帧格式形如:

$aaccc,ddd,ddd,…,ddd*hh(CR)(LF)

GPS帧数据种类大致如下:

实际应用中,并不是所有数据都完全用得上,我们可以根据需要选择所需要的数据。

下面我们以$GPGGA数据为例分享接收、解析方法。

$GPGGA 语句的基本格式如下:

$GPGGA,<1>,<2>,<3>,<4>,<5>,<6>,<7>,<8>,<9>,<10>,<11>,<12>,<13>,<14>*hh<CR><LF>

举例如下:

$GPGGA,082006.000,3852.9276,N,11527.4283,E,1,08,1.0,20.6,M,,,,0000*35

GPS数据接收

GPS模块使用串口通信,在解析之前当然需要先接收数据。我这里是在嵌入式Linux平台下做的接收,读串口的接口如:

int uart_read(void *data, int data_len, long time_out);

下面分享我在实际应用中的三种接收方法:

方法一:粗略法

为了能快速验证数据解析、跑通整个过程,可以先使用粗略的方法获取数据。粗略法我们可以先不用考虑一帧数据的实际字节数,我们先大致设置一个用于解析的缓冲数组,如:

char rx_gps_data[512];

uart_read每次读到的字节数与线程挂起时间有关,粗略法我们大致设置一个串口接收缓冲数组,如:

char uart_rx_buf[64];

这时候需要把每次收到的uart_rx_buf里的内容自己拼接一下,存放到rx_gps_data中,再去做解析。

粗略法可以用于快速验证数据解析、跑通整个过程,缺点就是uart_rx_buf、rx_gps_data设置得不够合理的话可能会破坏掉大量的数据帧。

一般我都比较习惯地先快速调通整个流程,再慢慢做优化。

方法二:状态机法

上面地粗略法可能会破坏掉一些数据帧,另外,代码结构可能不够清晰。针对这些问题做改进,使用状态机来接收。一字节一字节地接收,接收完完整一帧数据之后再去做解析。

代码如:

// GGA所有状态(GGA数据示例:$GPGGA,023543.00,2308.28715,N,11322.09875,E,1,06,1.49,41.6,M,-5.3,M,,*7D)
#define GGA_STATE_START     0  // $
#define GGA_STATE_HEAD1_G   1  // G
#define GGA_STATE_HEAD2_P   2  // P
#define GGA_STATE_HEAD3_G   3  // G
#define GGA_STATE_HEAD4_G   4  // G
#define GGA_STATE_HEAD5_A   5  // A
#define GGA_STATE_DATA      6  // ,023543.00,2308.28715,N,11322.09875,E,1,06,1.49,41.6,M,-5.3,M,,*
#define GGA_STATE_CHECK0    7  // 7
#define GGA_STATE_CHECK1    8  // D

static uint16_t gga_len = 0;
static uint8_t gga_state = GGA_STATE_START;
static void gps_gga_data_get(char in_data)
{
 switch (gga_state)
 {
  case GGA_STATE_START:
   if ('$' == in_data)
   {
    gga_len = 0;
    memset(rx_gps_gga_data, 0, GGA_DATA_MAX_LEN);
    rx_gps_gga_data[gga_len++] = in_data;
    gga_state = GGA_STATE_HEAD1_G;
   }
   else
   {
    gga_state = GGA_STATE_START;
   }
   break;
   
  case GGA_STATE_HEAD1_G:
   if ('G' == in_data)
   {
    rx_gps_gga_data[gga_len++] = in_data;
    gga_state = GGA_STATE_HEAD2_P;
   }
   else
   {
    gga_state = GGA_STATE_START;
   }
   break;
   
  case GGA_STATE_HEAD2_P:
   if ('P' == in_data)
   {
    rx_gps_gga_data[gga_len++] = in_data;
    gga_state = GGA_STATE_HEAD3_G;
   }
   else
   {
    gga_state = GGA_STATE_START;
   }
   break;
   
  case GGA_STATE_HEAD3_G:
   if ('G' == in_data)
   {
    rx_gps_gga_data[gga_len++] = in_data;
    gga_state = GGA_STATE_HEAD4_G;
   }
   else
   {
    gga_state = GGA_STATE_START;
   }
   break;
   
  case GGA_STATE_HEAD4_G:
   if ('G' == in_data)
   {
    rx_gps_gga_data[gga_len++] = in_data;
    gga_state = GGA_STATE_HEAD5_A;
   }
   else
   {
    gga_state = GGA_STATE_START;
   }
   break;
   
  case GGA_STATE_HEAD5_A:
   if ('A' == in_data)
   {
    rx_gps_gga_data[gga_len++] = in_data;
    gga_state = GGA_STATE_DATA;
   }
   else
   {
    gga_state = GGA_STATE_START;
   }
   break;
   
  case GGA_STATE_DATA:
   if ('*' == in_data)
   {
    rx_gps_gga_data[gga_len++] = in_data;
    gga_state = GGA_STATE_CHECK0;
   }
   else
   {
    rx_gps_gga_data[gga_len++] = in_data;
    if (gga_len > GGA_DATA_MAX_LEN)
    {
     gga_state = GGA_STATE_START;
    }
    else
    {
     gga_state = GGA_STATE_DATA;
    }
   }
   break;
   
  case GGA_STATE_CHECK0:
   rx_gps_gga_data[gga_len++] = in_data;
   gga_state = GGA_STATE_CHECK1;
   break;
   
  case GGA_STATE_CHECK1:
   rx_gps_gga_data[gga_len++] = in_data;
   printf("gga data : %s\n", rx_gps_gga_data);
   gga_state = GGA_STATE_START;
   break;
   
  default:
   break;
 }
}

这样就可以完整地接收到gga数据,每次走到GGA_STATE_CHECK1状态时的rx_gps_gga_data就是完整的gga数据,这时候就可以进行解析了,可以在这一步设置一个标志变量表明gga数据已经完全接收完毕,直到数据接收完毕了才做解析。

这种方法虽然可以比较好地接收数据,在单片机下很好用。但是在这里,相同的线程挂起时间情况下,每次uart_read只获取一个字节,这样会损耗一定的接收效率,有点拆东墙补西墙的感觉。

在我们这边的应用中,与算法所需的时序要求有冲突了,所以只能再想想其它方法。下面看看方法三。

方法三:时间戳法

这种方法需要明确每一帧数据包含有什么数据,以及数据输出的频率是多少。在相同的线程挂起时间情况下,先把用于uart_read接收数据的buffer设置得稍微大一点,看每一次最多能读取到多少个字节得数据以及读完一帧数据需要读几次串口数据。

然后我们可以通过时间来区分每一帧数据及每一包串口数据,该重新组包地就重新组包。

例如:每帧数据间隔200ms,线程挂起时间10ms,一帧数据有130字节,一帧数据由1包、2包串口数据组成。

可以通过时间戳来判断每一包之间是数据帧之间的间隔还是每一帧数据里的两个数据包之间地间隔,再做相应的逻辑处理即可很好地接收数据。

GPS数据解析

gps数据怎么解析呢?

方法可能很多,我们先看一下正点原子的解析方法:

大概分为两步,第一步先获取逗号的位置确定某个需要解析地字段,然后再将相应字段的字符串数据转换成数字。

这里分享一种简单实用的解析方法,思路与上面差不多,但是相对比较简单清晰些:

static bool gps_gga_data_parse(st_gps_gga_def *out_data, char *in_data)
{
 bool ret = FALSE;
 char *p_gga = in_data;
 
 if (NULL == p_gga)
 {
  return ret;
 }
 
 if (NULL != (p_gga = strstr(p_gga, "$GNGGA")))
 {
  printf("gga data : %s\n", p_gga);
  
  /* 数据校验 */
  if (TRUE == data_check(p_gga))
  {
   printf("gga data check success!\n");
   
   /* 解析出字符串 */
   printf("gga data parse: \n");
   for (int i = 0; i < GGA_STR_MAX; i++)
   {
    sscanf(p_gga, "%[^,]", gps_gga_str[i]);
    printf("%s\n", gps_gga_str[i]);
    p_gga = p_gga + (strlen(gps_gga_str[i]) + 1);
   }
   
   /* 字符串转数字 */
   out_data->latitude = atof(gps_gga_str[STR_LATITUDE]);
   out_data->longitude = atof(gps_gga_str[STR_LONGITUDE]);
   out_data->time = atof(gps_gga_str[STR_TIME]);
   out_data->quality = atof(gps_gga_str[STR_QUALITY]);
   ret = TRUE;
  }
  else
  {
   printf("gga data check error!\n");
  }
 }
    
    return ret;
}

这里使用sscanf+正则表达式来做解析。

sscanf(p_gga, "%[^,]", gps_gga_str[i]);

sscanf函数在做字符串相关解析时很好用,这里配合正则表达式来使用,上面这一句代码的意思就是从p_gga中取逗号前面的数据存放到gps_gga_str[i]中,因为gga数据都是用逗号隔开的,循环几次就可以把所有数据解析出来,很方便。

正则表达式学习资源如:

1、https://deerchao.cn/tutorials/regex/regex.htm
2、https://www.runoob.com/regexp/regexp-syntax.html

下面再看一下,sscanf+正则表达式的几种简单用法:

「1、取指定长度的字符串。」

如在下例中,取最大长度为4字节的字符串。

sscanf("123456 ""%4s", str);

「2、  取到指定字符为止的字符串。」

如在下例中,取遇到空格为止字符串。

sscanf("123456 abcdedf""%[^ ]", str);

「3、取仅包含指定字符集的字符串。」

如在下例中,取仅包含19和小写字母的字符串。

sscanf("123456abcdedfBCDEF""%[1-9a-z]", str);

「4、取到指定字符集为止的字符串。」

如在下例中,取遇到大写字母为止的字符串。

scanf("123456abcdedfBCDEF""%[^A-Z]", str);

sscanf+简单、易理解的正则表达式的方法有时候可以帮助我们很方便地进行字符串数据地解析。sscanf+复杂的正则表达式不太建议使用,因为代码可读性太差了。

另外,使用sscanf+正则表达式时有必要写点注释,有见过这种方式还好,有些后面看你代码的人可能没接触过正则表达式可能一时半会儿理解不了。

我之前大三出去实习的时候,在公司里就看到这样的代码,那时候知识储备还不够,第一次看到sscanf+正则表达式这种解析方法,但是搜索又搜索不到相关答案,很苦恼。所以,平时有必要写一些注释,利人利己。

参考:

1、正点原子《ATK-NEO-6M GPS模块》资料。

2、https://blog.csdn.net/absurd/article/details/1177092

猜你喜欢:

嵌入式大杂烩文章精选

MDK下一代开发工具——Keil Studio

为什么会有TCP/IP协议?

LeetCode刻意练习

在公众号聊天界面回复1024,可获取嵌入式资源;回复 ,可查看文章汇总。

文章都看完了不点个

嵌入式大杂烩 专注于嵌入式技术,包括但不限于C/C++、嵌入式、物联网、Linux等编程学习笔记,同时,内包含大量的学习资源。欢迎关注,一同交流学习,共同进步!
评论
  • 美国加州CEC能效跟DOE能效有什么区别?CEC/DOE是什么关系?美国加州CEC能效跟DOE能效有什么区别?CEC/DOE是什么关系?‌美国加州CEC能效认证与美国DOE能效认证在多个方面存在显著差异‌。认证范围和适用地区‌CEC能效认证‌:仅适用于在加利福尼亚州销售的电器产品。CEC认证的范围包括制冷设备、房间空调、中央空调、便携式空调、加热器、热水器、游泳池加热器、卫浴配件、光源、应急灯具、交通信号模块、灯具、洗碗机、洗衣机、干衣机、烹饪器具、电机和压缩机、变压器、外置电源、消费类电子设备
    张工nx808593 2025-02-27 18:04 128浏览
  •           近日受某专业机构邀请,参加了官方举办的《广东省科技创新条例》宣讲会。在与会之前,作为一名技术工作者一直认为技术的法例都是保密和侵权方面的,而潜意识中感觉法律有束缚创新工作的进行可能。通过一个上午学习新法,对广东省的科技创新有了新的认识。广东是改革的前沿阵地,是科技创新的沃土,企业是创新的主要个体。《广东省科技创新条例》是广东省为促进科技创新、推动高质量发展而制定的地方性法规,主要内容包括: 总则:明确立法目
    广州铁金刚 2025-02-28 10:14 111浏览
  • 一、VSM的基本原理震动样品磁强计(Vibrating Sample Magnetometer,简称VSM)是一种灵敏且高效的磁性测量仪器。其基本工作原理是利用震动样品在探测线圈中引起的变化磁场来产生感应电压,这个感应电压与样品的磁矩成正比。因此,通过测量这个感应电压,我们就能够精确地确定样品的磁矩。在VSM中,被测量的样品通常被固定在一个震动头上,并以一定的频率和振幅震动。这种震动在探测线圈中引起了变化的磁通量,从而产生了一个交流电信号。这个信号的幅度和样品的磁矩有着直接的关系。因此,通过仔细
    锦正茂科技 2025-02-28 13:30 108浏览
  • Matter 协议,原名 CHIP(Connected Home over IP),是由苹果、谷歌、亚马逊和三星等科技巨头联合ZigBee联盟(现连接标准联盟CSA)共同推出的一套基于IP协议的智能家居连接标准,旨在打破智能家居设备之间的 “语言障碍”,实现真正的互联互通。然而,目标与现实之间总有落差,前期阶段的Matter 协议由于设备支持类型有限、设备生态协同滞后以及设备通信协议割裂等原因,并未能彻底消除智能家居中的“设备孤岛”现象,但随着2025年的到来,这些现象都将得到完美的解决。近期,
    华普微HOPERF 2025-02-27 10:32 244浏览
  • RGB灯光无法同步?细致的动态光效设定反而成为产品客诉来源!随着科技的进步和消费者需求变化,电脑接口设备单一功能性已无法满足市场需求,因此在产品上增加「动态光效」的形式便应运而生,藉此吸引消费者目光。这种RGB灯光效果,不仅能增强电脑周边产品的视觉吸引力,还能为用户提供个性化的体验,展现独特自我风格。如今,笔记本电脑、键盘、鼠标、鼠标垫、耳机、显示器等多种电脑接口设备多数已配备动态光效。这些设备的灯光效果会随着音乐节奏、游戏情节或使用者的设置而变化。想象一个画面,当一名游戏玩家,按下电源开关,整
    百佳泰测试实验室 2025-02-27 14:15 140浏览
  • 应用趋势与客户需求,AI PC的未来展望随着人工智能(AI)技术的日益成熟,AI PC(人工智能个人电脑)逐渐成为消费者和企业工作中的重要工具。这类产品集成了最新的AI处理器,如NPU、CPU和GPU,并具备许多智能化功能,为用户带来更高效且直观的操作体验。AI PC的目标是提升工作和日常生活的效率,通过深度学习与自然语言处理等技术,实现更流畅的多任务处理、实时翻译、语音助手、图像生成等功能,满足现代用户对生产力和娱乐的双重需求。随着各行各业对数字转型需求的增长,AI PC也开始在各个领域中显示
    百佳泰测试实验室 2025-02-27 14:08 267浏览
  • 更多生命体征指标风靡的背后都只有一个原因:更多人将健康排在人生第一顺位!“AGEs,也就是晚期糖基化终末产物,英文名Advanced Glycation End-products,是存在于我们体内的一种代谢产物” 艾迈斯欧司朗亚太区健康监测高级市场经理王亚琴说道,“相信业内的朋友都会有关注,最近该指标的热度很高,它可以用来评估人的生活方式是否健康。”据悉,AGEs是可穿戴健康监测领域的一个“萌新”指标,近来备受关注。如果站在学术角度来理解它,那么AGEs是在非酶促条件下,蛋白质、氨基酸
    艾迈斯欧司朗 2025-02-27 14:50 426浏览
  • 1,微软下载免费Visual Studio Code2,安装C/C++插件,如果无法直接点击下载, 可以选择手动install from VSIX:ms-vscode.cpptools-1.23.6@win32-x64.vsix3,安装C/C++编译器MniGW (MinGW在 Windows 环境下提供类似于 Unix/Linux 环境下的开发工具,使开发者能够轻松地在 Windows 上编写和编译 C、C++ 等程序.)4,C/C++插件扩展设置中添加Include Path 5,
    黎查 2025-02-28 14:39 151浏览
  • 在物联网领域中,无线射频技术作为设备间通信的核心手段,已深度渗透工业自动化、智慧城市及智能家居等多元场景。然而,随着物联网设备接入规模的不断扩大,如何降低运维成本,提升通信数据的传输速度和响应时间,实现更广泛、更稳定的覆盖已成为当前亟待解决的系统性难题。SoC无线收发模块-RFM25A12在此背景下,华普微创新推出了一款高性能、远距离与高性价比的Sub-GHz无线SoC收发模块RFM25A12,旨在提升射频性能以满足行业中日益增长与复杂的设备互联需求。值得一提的是,RFM25A12还支持Wi-S
    华普微HOPERF 2025-02-28 09:06 166浏览
  •         近日,广电计量在聚焦离子束(FIB)领域编写的专业著作《聚焦离子束:失效分析》正式出版,填补了国内聚焦离子束领域实践性专业书籍的空白,为该领域的技术发展与知识传播提供了重要助力。         随着芯片技术不断发展,芯片的集成度越来越高,结构也日益复杂。这使得传统的失效分析方法面临巨大挑战。FIB技术的出现,为芯片失效分析带来了新的解决方案。它能够在纳米尺度上对芯片进行精确加工和分析。当芯
    广电计量 2025-02-28 09:15 141浏览
  • 振动样品磁强计是一种用于测量材料磁性的精密仪器,广泛应用于科研、工业检测等领域。然而,其测量准确度会受到多种因素的影响,下面我们将逐一分析这些因素。一、温度因素温度是影响振动样品磁强计测量准确度的重要因素之一。随着温度的变化,材料的磁性也会发生变化,从而影响测量结果的准确性。因此,在进行磁性测量时,应确保恒温环境,以减少温度波动对测量结果的影响。二、样品制备样品的制备过程同样会影响振动样品磁强计的测量准确度。样品的形状、尺寸和表面处理等因素都会对测量结果产生影响。为了确保测量准确度,应严格按照规
    锦正茂科技 2025-02-28 14:05 155浏览
  • 在2024年的科技征程中,具身智能的发展已成为全球关注的焦点。从实验室到现实应用,这一领域正以前所未有的速度推进,改写着人类与机器的互动边界。这一年,我们见证了具身智能技术的突破与变革,它不仅落地各行各业,带来新的机遇,更在深刻影响着我们的生活方式和思维方式。随着相关技术的飞速发展,具身智能不再仅仅是一个技术概念,更像是一把神奇的钥匙。身后的众多行业,无论愿意与否,都像是被卷入一场伟大变革浪潮中的船只,注定要被这股汹涌的力量重塑航向。01为什么是具身智能?为什么在中国?最近,中国具身智能行业的进
    艾迈斯欧司朗 2025-02-28 15:45 243浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦