|
路线栈欢迎您!
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
前阵子一朋友使用单片机与某外设进行通信时,外设返回的是一堆格式如下的数据:
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;
- }
- else if(flag == 1)
- {
- if(tempData == 0xAA)
- flag++;
- else
- flag = 0;
- }
- else if(flag == 2)
- {
- if(tempData == 0x04)
- flag++;
- else
- flag = 0;
- }
- else if(flag == 3)
- {
- if(tempData == 0x80)
- flag++;
- else
- flag = 0;
- }
- else if(flag == 4)
- {
- if(tempData == 0x02)
- flag++;
- else
- flag = 0;
- }
- else if(flag == 5 || flag == 6 || flag == 7)
- {
- data[flag-5] = tempData;
- flag = (flag == 7) ? 0 : flag+1;
- }
复制代码
使用上述方法是最容易想到的也是最简单的方法了,百度了一下基本上也都是使用类似的方法进行数据解析,但是使用这种方法有如下几个缺点:
1、 大量使用了判断,容易导致出现逻辑混乱。
2、 代码重复率高,抽象程度低。从上述代码可以看到一大堆代码仅仅是判断的数据不同,其他代码都完全一致。
3、 代码可复用性差。写好的代码无法用在其他类似的外设上,如果有多个外设就需要编写多份类似的代码。
4、 可扩展性低。如果外设还有一个数据校验尾需要校验或者数据校验头发生改变,就需要再次写多个判断重新用于校验,无法在原有的代码上进行扩展。
5、 容易出现误判 。
对此,这里提出了一种新的解决方案,可以通用与所有类似的数据解析,原理如下:
使用一个固定容量的队列用来缓存接收到的数据,队列容量等于一帧数据的大小,每来一个数据就将数据往队列里面加,当完整接收到一帧数据时此时队列中的全部数据也就是一帧完整的数据,因此只需要判断队列是否是数据校验头,队列尾是否是数据校验尾就可以得知当前是否已经接收到了一帧完整的数据,然后在将数据从队列中取出即可。
每来一个数据就往队列里面加:
当接收到一帧完整数据时队列头和数据校验头重合:
此时只需要从队列中取出有效数据即可;如果有数据尾校验,仅仅只需要添加一个校验尾即可,如下图所示:
好,分析结束,开始编码。
首先需要一个队列,为了保证通用性,队列底层使用类似于双向链表的实现(当然也可以使用数组实现),需要封装的结构有队列容量、队列大小、队头节点和队尾节点,需要实现的操作有队列初始化、数据入队、数据出队、清空队列和释放队列,具体代码如下:
/* queue.h */
#ifndef _QUEUE_H_
#define _QUEUE_H_
#ifndef NULL
#define NULL ((void *)0)
#endif
typedef unsigned char uint8;
/* 队列节点 */
typedef struct Node
{
uint8 data;
struct Node *pre_node;
struct Node *next_node;
} Node;
/* 队列结构 */
typedef struct Queue
{
uint8 capacity; // 队列总容量
uint8 size; // 当前队列大小
Node *front; // 队列头节点
Node *back; // 队列尾节点
} Queue;
/* 初始化一个队列 */
Queue *init_queue(uint8 _capacity);
/* 数据入队 */
uint8 en_queue(Queue *_queue, uint8 _data);
/* 数据出队 */
uint8 de_queue(Queue *_queue);
/* 清空队列 */
void clear_queue(Queue *_queue);
/* 释放队列 */
void release_queue(Queue *_queue);
#endif
其次是解析器,需要封装的结构有解析数据队列、数据校验头、数据校验尾、解析结果以及指向解析结果的指针,需要实现的操作有解析器初始化、添加数据解析、获取解析结果、重置解析器和释放解析器,具体代码如下:
- /* parser.h */
-
- #ifndef _PARSER_H_
- #define _PARSER_H_
-
- #include "queue.h"
-
- typedef enum
- {
- RESULT_FALSE,
- RESULT_TRUE
- } ParserResult;
-
- /* 解析器结构 */
- typedef struct DataParser
- {
- Queue *parser_queue; // 数据解析队列
- Node *resule_pointer; // 解析结果数据指针
- uint8 *data_header; // 数据校验头指针
- uint8 header_size; // 数据校验头大小
- uint8 *data_footer; // 数据校验尾指针
- uint8 footer_size; // 数据校验尾大小
- uint8 result_size; // 解析数据大小
- ParserResult parserResult; // 解析结果
- } DataParser;
-
- /* 初始化一个解析器 */
- DataParser *parser_init(uint8 *_data_header, uint8 _header_size, uint8 *_data_footer, uint8 _foot_size, uint8 _data_frame_size);
- /* 将数据添加到解析器中进行解析 */
- ParserResult parser_put_data(DataParser *_parser, uint8 _data);
- /* 解析成功后从解析器中取出解析结果 */
- int parser_get_data(DataParser *_parser, uint8 _index);
- /* 重置解析器 */
- void parser_reset(DataParser *_parser);
- /* 释放解析器 */
- void parser_release(DataParser *_parser);
-
- #endif
复制代码
接下来编写测试代码测试一下:
- /* main.c */
-
- #include <stdio.h>
- #include "parser.h"
-
- int main()
- {
- uint8 i;
- // 数据头
- uint8 data_header[] = {0xAA, 0xAA, 0x04, 0x80, 0x02};
- // 要解析的数据,测试用
- uint8 data[] = {
- 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("成功解析出一帧数据...\n");
-
- /* 一位一位取出解析后的数据 */
- printf("第一个数据是:0x%x\n", parser_get_data(data_parser, 0));
- printf("第二个数据是:0x%x\n", parser_get_data(data_parser, 1));
- printf("第三个数据是:0x%x\n\n\n", parser_get_data(data_parser, 2));
- }
- }
-
- // 当不再需要解析器时,应该把解析器释放掉,回收内存,避免造成内存泄漏
- parser_release(data_parser);
-
- return 0;
- }
复制代码
测试结果如下:
从上面可以看出,解析的结果与目标一致。
|
|