当前位置:首页 > 公众号精选 > 嵌入式大杂烩
[导读]前阵子一朋友使用单片机与某外设进行通信时,外设返回的是一堆格式如下的数据。

来源 | 网络

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

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 
/* queue.c */ #include  #include "parser.h" /**
 * 初始化一个队列
 *
 * @_capacity: 队列总容量
 */ Queue *init_queue(uint8 _capacity) {
 Queue *queue = (Queue *)malloc(sizeof(Queue)); queue->capacity = _capacity; queue->size = 0; return queue;
} /**
 * 数据入队
 *
 * @_queue: 队列
 * @_data: 数据
 **/ uint8 en_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: 出队的数据
 */ uint8 de_queue(Queue *_queue) {
    uint8 old_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--;
    } return old_data;
} /**
 * 清空队列
 *
 * @_queue: 队列
 */ void clear_queue(Queue *_queue) { while(_queue->size > 0)
    {
        de_queue(_queue);
    }
} /**
 * 释放队列
 *
 * @_queue: 队列
 */ void release_queue(Queue *_queue) {
    clear_queue(_queue); free(_queue);
    _queue = NULL;
}

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

/* 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 
/* 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) return NULL;
 
    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);
    } return parser;
} /**
 * 将数据添加到解析器中进行解析
 *
 * @_parser: 解析器
 * @_data: 要解析的数据
 *
 * @return: 当前解析结果,返回 RESULT_TRUE 代表成功解析出一帧数据
 */ ParserResult parser_put_data(DataParser *_parser, uint8 _data) {
    uint8 i;
    Node *node; if(_parser == NULL) return RESULT_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]) goto DATA_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]) goto DATA_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 代表数据获取失败
 */ int parser_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--;
    } return node->data;
} /**
 * 重置解析器
 *
 * @_parser: 解析器
 */ void parser_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: 解析器
 */ void parser_release(DataParser *_parser) { if(_parser == NULL) return;
    release_queue(_parser->parser_queue); free(_parser);
    _parser = NULL;
}

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

/* main.c */ #include  #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;
}

测试结果如下:

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

github地址:

https://github.com/528787067/DataFrameParser

免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

这款全新的中端MCU系列为设计人员提供了更高水平的安全性和灵活性

关键字: 嵌入式 单片机

单片机是一种嵌入式系统,它是一块集成电路芯片,内部包含了处理器、存储器和输入输出接口等功能。

关键字: 单片机 编写程序 嵌入式

在现代电子技术的快速发展中,单片机以其高度的集成性、稳定性和可靠性,在工业自动化、智能家居、医疗设备、航空航天等诸多领域得到了广泛应用。S32单片机,作为其中的佼佼者,其引脚功能丰富多样,是实现与外部设备通信、控制、数据...

关键字: s32单片机引脚 单片机

在微控制器领域,MSP430与STM32无疑是两颗璀璨的明星。它们各自凭借其独特的技术特点和广泛的应用领域,在市场上占据了重要的位置。本文将深入解析MSP430与STM32之间的区别,探讨它们在不同应用场景下的优势和局限...

关键字: MSP430 STM32 单片机

该系列产品有助于嵌入式设计人员在更广泛的系统中轻松实现USB功能

关键字: 单片机 嵌入式设计 USB

单片机编程语言是程序员与微控制器进行交流的桥梁,它们构成了单片机系统的软件开发基石,决定着如何有效、高效地控制和管理单片机的各项资源。随着微控制器技术的不断发展,针对不同应用场景的需求,形成了丰富多样的编程语言体系。本文...

关键字: 单片机 微控制器

单片机,全称为“单片微型计算机”或“微控制器”(Microcontroller Unit,简称MCU),是一种高度集成化的电子器件,它是现代科技领域的关键组件,尤其在自动化控制、物联网、消费电子、汽车电子、工业控制等领域...

关键字: 单片机 MCU

STM32是由意法半导体公司(STMicroelectronics)推出的基于ARM Cortex-M内核的32位微控制器系列,以其高性能、低功耗、丰富的外设接口和强大的生态系统深受广大嵌入式开发者喜爱。本文将详细介绍S...

关键字: STM32 单片机

在当前的科技浪潮中,单片机作为嵌入式系统的重要组成部分,正以其强大的功能和广泛的应用领域受到越来越多行业的青睐。在众多单片机中,W79E2051以其卓越的性能和稳定的工作特性,成为市场上的明星产品。本文将深入探讨W79E...

关键字: 单片机 w79e2051单片机

单片机,又称为微控制器或微处理器,是现代电子设备中的核心部件之一。它集成了中央处理器、存储器、输入输出接口等电路,通过外部信号引脚与外部设备进行通信,实现对设备的控制和管理。本文将详细介绍单片机的外部信号引脚名称及其功能...

关键字: 单片机 微控制器 中央处理器
关闭
关闭