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

来源 | 网络

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

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获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

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

LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: 驱动电源

在工业自动化蓬勃发展的当下,工业电机作为核心动力设备,其驱动电源的性能直接关系到整个系统的稳定性和可靠性。其中,反电动势抑制与过流保护是驱动电源设计中至关重要的两个环节,集成化方案的设计成为提升电机驱动性能的关键。

关键字: 工业电机 驱动电源

LED 驱动电源作为 LED 照明系统的 “心脏”,其稳定性直接决定了整个照明设备的使用寿命。然而,在实际应用中,LED 驱动电源易损坏的问题却十分常见,不仅增加了维护成本,还影响了用户体验。要解决这一问题,需从设计、生...

关键字: 驱动电源 照明系统 散热

根据LED驱动电源的公式,电感内电流波动大小和电感值成反比,输出纹波和输出电容值成反比。所以加大电感值和输出电容值可以减小纹波。

关键字: LED 设计 驱动电源

电动汽车(EV)作为新能源汽车的重要代表,正逐渐成为全球汽车产业的重要发展方向。电动汽车的核心技术之一是电机驱动控制系统,而绝缘栅双极型晶体管(IGBT)作为电机驱动系统中的关键元件,其性能直接影响到电动汽车的动力性能和...

关键字: 电动汽车 新能源 驱动电源

在现代城市建设中,街道及停车场照明作为基础设施的重要组成部分,其质量和效率直接关系到城市的公共安全、居民生活质量和能源利用效率。随着科技的进步,高亮度白光发光二极管(LED)因其独特的优势逐渐取代传统光源,成为大功率区域...

关键字: 发光二极管 驱动电源 LED

LED通用照明设计工程师会遇到许多挑战,如功率密度、功率因数校正(PFC)、空间受限和可靠性等。

关键字: LED 驱动电源 功率因数校正

在LED照明技术日益普及的今天,LED驱动电源的电磁干扰(EMI)问题成为了一个不可忽视的挑战。电磁干扰不仅会影响LED灯具的正常工作,还可能对周围电子设备造成不利影响,甚至引发系统故障。因此,采取有效的硬件措施来解决L...

关键字: LED照明技术 电磁干扰 驱动电源

开关电源具有效率高的特性,而且开关电源的变压器体积比串联稳压型电源的要小得多,电源电路比较整洁,整机重量也有所下降,所以,现在的LED驱动电源

关键字: LED 驱动电源 开关电源

LED驱动电源是把电源供应转换为特定的电压电流以驱动LED发光的电压转换器,通常情况下:LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: LED 隧道灯 驱动电源
关闭