0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

一种单片机数据解析方法

GReq_mcu168 来源:CSDN 作者:CSDN 2022-04-01 10:16 次阅读

前阵子一朋友使用单片机与某外设进行通信时,外设返回的是一堆格式如下的数据:

AA AA 04 80 02 00 02 7B AA AA 04 80 02 00 08 75 AA AA 04 80 02 00 9B E2 AA AA 04 80 02 00 F6 87 AA AA 04 80 02 00 EC 91

其中 AA AA 04 80 02 是数据校验头,后面三位是有效数据,问我怎么从外设不断返回的数据中取出有效的数据。

对于这种问题最容易想到的就是使用一个标志位用于标志当前正解析到一帧数据的第几位,然后判断当前接收的数据是否与校验数据一致,如果一致则将标志位加一,否则将标志位置0重新判断,使用这种方法解析数据的代码如下:

if(flag==0)
{
if(tempData==0xAA)
flag++;
else
flag=0;
}
elseif(flag==1)
{
if(tempData==0xAA)
flag++;
else
flag=0;
}
elseif(flag==2)
{
if(tempData==0x04)
flag++;
else
flag=0;
}
elseif(flag==3)
{
if(tempData==0x80)
flag++;
else
flag=0;
}
elseif(flag==4)
{
if(tempData==0x02)
flag++;
else
flag=0;
}
elseif(flag==5||flag==6||flag==7)
{
data[flag-5]=tempData;
flag=(flag==7)?0:flag+1;
}

使用上述方法是最容易想到的也是最简单的方法了,百度了一下基本上也都是使用类似的方法进行数据解析,但是使用这种方法有如下几个缺点:

1、 大量使用了判断,容易导致出现逻辑混乱。

2、 代码重复率高,抽象程度低。从上述代码可以看到一大堆代码仅仅是判断的数据不同,其他代码都完全一致。

3、 代码可复用性差。写好的代码无法用在其他类似的外设上,如果有多个外设就需要编写多份类似的代码。

4、 可扩展性低。如果外设还有一个数据校验尾需要校验或者数据校验头发生改变,就需要再次写多个判断重新用于校验,无法在原有的代码上进行扩展。

5、 容易出现误判 。

对此,这里提出了一种新的解决方案,可以通用与所有类似的数据解析,原理如下:

使用一个固定容量的队列用来缓存接收到的数据,队列容量等于一帧数据的大小,每来一个数据就将数据往队列里面加,当完整接收到一帧数据时此时队列中的全部数据也就是一帧完整的数据,因此只需要判断队列是否是数据校验头,队列尾是否是数据校验尾就可以得知当前是否已经接收到了一帧完整的数据,然后在将数据从队列中取出即可。原理图如下:

每来一个数据就往队列里面加:

3b5c029a-b145-11ec-aa7f-dac502259ad0.png

当接收到一帧完整数据时队列头和数据校验头重合:

3b6e2ed4-b145-11ec-aa7f-dac502259ad0.png

此时只需要从队列中取出有效数据即可。

如果有数据尾校验,仅仅只需要添加一个校验尾即可,如下图所示:

3b93d3dc-b145-11ec-aa7f-dac502259ad0.png

好,分析结束,开始编码。

首先需要一个队列,为了保证通用性,队列底层使用类似于双向链表的实现(当然也可以使用数组实现),需要封装的结构有队列容量、队列大小、队头节点和队尾节点,需要实现的操作有队列初始化、数据入队、数据出队、清空队列和释放队列,具体代码如下:

/*queue.h*/

#ifndef_QUEUE_H_
#define_QUEUE_H_

#ifndefNULL
#defineNULL((void*)0)
#endif

typedefunsignedcharuint8;

/*队列节点*/
typedefstructNode
{
uint8data;
structNode*pre_node;
structNode*next_node;
}Node;

/*队列结构*/
typedefstructQueue
{
uint8capacity;//队列总容量
uint8size;//当前队列大小
Node*front;//队列头节点
Node*back;//队列尾节点
}Queue;

