嵌入式硬件通信接口協(xié)議-UART(四)設計起止式的應用層協(xié)議
串口實(shí)現了兩個(gè)終端設備之間進(jìn)行可靠的通信,串口在這中間完成了傳輸層的作用。本次要講的是關(guān)于數據的協(xié)議。
本文引用地址:http://dyxdggzs.com/article/201903/398678.htm類(lèi)似場(chǎng)景
洞幺!洞幺!我是洞拐!收到請回答!收到請回答!over!
在戰爭題材影視劇中經(jīng)常能夠看到這樣的對白,在通過(guò)對講機等相關(guān)無(wú)線(xiàn)設備呼叫隊友時(shí),先呼叫對方名稱(chēng),然后告知自己身份,說(shuō)完內容最后再說(shuō)over,表示一次呼叫結束。
是的,沒(méi)錯,這就是本節要講的在串口通信中發(fā)揮重要作用的起止式協(xié)議!
UART的時(shí)序本身就是起止式協(xié)議,具體可參考《嵌入式硬件通信接口協(xié)議-UART(一)協(xié)議基礎》這一篇內容的介紹。
事實(shí)上串口實(shí)現了數據通信過(guò)程中的傳輸層,而應用層就系統功能的業(yè)務(wù)邏輯,應用層控制需要收發(fā)的各種數據內容。
數據解析的前提是通信雙方都是用統一的數據幀格式,因此在這里將設計一個(gè)簡(jiǎn)單的起止式的數據幀格式,保證設備之間進(jìn)行可靠的通信。
現在的很多無(wú)線(xiàn)模塊,為了使用簡(jiǎn)單和易于集成,模塊對外使用UART接口,并采用AT指令來(lái)完成配置和使用,常見(jiàn)的有ESP8266的WiFi模塊、HC-05藍牙串口模塊。
AT指令的特點(diǎn)是易于人機交互,使用者對其發(fā)AT指令時(shí),都是用ASCII字符發(fā)送,對于模塊的處理,也是以字符來(lái)處理。這樣的AT指令,它的起止式特點(diǎn)是以“AT”兩個(gè)字符開(kāi)頭,并以回車(chē)換行“”字符結束。

HC-05藍牙模塊指令示例
但是項目工程中,數據在嵌入式設備是以HEX數據(16進(jìn)制)運算和處理,如果參考AT指令去設計幀結構,那么在收發(fā)處理時(shí)候,必然要將收到的純數據(16進(jìn)制)按照字符處理。
比如一個(gè)終端設備,其功能就是環(huán)境檢測,可能包含溫濕度、光照強度、二氧化碳濃度、PM2.5濃度等等,如果要發(fā)出一個(gè)溫度采集結果24℃數據,采集設備將數據24分成2個(gè)字節發(fā)送,因為ASCII字符’2’對應的16進(jìn)制是0x32、ASCII字符’4’對應的16進(jìn)制是0x34,這樣的一個(gè)溫度數據就需要2個(gè)字節來(lái)發(fā)送。接收端接收到的是0x32、0x34后,再以查表方式逆向換算出原溫度數據’24’,這個(gè)過(guò)程就是采用字符處理的麻煩之一。
因此不考慮使用ASCII字符來(lái)組幀結構。
精簡(jiǎn)起止式結構
最簡(jiǎn)單的幀,就是有開(kāi)頭+結尾做起止標志。
比如0x55 + 數據包 + 0xAA。
在一長(cháng)串的數據流中,接收端逐字節接收,并判斷是否存在0x55,如果存在則開(kāi)始存入數據包緩沖器,直到接收了0xAA數據,認為完成一幀數據的接收。
這個(gè)方法確實(shí)相當簡(jiǎn)單,不用太多的處理,只需要判斷開(kāi)頭和結尾即可。
而這樣存在很大的問(wèn)題,如果傳輸的內容也有0xAA這樣的數據,這個(gè)0xAA并非結尾標志,而程序接收過(guò)程就提前結束,這樣就不能保證完整接收一幀數據包了。
增加長(cháng)度限制
在精簡(jiǎn)起止式結構基礎上,增加一數據來(lái)標志數據包長(cháng)度。
比如0x55 + 長(cháng)度 + 數據包 + 0xAA。
這樣一來(lái),接收端判斷接收到了0x55的開(kāi)頭標志,緊接著(zhù)再接收一個(gè)“長(cháng)度”的字節,基于這個(gè)長(cháng)度來(lái)繼續接收后續剩余的數據。
可見(jiàn)如果有了長(cháng)度的約束,那么最后都不需要0xAA作為結尾標志了。
這樣的接口,即使有開(kāi)頭、長(cháng)度、結尾,還存在風(fēng)險。比如傳輸數據時(shí),物理線(xiàn)路受到未知干擾,導致數據內容出現了異常,那么接收端即使完整接收所有數量的數據下來(lái),也是錯誤的內容。
增加校驗檢查
解決在發(fā)送過(guò)程中出現的未知錯誤問(wèn)題,必然需要對數據進(jìn)行校驗。再增加一字段來(lái)標志數據內容的校驗計算結果。
比如0x55 + 長(cháng)度 + 校驗值 + 數據內容 + 0xAA。
校驗值是對數據包采用算法計算而得,接收方完整收下所有數量的數據,再對數據包采用同樣的算法計算出校驗值,從而對比校驗值來(lái)確定數據包的準確性。
對于校驗值的運算,采用CRC-16運算的方式,檢錯能力強,開(kāi)銷(xiāo)小。
設計協(xié)議幀結構
綜上所述,基于起止式的幀結構可以設計成:0x55 + 長(cháng)度 + CRC校驗 + 數據包。
在這里,幀頭標志采用0x55一個(gè)字節。
0x55二進(jìn)制是01010101,這樣在UART物理線(xiàn)路上輸出的信號將會(huì )是占空比50%的方波,方波是最容易進(jìn)行測量和診斷的,在實(shí)際波形觀(guān)測時(shí)可以確定穩定性、噪聲毛刺等。
要說(shuō)0xAA(二進(jìn)制10101010)也是可以,但是UART發(fā)送時(shí)候是有一個(gè)起始位0,并且是以L(fǎng)SB方式先發(fā)送bit0的最低位,0xAA的bit0已經(jīng)是0,而0x55的bit0是1,因此想得到方波當然優(yōu)先考慮用0x55。
長(cháng)度采用一個(gè)字節表示,則后續的CRC校驗 + 數據包的總數量最多能放255個(gè)字節。
CRC校驗采用CRC-16算法,占2個(gè)字節,此時(shí)后續的數據包最多能放253個(gè)字節。
終上所述,得出最終的起止式幀結構:


