嵌入式硬件通信接口協(xié)議-UART(五)數(shù)據(jù)包設(shè)計(jì)與解析
上一節(jié)講到起止式SST(Start-Stop-Type)幀結(jié)構(gòu)協(xié)議,該協(xié)議利用幀頭、長(zhǎng)度、校驗(yàn)構(gòu)建幀結(jié)構(gòu),基于幀結(jié)構(gòu)能實(shí)現(xiàn)對(duì)數(shù)據(jù)包的可靠、準(zhǔn)確傳輸。
本文引用地址:http://m.butianyuan.cn/article/201903/398679.htm應(yīng)用層數(shù)據(jù)包設(shè)計(jì)思路
回到工程本身,幀結(jié)構(gòu)中的數(shù)據(jù)包才是應(yīng)用程序最終需要解析使用的,且與具體的業(yè)務(wù)需求有關(guān)。
這篇文章將簡(jiǎn)單介紹,在數(shù)據(jù)包里如何設(shè)計(jì)應(yīng)用層的交互指令,從而實(shí)現(xiàn)具體的業(yè)務(wù)需求。分享個(gè)思路,就當(dāng)拋磚引玉了。
類似于幀結(jié)構(gòu),在設(shè)計(jì)數(shù)據(jù)包時(shí),根據(jù)交互邏輯的具體需求,同樣采用逐字節(jié)組成字段,字段組成數(shù)據(jù)包,從而完成指令交互。
具體到項(xiàng)目中,一般地有目標(biāo)地址、源地址、指令類型、傳輸方向、級(jí)聯(lián)序號(hào)、參數(shù)ID、參數(shù)值等等。
字段的定義因項(xiàng)目需求而定,以上提及的字段可能存在且不限于此。
以下介紹在具體項(xiàng)目中,對(duì)數(shù)據(jù)包設(shè)計(jì)與解析思路。工程實(shí)踐中方法眾多,相信很多經(jīng)驗(yàn)嫻熟的老工程師肯定都有各自巧妙的編程思路,歡迎在本頁(yè)留言交流。
項(xiàng)目案例
基于nRF51822的BLE終端設(shè)備,與上位機(jī)使用UART通信,物理線路使用USB轉(zhuǎn)UART。
數(shù)據(jù)包定義
類型定義
參數(shù)名&參數(shù)值定義
根據(jù)以上定義,可以為應(yīng)用程序設(shè)計(jì)指令解析的結(jié)構(gòu)體,結(jié)構(gòu)體中所定義的類型type和參數(shù)名para,使用枚舉類型定義:
常規(guī)解析過程
解析函數(shù),一般地會(huì)把輸入?yún)?shù)的 *indata,利用一個(gè)新的結(jié)構(gòu)體指針指向該輸入?yún)?shù),之后的解析使用結(jié)構(gòu)體指針來對(duì)數(shù)據(jù)處理,增強(qiáng)代碼可讀性!
上述截圖中的定義方式出現(xiàn)了警告,這里需要做個(gè)如下的強(qiáng)制轉(zhuǎn)換:
常規(guī)的判斷處理,多采用switch(){case :}聯(lián)合if(...){;}else(...){;}判斷邏輯,這個(gè)模式的判斷處理架構(gòu)如下:
以上的做法,依次去判斷類型type、參數(shù)名para,然后直接處理。當(dāng)這兩個(gè)字段的枚舉成員數(shù)量少,倒還可以這么判斷;但是如果工程需要擴(kuò)展、業(yè)務(wù)有了新的需求,那么if(...){;}else(...){;}的逐一判斷將會(huì)使得解析函數(shù)里的代碼量巨大!
總結(jié)有這幾個(gè)缺點(diǎn):
1.業(yè)務(wù)需求有多少個(gè)類型或者其他分支,就需要多少個(gè)這樣的判斷邏輯,對(duì)于編寫代碼變成個(gè)體力活;
2.在代碼查看、維護(hù)時(shí),面對(duì)的還是羅列了一大堆的switch(){case :}和if(...){;}else(...){;}語句;
3.增刪功能時(shí),需要找到代碼中具體的判斷位置,然后小心翼翼給注釋或者修改掉。
這里已經(jīng)沒有任何的技術(shù)含量,基本上就是復(fù)制粘貼判斷語句、修改判斷對(duì)象,說到底也就是個(gè)查表的過程!
構(gòu)建查表方式解析
既然要查表,當(dāng)然是有個(gè)while()循環(huán),然后遞增某一變量來查表的過程。在這里,數(shù)據(jù)包結(jié)構(gòu)體中定義的類型type、參數(shù)名para,都可以作為查表的對(duì)象,該如何選擇?
假設(shè):
1.以類型作為查表對(duì)象,假如查表后類型等于查詢參數(shù),那么參數(shù)名仍然是個(gè)多個(gè)分支的情況,要么繼續(xù)查表要么繼續(xù)采用switch(){case :}或者if(...){;}else(...){;}來判斷眾多不同的參數(shù)名;
2.以參數(shù)名作為查表對(duì)象,假如查表后參數(shù)名等于設(shè)備運(yùn)行狀態(tài),那么類型需要做最多三種判斷:查詢、設(shè)置、其他。
對(duì)比以上兩種,必然是第2個(gè)更能提高編程效率、縷清邏輯框架。
要查表就要建表,建表的結(jié)構(gòu)體,以參數(shù)名para作為被查對(duì)象,并且以回調(diào)函數(shù)的形式執(zhí)行查表結(jié)果。建表如下:
說是建表,其實(shí)就是定義一個(gè)結(jié)構(gòu)體數(shù)組,數(shù)組的每個(gè)元素都是結(jié)構(gòu)體類型,這里的結(jié)構(gòu)體,主要由數(shù)據(jù)包協(xié)議的參數(shù)名和回調(diào)函數(shù)組成,定義如下:
在執(zhí)行數(shù)據(jù)包解析的時(shí)候,查表的思路是:
1.先創(chuàng)建一個(gè)表結(jié)構(gòu)的指針*ptable指向表的開始位置,也就是指向數(shù)組內(nèi)第一個(gè)元素{ECHO, dcapp_dev_echo}
2.再創(chuàng)建一個(gè)數(shù)據(jù)包結(jié)構(gòu)的指針*pbuf指向輸入數(shù)據(jù)首地址
3.通過遞增ptable指針,對(duì)ptable與pbuf的參數(shù)名成員進(jìn)行比對(duì)
4.最后執(zhí)行ptable指針對(duì)應(yīng)回調(diào)函數(shù)
以上的思路,放到代碼中,僅僅數(shù)行就可以實(shí)現(xiàn)對(duì)輸入數(shù)據(jù)包參數(shù)名的解析!高效、清晰!
另外,建表時(shí),把無效參數(shù)名對(duì)應(yīng)的值和對(duì)應(yīng)的回調(diào)函數(shù)放在最后,這樣做的好處是查完整個(gè)表,無需區(qū)分是否找到對(duì)應(yīng)的參數(shù)名,而直接執(zhí)行指針對(duì)應(yīng)的回調(diào)函數(shù)即可。
這樣即使是未找到參數(shù)名,也會(huì)執(zhí)行表中最后一個(gè)元素,就是錯(cuò)誤解析的回調(diào)函數(shù)dcapp_parser_err()。
有了這樣一個(gè)查表的處理方式,增刪指令功能就變得簡(jiǎn)單太多了!增加功能,只需要在表中添加參數(shù)名和對(duì)應(yīng)的回調(diào)函數(shù),刪除某功能,也是回到表中找到對(duì)應(yīng)的參數(shù)名和回調(diào)函數(shù)即可!
總結(jié)一下,雖然查表方式非常清晰,但是對(duì)應(yīng)的回調(diào)函數(shù)內(nèi)部,需要獨(dú)自處理和實(shí)現(xiàn),并且每個(gè)參數(shù)名都需要單獨(dú)處理。相比于采用switch(){case :}聯(lián)合if(...){;}else(...){;}判斷邏輯,確實(shí)清晰很多。
以上的查表思路,來源于經(jīng)歷的項(xiàng)目,同時(shí)還參考了
《STM32CubeExpansion_MEMSMIC1_V1.1》
這個(gè)ST官方的數(shù)字麥克風(fēng)開源項(xiàng)目示例,作為USB音頻設(shè)備時(shí),類似的回調(diào)函數(shù)方式:
調(diào)試截圖
正確解析了數(shù)據(jù)包的參數(shù)名之后,對(duì)應(yīng)的函數(shù)執(zhí)行結(jié)果是打印輸出調(diào)試信息,如下截圖:
以上是初步的解析效果,可以通過回調(diào)函數(shù),正確地跳轉(zhuǎn)到對(duì)應(yīng)的函數(shù)執(zhí)行。具體的處理仍需要針對(duì)項(xiàng)目的業(yè)務(wù)需求而設(shè)計(jì),在此不做更多的延伸。
評(píng)論