轉載連接: http://blog.csdn.net/XR528787067/article/details/52822377?locationNum=2&fps=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重新判斷,使用這種方法解析數據的代碼如下:
- 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;
- }
測試結果如下:
從上面可以看出,解析的結果與目標一致。
github地址:https://github.com/528787067/DataFrameParser