单片机接收数据帧帧头帧尾校验数据解析

2019-04-15 12:08发布

转载连接: http://blog.csdn.net/XR528787067/article/details/52822377?locationNum=2&fps=1
前阵子一朋友使用单片机与某外设进行通信时,外设返回的是一堆格式如下的数据: [cpp] view plain copy
  1. 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重新判断,使用这种方法解析数据的代码如下: [cpp] view plain copy
  1. if(flag == 0)  
  2. {  
  3.     if(tempData == 0xAA)  
  4.         flag++;  
  5.     else  
  6.         flag = 0;  
  7. }  
  8. else if(flag == 1)  
  9. {  
  10.     if(tempData == 0xAA)  
  11.         flag++;  
  12.     else  
  13.         flag = 0;  
  14. }  
  15. else if(flag == 2)  
  16. {  
  17.     if(tempData == 0x04)  
  18.         flag++;  
  19.     else  
  20.         flag = 0;  
  21. }  
  22. else if(flag == 3)  
  23. {  
  24.     if(tempData == 0x80)  
  25.         flag++;  
  26.     else  
  27.         flag = 0;  
  28. }  
  29. else if(flag == 4)  
  30. {  
  31.     if(tempData == 0x02)  
  32.         flag++;  
  33.     else  
  34.         flag = 0;  
  35. }  
  36. else if(flag == 5 || flag == 6 || flag == 7)  
  37. {  
  38.     data[flag-5] = tempData;  
  39.     flag = (flag == 7) ? 0 : flag+1;  
  40. }  
使用上述方法是最容易想到的也是最简单的方法了,百度了一下基本上也都是使用类似的方法进行数据解析,但是使用这种方法有如下几个缺点:  1、 大量使用了判断,容易导致出现逻辑混乱 2、 代码重复率高,抽象程度低。从上述代码可以看到一大堆代码仅仅是判断的数据不同,其他代码都完全一致 3、 代码可复用性差。写好的代码无法用在其他类似的外设上,如果有多个外设就需要编写多份类似的代码 4、 可扩展性低。如果外设还有一个数据校验尾需要校验或者数据校验头发生改变,就需要再次写多个判断重新用于校验,无法在原有的代码上进行扩展 5、 容易出现误判   对此,这里提出了一种新的解决方案,可以通用与所有类似的数据解析,原理如下:  使用一个固定容量的队列用来缓存接收到的数据,队列容量等于一帧数据的大小,每来一个数据就将数据往队列里面加,当完整接收到一帧数据时此时队列中的全部数据也就是一帧完整的数据,因此只需要判断队列是否是数据校验头,队列尾是否是数据校验尾就可以得知当前是否已经接收到了一帧完整的数据,然后在将数据从队列中取出即可。原理图如下: 每来一个数据就往队列里面加:
当接收到一帧完整数据时队列头和数据校验头重合:
此时只需要从队列中取出有效数据即可。 如果有数据尾校验,仅仅只需要添加一个校验尾即可,如下图所示: 好,分析结束,开始编码。 首先需要一个队列,为了保证通用性,队列底层使用类似于双向链表的实现(当然也可以使用数组实现),需要封装的结构有队列容量、队列大小、队头节点和队尾节点,需要实现的操作有队列初始化、数据入队、数据出队、清空队列和释放队列,具体代码如下: [cpp] view plain copy
  1. /* queue.h */  
  2.   
  3. #ifndef _QUEUE_H_  
  4. #define _QUEUE_H_  
  5.   
  6. #ifndef NULL  
  7. #define NULL    ((void *)0)  
  8. #endif  
  9.   
  10. typedef unsigned char uint8;  
  11.   
  12. /* 队列节点 */  
  13. typedef struct Node  
  14. {  
  15.     uint8 data;  
  16.     struct Node *pre_node;  
  17.     struct Node *next_node;  
  18. } Node;  
  19.   
  20. /* 队列结构 */  
  21. typedef struct Queue  
  22. {  
  23.     uint8 capacity;     // 队列总容量  
  24.     uint8 size;         // 当前队列大小  
  25.     Node *front;        // 队列头节点  
  26.     Node *back;         // 队列尾节点  
  27. } Queue;  
  28.   
  29. /* 初始化一个队列 */  
  30. Queue *init_queue(uint8 _capacity);  
  31. /* 数据入队 */  
  32. uint8 en_queue(Queue *_queue, uint8 _data);  
  33. /* 数据出队 */  
  34. uint8 de_queue(Queue *_queue);  
  35. /* 清空队列 */  
  36. void clear_queue(Queue *_queue);  
  37. /* 释放队列 */  
  38. void release_queue(Queue *_queue);  
  39.   
  40. #endif  

[cpp] view plain copy
  1. /* queue.c */  
  2.   
  3. #include   
  4. #include "parser.h"  
  5.   
  6. /** 
  7.  * 初始化一个队列 
  8.  * 
  9.  * @_capacity: 队列总容量 
  10.  */  
  11. Queue *init_queue(uint8 _capacity)  
  12. {  
  13.     Queue *queue = (Queue *)malloc(sizeof(Queue));  
  14.     queue->capacity = _capacity;  
  15.     queue->size = 0;  
  16.     return queue;  
  17. }  
  18.   
  19. /** 
  20.  * 数据入队 
  21.  * 
  22.  * @_queue: 队列 
  23.  * @_data: 数据 
  24.  **/  
  25. uint8 en_queue(Queue *_queue, uint8 _data)  
  26. {  
  27.     if