接下來(lái)開(kāi)始設計處理程序。
根據幀結構,可以定義如下的結構體:
typedef struct{
uint8_t head;
uint8_t len;
uint8_t crc16L;
uint8_t crc16H;
uint8_t packet[253];
}sst_frame_t;
其中要特別說(shuō)明的:
packet數據包最大長(cháng)度設為253,是因為len是uint8_t類(lèi)型,len最大255,而CRC校驗值占了2個(gè)字節,因此packet數據包最多可253個(gè)字節。
CRC校驗值采用的是CRC-16標準,校驗值是個(gè)uint16_t類(lèi)型的數據,傳輸時(shí)采用的是LSB模式,因此將CRC校驗值設為兩個(gè)uint8_t類(lèi)型的數據,這樣做便于在源碼移植過(guò)程中,不同平臺的大小端差異能夠得到正確處理。
簡(jiǎn)述嵌入式設備內存大小端差異在結構體定義以及使用時(shí)存在的問(wèn)題:
假如對幀結構定義了如下的結構體:
typedef struct{
uint8_t head;
uint8_t len;
uint16_t crc16;
uint8_t packet[253];
}sst_frame_t;
計算后得到某一次的校驗值結果是 0xDC66,這是一個(gè)uint16_t類(lèi)型的數據,如果直接使用這個(gè)結構體來(lái)處理數據發(fā)送,那么:
在LSB的小端模式平臺下,數據的發(fā)送順序是
head、len、0x66、0xDC、packet[0]、packet[1]、...
反之在MSB大端模式的平臺里,數據的發(fā)送順序是
head、len、0xDC、0x66、packet[0]、packet[1]、...
因此采用2個(gè)字節uint8_t數據類(lèi)型代替uint16_t來(lái)定義結構體中的CRC校驗值,使得在跨平臺收發(fā)數據時(shí)無(wú)需做差異化處理。

構建幀結構
使用起止式進(jìn)行數據傳輸時(shí),把應用層的數據包進(jìn)行組幀,這樣可構造一個(gè)完整的數據幀,便于在應用層將完整的一幀數據傳遞給傳輸層發(fā)出。
這里的構造過(guò)程,事實(shí)上是對幀結構的“填充”過(guò)程。
首先是計算數據包的CRC校驗值,隨后就是“填充”的過(guò)程。
為了防止應用層調用接口時(shí),傳進(jìn)來(lái)的數據包的地址、組幀結果的首地址指向同一個(gè)內存地址,所以在組幀前需要將源數據內容單獨緩存,再進(jìn)行“填充”的操作。

解析幀結構
解析幀結構其實(shí)就是對一長(cháng)串的數據流進(jìn)行解析處理,從而提取出數據包。
這里被解析的數據來(lái)源是一個(gè)循環(huán)緩沖區,對循環(huán)緩沖區內的可讀數據進(jìn)行解析。因此需要使用循環(huán)緩沖區配合。
代碼截圖:

評論