/*初始化一个队列*/
Queue*init_queue(uint8_capacity);
/*数据入队*/
uint8en_queue(Queue*_queue,uint8_data);
/*数据出队*/
uint8de_queue(Queue*_queue);
/*清空队列*/
voidclear_queue(Queue*_queue);
/*释放队列*/
voidrelease_queue(Queue*_queue);

#endif
/*queue.c*/

#include
#include"parser.h"

/**
*初始化一个队列
*
*@_capacity:队列总容量
*/
Queue*init_queue(uint8_capacity)
{
Queue*queue=(Queue*)malloc(sizeof(Queue));
queue->capacity=_capacity;
queue->size=0;
returnqueue;
}

/**
*数据入队
*
*@_queue:队列
*@_data:数据
**/
uint8en_queue(Queue*_queue,uint8_data)
{
if(_queue->size< _queue->capacity)
{
Node*node=(Node*)malloc(sizeof(Node));
node->data=_data;
node->next_node=NULL;

if(_queue->size==0)
{
node->pre_node=NULL;
_queue->back=node;
_queue->front=_queue->back;
}
else
{
node->pre_node=_queue->back;

_queue->back->next_node=node;
_queue->back=_queue->back->next_node;
}
_queue->size++;
}
else
{
Node*temp_node=_queue->front->next_node;
_queue->front->pre_node=_queue->back;
_queue->back->next_node=_queue->front;
_queue->back=_queue->back->next_node;
_queue->back->data=_data;
_queue->back->next_node=NULL;
_queue->front=temp_node;
}
return_queue->size-1;
}

/**
*数据出队
*
*@_queue:队列
*
*@return:出队的数据
*/
uint8de_queue(Queue*_queue)
{
uint8old_data=0;

if(_queue->size>0)
{
old_data=_queue->front->data;
if(_queue->size==1)
{
free(_queue->front);
_queue->front=NULL;
_queue->back=NULL;
}
else
{
_queue->front=_queue->front->next_node;
free(_queue->front->pre_node);
_queue->front->pre_node=NULL;
}
_queue->size--;
}
returnold_data;
}

/**
*清空队列
*
*@_queue:队列
*/
voidclear_queue(Queue*_queue)
{
while(_queue->size>0)
{
de_queue(_queue);
}
}

/**
*释放队列
*
*@_queue:队列
*/
voidrelease_queue(Queue*_queue)
{
clear_queue(_queue);
free(_queue);
_queue=NULL;
}

其次是解析器,需要封装的结构有解析数据队列、数据校验头、数据校验尾、解析结果以及指向解析结果的指针,需要实现的操作有解析器初始化、添加数据解析、获取解析结果、重置解析器和释放解析器,具体代码如下:

/*parser.h*/

#ifndef_PARSER_H_
#define_PARSER_H_

#include"queue.h"

typedefenum
{
RESULT_FALSE,
RESULT_TRUE
}ParserResult;

/*解析器结构*/
typedefstructDataParser
{
Queue*parser_queue;//数据解析队列
Node*resule_pointer;//解析结果数据指针
uint8*data_header;//数据校验头指针
uint8header_size;//数据校验头大小
uint8*data_footer;//数据校验尾指针
uint8footer_size;//数据校验尾大小
uint8result_size;//解析数据大小
ParserResultparserResult;//解析结果
}DataParser;

/*初始化一个解析器*/
DataParser*parser_init(uint8*_data_header,uint8_header_size,uint8*_data_footer,uint8_foot_size,uint8_data_frame_size);
/*将数据添加到解析器中进行解析*/
ParserResultparser_put_data(DataParser*_parser,uint8_data);
/*解析成功后从解析器中取出解析结果*/
intparser_get_data(DataParser*_parser,uint8_index);
/*重置解析器*/
voidparser_reset(DataParser*_parser);
/*释放解析器*/
voidparser_release(DataParser*_parser);

#endif
/*parser.c*/

#include
#include"parser.h"

