前陣子一朋友使用單片機與某外設進行通信時,外設返回的是一堆格式如下的數據:
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、 容易出現誤判 。
對此,這里提出了一種新的解決方案,可以通用與所有類似的數據解析,原理如下:
使用一個固定容量的隊列用來緩存接收到的數據,隊列容量等于一幀數據的大小,每來一個數據就將數據往隊列里面加,當完整接收到一幀數據時此時隊列中的全部數據也就是一幀完整的數據,因此只需要判斷隊列是否是數據校驗頭,隊列尾是否是數據校驗尾就可以得知當前是否已經接收到了一幀完整的數據,然后在將數據從隊列中取出即可。原理圖如下:
每來一個數據就往隊列里面加:
![769cb4b8-f4af-11ed-90ce-dac502259ad0.png](https://file1.elecfans.com//web2/M00/9B/30/wKgaomTnor-AD8pBAADV_KTgHoA274.png)
當接收到一幀完整數據時隊列頭和數據校驗頭重合:
![76aad64c-f4af-11ed-90ce-dac502259ad0.png](https://file1.elecfans.com//web2/M00/9B/30/wKgaomTnor-AIH9LAADDbB3dwyI047.png)
此時只需要從隊列中取出有效數據即可。
如果有數據尾校驗,僅僅只需要添加一個校驗尾即可,如下圖所示:
![76b1e68a-f4af-11ed-90ce-dac502259ad0.png](https://file1.elecfans.com//web2/M00/9B/30/wKgaomTnor-ARYrjAADv-m3fhVY549.png)
好,分析結束,開始編碼。
首先需要一個隊列,為了保證通用性,隊列底層使用類似于雙向鏈表的實現(當然也可以使用數組實現),需要封裝的結構有隊列容量、隊列大小、隊頭節點和隊尾節點,需要實現的操作有隊列初始化、數據入隊、數據出隊、清空隊列和釋放隊列,具體代碼如下:
/* 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("成功解析出一幀數據... "); /* 一位一位取出解析后的數據 */ printf("第一個數據是:0x%x ", parser_get_data(data_parser, 0)); printf("第二個數據是:0x%x ", parser_get_data(data_parser, 1)); printf("第三個數據是:0x%x ", parser_get_data(data_parser, 2)); } } // 當不再需要解析器時,應該把解析器釋放掉,回收內存,避免造成內存泄漏 parser_release(data_parser); return 0; }
測試結果如下:
從上面可以看出,解析的結果與目標一致。
github地址:
https://github.com/528787067/DataFrameParser
審核編輯:湯梓紅
-
單片機
+關注
關注
6044文章
44627瀏覽量
638962 -
數據
+關注
關注
8文章
7169瀏覽量
89694 -
代碼
+關注
關注
30文章
4836瀏覽量
69121 -
數據解析
+關注
關注
0文章
14瀏覽量
3526
原文標題:一種單片機數據解析方法
文章出處:【微信號:c-stm32,微信公眾號:STM32嵌入式開發】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論