/**
*初始化一个解析器
*
*@_data_header:数据头指针
*@_header_size:数据头大小
*@_data_footer:数据尾指针
*@_foot_size:数据尾大小
*@_data_frame_size:一帧完整数据的大小
*
*@return:解析器
*/
DataParser*parser_init(uint8*_data_header,uint8_header_size,uint8*_data_footer,uint8_foot_size,uint8_data_frame_size)
{
if((_header_size+_foot_size)>_data_frame_size||(_header_size+_foot_size)==0)
returnNULL;

DataParser*parser=(DataParser*)malloc(sizeof(DataParser));
parser->parser_queue=init_queue(_data_frame_size);
parser->resule_pointer=NULL;
parser->data_header=_data_header;
parser->header_size=_header_size;
parser->data_footer=_data_footer;
parser->footer_size=_foot_size;
parser->result_size=_data_frame_size-parser->header_size-parser->footer_size;
parser->parserResult=RESULT_FALSE;

while(_data_frame_size-->0)
{
en_queue(parser->parser_queue,0);
}

returnparser;
}

/**
*将数据添加到解析器中进行解析
*
*@_parser:解析器
*@_data:要解析的数据
*
*@return:当前解析结果,返回RESULT_TRUE代表成功解析出一帧数据
*/
ParserResultparser_put_data(DataParser*_parser,uint8_data)
{
uint8i;
Node*node;

if(_parser==NULL)
returnRESULT_FALSE;

en_queue(_parser->parser_queue,_data);

/*校验数据尾*/
node=_parser->parser_queue->back;
for(i=_parser->footer_size;i>0;i--)
{
if(node->data!=_parser->data_footer[i-1])
gotoDATA_FRAME_FALSE;
node=node->pre_node;
}

/*校验数据头*/
node=_parser->parser_queue->front;
for(i=0;i< _parser->header_size;i++)
{
if(node->data!=_parser->data_header[i])
gotoDATA_FRAME_FALSE;
node=node->next_node;
}

if(_parser->resule_pointer==NULL&&_parser->result_size>0)
_parser->resule_pointer=node;
if(_parser->parserResult!=RESULT_TRUE)
_parser->parserResult=RESULT_TRUE;
return_parser->parserResult;

DATA_FRAME_FALSE:
if(_parser->resule_pointer!=NULL)
_parser->resule_pointer=NULL;
if(_parser->parserResult!=RESULT_FALSE)
_parser->parserResult=RESULT_FALSE;
return_parser->parserResult;

}

/**
*解析成功后从解析器中取出解析结果
*
*@_parser:解析器
*@_index:解析结果集合中的第_index个数据
*
*@return:获取解析成功的数据,返回-1代表数据获取失败
*/
intparser_get_data(DataParser*_parser,uint8_index)
{
Node*node;
if(_parser==NULL
||_parser->parserResult!=RESULT_TRUE
||_index>=_parser->result_size
||_parser->resule_pointer==NULL)
return-1;
node=_parser->resule_pointer;
while(_index>0)
{
node=node->next_node;
_index--;
}
returnnode->data;
}

/**
*重置解析器
*
*@_parser:解析器
*/
voidparser_reset(DataParser*_parser)
{
uint8_data_frame_size;

if(_parser==NULL)
return;

_data_frame_size=_parser->parser_queue->size;
while(_data_frame_size-->0)
{
en_queue(_parser->parser_queue,0);
}
_parser->resule_pointer=NULL;
_parser->parserResult=RESULT_FALSE;
}

/**
*释放解析器
*
*@_parser:解析器
*/
voidparser_release(DataParser*_parser)
{
if(_parser==NULL)
return;
release_queue(_parser->parser_queue);
free(_parser);
_parser=NULL;
}

接下来编写测试代码测试一下:

/*main.c*/

#include
#include"parser.h"

intmain()
{
uint8i;
//数据头
uint8data_header[]={0xAA,0xAA,0x04,0x80,0x02};
//要解析的数据,测试用
uint8data[]={
0xAA,0xAA,0x04,0x80,0x02,0x00,0x02,0x7B,0xAA,0xAA,0x04,0x80,
0x02,0x00,0x08,0x75,0xAA,0xAA,0x04,0x80,0x02,0x00,0x9B,0xE2,
0xAA,0xAA,0x04,0x80,0x02,0x00,0xF6,0x87,0xAA,0xAA,0x04,0x80,
0x02,0x00,0xEC,0x91,0xAA,0xAA,0x04,0x80,0x02,0x01,0x15,0x67,
0xAA,0xAA,0x04,0x80,0x02,0x01,0x49,0x33,0xAA,0xAA,0x04,0x80,
0x02,0x00,0xE7,0x96,0xAA,0xAA,0x04,0x80,0x02,0x00,0x68,0x15,
0xAA,0xAA,0x04,0x80,0x02,0x00,0x3C,0x41,0xAA,0xAA,0x04,0x80,
0x02,0x00,0x66,0x17,0xAA,0xAA,0x04,0x80,0x02,0x00,0xA5,0xD8,
0xAA,0xAA,0x04,0x80,0x02,0x01,0x26,0x56,0xAA,0xAA,0x04,0x80,
0x02,0x01,0x73,0x09,0xAA,0xAA,0x04,0x80,0x02,0x01,0x64,0x18,
0xAA,0xAA,0x04,0x80,0x02,0x01,0x8B,0xF1,0xAA,0xAA,0x04,0x80,
0x02,0x01,0xC6,0xB6,0xAA,0xAA,0x04,0x80,0x02,0x01,0x7B,0x01,
0xAA,0xAA,0x04,0x80,0x02,0x00,0xCB,0xB2,0xAA,0xAA,0x04,0x80,
0x02,0x00,0x2C,0x51,0xAA,0xAA,0x04,0x80,0x02,0xFF,0xE5,0x99
};

/**
*初始化一个解析器
*第一个参数是数据头
*第二个参数是数据头长度
*第三个参数是数据尾指针
*第四个参数是数据尾大小
*第五个参数是一整帧数据的大小
*/
DataParser*data_parser=parser_init(data_header,sizeof(data_header),NULL,0,8);

//将要解析的数据逐个取出,添加到解析器中
for(i=0;i< sizeof(data);i++)
{
//解析数据,返回RESULT_TRUE代表成功解析出一组数据
if(parser_put_data(data_parser,data[i])==RESULT_TRUE)
{
printf("成功解析出一帧数据...
");

/*一位一位取出解析后的数据*/
printf("第一个数据是:0x%x
",parser_get_data(data_parser,0));
printf("第二个数据是:0x%x
",parser_get_data(data_parser,1));
printf("第三个数据是:0x%x


",parser_get_data(data_parser,2));
}
}

//当不再需要解析器时,应该把解析器释放掉,回收内存,避免造成内存泄漏
parser_release(data_parser);

return0;
}

测试结果如下:

3bb3cb9c-b145-11ec-aa7f-dac502259ad0.png

从上面可以看出,解析的结果与目标一致。

github地址:

https://github.com/528787067/DataFrameParser

审核编辑 :李倩


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 单片机
    +关注

    关注

    6030

    文章

    44505

    浏览量

    632340
  • 数据
    +关注

    关注

    8

    文章

    6853

    浏览量

    88765

原文标题:一种单片机数据解析方法

文章出处:【微信号:mcu168,微信公众号:硬件攻城狮】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    单片机调试常见问题与解决方法

    烧录到单片机时,烧录软件显示无法与单片机通信。 解决方法: 检查单片机的电源是否正常,确保电源电压在规定的范围内。 检查烧录线的连接是否正确,包括
    的头像 发表于 11-01 14:11 221次阅读

    如何实现51单片机与PC的串行通信

    的详细步骤和方法。 硬件连接 首先,我们需要将51单片机与PC的串行接口连接起来。通常,我们使用RS-232接口进行连接。RS-232接口是一种常用的串行通信接口,它使用9个引脚进行
    的头像 发表于 10-21 11:35 282次阅读

    单片机的中断机制

    单片机的中断机制是一种重要的处理方式,它允许单片机在执行主程序的过程中,能够暂停当前任务,转而处理外部或内部紧急事件。这种机制极大地提高了系统的响应速度和处理能力,使得单片机在各种应用
    的头像 发表于 10-17 18:03 305次阅读

    单片机io口的四工作状态

    )是单片机与外部世界进行数据交换的接口。每个I/O口可以配置为输入或输出状态,或者同时具备这两功能。I/O口的状态可以通过编程来控制,以满足不同的应用需求。 2. 单片机I/O口的工
    的头像 发表于 09-14 14:24 1037次阅读

    单片机的三总线结构

    单片机的三总线结构包括地址总线(Address Bus, AB)、数据总线(Data Bus, DB)和控制总线(Control Bus, CB)。这三总线在
    的头像 发表于 09-10 11:32 1449次阅读

    文读懂什么单片机:组成结构与应用

    欢迎来到单片机的世界,这是一种微小(但功能强大)的设备,改变了嵌入式系统的面貌。在本文中,您将了解单片机些基本知识、单片机的结构以及微处
    的头像 发表于 08-09 11:49 1594次阅读
    <b class='flag-5'>一</b>文读懂什么<b class='flag-5'>单片机</b>:组成结构与应用

    单片机是什么?单片机编程如何入门?

    单片机其实是一种很早的叫法了,最开始我们的前辈叫单板机或者叫单片机,因为那个时候单片机的处理能力还没有很强,现在我们所用的基本都是微型处理单元,也叫为控制器也称之为MCU
    的头像 发表于 03-28 08:03 1709次阅读
    <b class='flag-5'>单片机</b>是什么?<b class='flag-5'>单片机</b>编程如何入门?

    FH8A1‍8A是一种io型,完全静态,基于otp的控制器8位MCU单片机

    FH8A1‍8A是一种io型,完全静态,基于otp的控制器8位MCU单片机
    的头像 发表于 03-25 17:35 515次阅读

    单片机原理及应用是什么

    单片机(Microcontroller)是一种集成电路(IC)芯片,其中包括了中央处理器(CPU)、存储器和输入/输出(I/O)设备等功能模块。它是一种小型计算机系统,具有独立的工作能力和完整的计算
    的头像 发表于 01-12 15:28 2071次阅读

    单片机如何读取键盘数据

    单片机作为一种嵌入式系统,往往需要与外部设备进行交互。其中之的重要环节就是通过键盘读取输入的数据。在本文中,我们将详细介绍如何使用单片机
    的头像 发表于 01-04 17:09 1552次阅读

    一种面向标识公共递归解析节点的数据安全加固策略

    摘要 :为解决工业互联网标识解析体系公共递归解析节点信息透明、缺乏隐私数据保护和身份权限管理等问题,提出了一种面向标识公共递归解析节点的
    的头像 发表于 12-26 11:27 621次阅读
    <b class='flag-5'>一种</b>面向标识公共递归<b class='flag-5'>解析</b>节点的<b class='flag-5'>数据</b>安全加固策略

    单片机解析g代码的方法

    单片机一种完整的计算机系统,它集成了处理器、存储器、输入输出接口等基本的计算机组成部分。单片机应用广泛,可以用于控制各种电子设备。在单片机中,G代码是
    的头像 发表于 12-22 14:15 1598次阅读

    单片机串口通信的接收与发送

    单片机串口通信是一种常见的通信方式,它可以实现单片机与外部设备的数据交换。在实际应用中,单片机串口通信常用于与电脑、传感器、LCD显示屏等外
    的头像 发表于 12-20 14:03 3439次阅读

    你用过哪些编程语言开发单片机

    C语言是最常用的一种用于单片机的开发语言,也是一种高级编程语言,具有较好的可移植性和可读性。对于单片机,通常使用嵌入式C来进行开发。 举例:用C语言写
    发表于 12-04 10:18 1238次阅读

    分享一种单片机IO口测量外部电阻的方法

    在以前的有些单片机应用中,有的时候会遇到 AD 端口资源不够用的情况,但又需要测量电阻的大小。
    的头像 发表于 11-24 16:42 2162次阅读
    分享<b class='flag-5'>一种</b><b class='flag-5'>单片机</b>IO口测量外部电阻的<b class='flag-5'>方法</b>