天天看點

android應用層發送at_嵌入式硬體通信接口協定-UART(四)設計起止式的應用層協定...

文章首發于同名微信公衆号:

DigCore

歡迎關注同名微信公衆号:

DigCore

,及時擷取最新技術博文。

原文連結:https://mp.weixin.qq.com/s/amO7rGkqFJtCzuyjyIVwDw

(說明:此處的文章從微信公衆号拷貝而來,圖檔或者排版上可能存在一定的瑕疵,歡迎點選原文連結閱讀)

序列槽實作了兩個終端裝置之間進行可靠的通信,序列槽在這中間完成了傳輸層的作用。本次要講的是關于資料的協定。

類似場景

A:洞幺!洞幺!我是洞拐!收到請回答!收到請回答!over!

在戰争題材影視劇中經常能夠看到這樣的對白,在通過對講機等相關無線裝置呼叫隊友時,先呼叫對方名稱,然後告知自己身份,說完内容最後再說over,表示一次呼叫結束。

是的,沒錯,這就是本節要講的在序列槽通信中發揮重要作用的起止式協定!

UART的時序本身就是起止式協定,具體可參考《嵌入式硬體通信接口協定-UART(一)協定基礎》這一篇内容的介紹。

事實上序列槽實作了資料通信過程中的傳輸層,而應用層就系統功能的業務邏輯,應用層控制需要收發的各種資料内容。

資料解析的前提是通信雙方都是用統一的資料幀格式,是以在這裡将設計一個簡單的起止式的資料幀格式,保證裝置之間進行可靠的通信。

現在的很多無線子產品,為了使用簡單和易于內建,子產品對外使用UART接口,并采用AT指令來完成配置和使用,常見的有ESP8266的WiFi子產品、HC-05藍牙序列槽子產品。

AT指令的特點是易于人機互動,使用者對其發AT指令時,都是用ASCII字元發送,對于子產品的處理,也是以字元來處理。這樣的AT指令,它的起止式特點是以“AT”兩個字元開頭,并以回車換行“rn”字元結束。
android應用層發送at_嵌入式硬體通信接口協定-UART(四)設計起止式的應用層協定...

HC-05藍牙子產品指令示例

但是項目工程中,資料在嵌入式裝置是以HEX資料(16進制)運算和處理,如果參考AT指令去設計幀結構,那麼在收發處理時候,必然要将收到的純資料(16進制)按照字元處理。

比如一個終端裝置,其功能就是環境檢測,可能包含溫濕度、光照強度、二氧化碳濃度、PM2.5濃度等等,如果要發出一個溫度采集結果24℃資料,采集裝置将資料24分成2個位元組發送,因為ASCII字元’2’對應的16進制是0x32、ASCII字元’4’對應的16進制是0x34,這樣的一個溫度資料就需要2個位元組來發送。接收端接收到的是0x32、0x34後,再以查表方式逆向換算出原溫度資料’24’,這個過程就是采用字元處理的麻煩之一。

是以不考慮使用ASCII字元來組幀結構。

精簡起止式結構

最簡單的幀,就是有開頭+結尾做起止标志。

比如

0x55 + 資料包 + 0xAA。

在一長串的資料流中,接收端逐位元組接收,并判斷是否存在0x55,如果存在則開始存入資料包緩沖器,直到接收了0xAA資料,認為完成一幀資料的接收。

這個方法确實相當簡單,不用太多的處理,隻需要判斷開頭和結尾即可。

而這樣存在很大的問題,如果傳輸的内容也有0xAA這樣的資料,這個0xAA并非結尾标志,而程式接收過程就提前結束,這樣就不能保證完整接收一幀資料包了。

增加長度限制

在精簡起止式結構基礎上,增加一資料來标志資料包長度。

比如

0x55 + 長度 + 資料包 + 0xAA。

這樣一來,接收端判斷接收到了0x55的開頭标志,緊接着再接收一個“長度”的位元組,基于這個長度來繼續接收後續剩餘的資料。

可見如果有了長度的限制,那麼最後都不需要0xAA作為結尾标志了。

這樣的接口,即使有開頭、長度、結尾,還存在風險。比如傳輸資料時,實體線路受到未知幹擾,導緻資料内容出現了異常,那麼接收端即使完整接收所有數量的資料下來,也是錯誤的内容。

增加校驗檢查

解決在發送過程中出現的未知錯誤問題,必然需要對資料進行校驗。再增加一字段來标志資料内容的校驗計算結果。

比如

0x55 + 長度 + 校驗值 + 資料内容 + 0xAA。

校驗值是對資料包采用算法計算而得,接收方完整收下所有數量的資料,再對資料包采用同樣的算法計算出校驗值,進而對比校驗值來确定資料包的準确性。

對于校驗值的運算,采用CRC-16運算的方式,檢錯能力強,開銷小。

設計協定幀結構

綜上所述,基于起止式的幀結構可以設計成:

0x55 + 長度 + CRC校驗 + 資料包

在這裡,幀頭标志采用0x55一個位元組。

0x55二進制是01010101,這樣在UART實體線路上輸出的信号将會是占空比50%的方波,方波是最容易進行測量和診斷的,在實際波形觀測時可以确定穩定性、噪聲毛刺等。要說0xAA(二進制10101010)也是可以,但是UART發送時候是有一個起始位0,并且是以LSB方式先發送bit0的最低位,0xAA的bit0已經是0,而0x55的bit0是1,是以想得到方波當然優先考慮用0x55。 長度采用一個位元組表示,則後續的CRC校驗 + 資料包的總數量最多能放255個位元組。 CRC校驗采用CRC-16算法,占2個位元組,此時後續的資料包最多能放253個位元組。

終上所述,得出最終的起止式幀結構:

android應用層發送at_嵌入式硬體通信接口協定-UART(四)設計起止式的應用層協定...
android應用層發送at_嵌入式硬體通信接口協定-UART(四)設計起止式的應用層協定...

接下來開始設計處理程式。

根據幀結構,可以定義如下的結構體:

typedef struct{

uint8_t head;

uint8_t len;

uint8_t crc16L;

uint8_t crc16H;

uint8_t packet[253];

}sst_frame_t;

其中要特别說明的:

packet資料包最大長度設為253,是因為len是uint8_t類型,len最大255,而CRC校驗值占了2個位元組,是以packet資料包最多可253個位元組。 CRC校驗值采用的是CRC-16标準,校驗值是個uint16_t類型的資料,傳輸時采用的是LSB模式,是以将CRC校驗值設為兩個uint8_t類型的資料,這樣做便于在源碼移植過程中,不同平台的大小端差異能夠得到正确處理。 簡述嵌入式裝置記憶體大小端差異在結構體定義以及使用時存在的問題:

假如對幀結構定義了如下的結構體:

typedef struct{

uint8_t head;

uint8_t len;

uint16_t crc16;

uint8_t packet[253];

}sst_frame_t;

計算後得到某一次的校驗值結果是0xDC66,這是一個uint16_t類型的資料,如果直接使用這個結構體來處理資料發送,那麼:

在LSB的小端模式平台下,資料的發送順序是

head、len、0x66、0xDC、packet[0]、packet[1]、…

反之如果在MSB大端模式的平台裡,資料的發送順序是

head、len、0xDC、0x66、packet[0]、packet[1]、…

是以采用2個位元組uint8_t資料類型代替uint16_t來定義結構體中的CRC校驗值,使得在跨平台收發資料時無需做差異化處理。

android應用層發送at_嵌入式硬體通信接口協定-UART(四)設計起止式的應用層協定...

建構幀結構

使用起止式進行資料傳輸時,把應用層的資料包進行組幀,這樣可構造一個完整的資料幀,便于在應用層将完整的一幀資料傳遞給傳輸層發出。

這裡的構造過程,事實上是對幀結構的“填充”過程。

首先是計算資料包的CRC校驗值,随後就是“填充”的過程。

為了防止應用層調用接口時,傳進來的資料包的位址、組幀結果的首位址指向同一個記憶體位址,是以在組幀前需要将源資料内容單獨緩存,再進行“填充”的操作。
android應用層發送at_嵌入式硬體通信接口協定-UART(四)設計起止式的應用層協定...

解析幀結構

解析幀結構其實就是對一長串的資料流進行解析處理,進而提取出資料包。

這裡被解析的資料來源是一個循環緩沖區,對循環緩沖區内的可讀資料進行解析。

是以需要使用循環緩沖區配合。

代碼截圖:

android應用層發送at_嵌入式硬體通信接口協定-UART(四)設計起止式的應用層協定...

解析思路是:

  1. 確定環形緩沖區有足夠一個幀結構的資料量,否則返資料量不足的錯誤;
  2. 接着讀出一個位元組判斷幀頭标志是否為0x55,否則返幀頭錯誤;
  3. 再次讀一個位元組作為幀長度資料,且長度至少3個位元組(2個CRC校驗值+至少1位元組資料包),否則返幀長度錯誤;
  4. 讀出幀長度資料,如果此時環形緩沖區的可讀數量比長度數值小,出現這情況的原因可能是幀長度字段在發送期間出現異常,或是對端裝置序列槽傳輸慢而未完整傳輸一幀,此時可做适當的延時等待,如果逾時退出,且返幀長度錯誤;
  5. 繼續讀出2個位元組作為CRC校驗值,且需要注意先收到的是crc16L,先收到小端數值;
  6. 緊接着把資料包讀出,此時讀的長度應該是第4步中的幀長度資料少2個位元組;
  7. 最後對資料包計算一個CRC校驗值,對比接收到的校驗值,校驗值不一緻則返錯誤校驗碼。

函數傳回值符合以下枚舉的錯誤碼:

android應用層發送at_嵌入式硬體通信接口協定-UART(四)設計起止式的應用層協定...

被解析資料源

看到這裡也許仍有疑問,用于解析的資料源哪來?資料什麼時候被寫進環形緩沖區内?

參考上一篇《嵌入式硬體通信接口-使用RingBuffer處理資料(二)詳細設計過程》介紹的關于向環形緩沖區寫入一個位元組,但dclib_ringbuffer這個子產品屬于應用庫子產品層,而如果直接把dclib_rb_writebyte這一個接口放在序列槽接收中斷裡執行,這就破壞了系統的架構層次,對工程代碼的維護和移植是個麻煩事,是以采用回調函數的方式。

嵌入式開發工程師都知道,一般在使用官方的庫時,經常會遇到需要自己實作一些回調函數,進而利用注冊接口把回調函數傳遞給庫或者驅動層,使庫或者驅動層在執行時調用該回調函數。

根據這個思路,同樣的這裡也采用回調函數的形式,回調函數内完成了把序列槽接收到的資料寫入環形緩沖區内。

回調函數的實作源碼截圖:

android應用層發送at_嵌入式硬體通信接口協定-UART(四)設計起止式的應用層協定...

事實上僅僅調用了dclib_ringbuffer功能裡的寫一位元組接口dclib_rb_writebyte,回調函數傳進來的參數dat就是序列槽接收到的資料。

有了回調函數,還要把這個回調函數的位址傳給底層驅動,這也就是常說的“注冊”的過程,注冊接口在固件闆級接口層裡序列槽子產品dcbsp_uart實作,注冊接口時dclib_uart_callback_reg函數:
android應用層發送at_嵌入式硬體通信接口協定-UART(四)設計起止式的應用層協定...

又偏題了,關于回調函數在此不做深入論述。

簡而言之,環形緩沖區寫入一位元組的執行過程,放在回調函數裡,當序列槽接收中斷觸發後,中斷裡會根據注冊的回調函數位址,進而執行回調函數,實作對環形緩沖區寫入一個位元組資料。如此操作的理由是不改變工程代碼的分層架構,并且便于維護與移植!

為了縮減篇幅,最後貼上測試代碼的部分:

android應用層發送at_嵌入式硬體通信接口協定-UART(四)設計起止式的應用層協定...
android應用層發送at_嵌入式硬體通信接口協定-UART(四)設計起止式的應用層協定...

最後也附上調試期間序列槽列印的解析結果:

android應用層發送at_嵌入式硬體通信接口協定-UART(四)設計起止式的應用層協定...

起止式幀結構的講解稍有匆忙,篇幅也略大,文中基礎技術要點未能細緻講解,後續統籌規劃再做單獨介紹!

接下來在此幀結構基礎上,講述

如何設計在資料包放置應用層的互動指令

,敬請期待下回分!

《ASCII》@百度百科

https://baike.baidu.com/item/ASCII

《CRC》@百度百科

https://baike.baidu.com/item/CRC/1453359

《CRC開源項目》@github

https://github.com/lammertb/libcrc

★★★★★推薦文章

《【嵌入式程式設計】函數傳回類型設計》

《【嵌入式程式設計】平台大小端存儲差異解決辦法》

《嵌入式硬體通信接口-使用RingBuffer處理資料(二)詳細設計過程》

《嵌入式硬體通信接口-使用RingBuffer處理資料(一)》

《快速開發MQTT(一)電子工程師眼中的MQTT》

《快速開發MQTT(二)初識MQTT》

《MQTT用戶端搭建-最清晰的MQTT協定架構》

《MQTT服務端搭建-最快方式驗證自己開發的用戶端》

★★★★★相似文章

《嵌入式硬體通信接口協定-UART(五)資料包設計與解析》

《嵌入式硬體通信接口協定-UART(四)設計起止式的應用層協定》

《嵌入式硬體通信接口協定-UART(三)快速使用序列槽及應用》

《嵌入式硬體通信接口協定-UART(二)不同電氣規範下的标準》

《嵌入式硬體通信接口協定-UART(一)協定基礎》

《嵌入式硬體通信接口協定-SPI(三)模拟接口應用》

《嵌入式硬體通信接口協定-SPI(二)分層架構設計模拟接口》

《嵌入式硬體通信接口協定-SPI(一)協定基礎》

《嵌入式硬體通信接口協定-IIC(一)協定基礎》

★★★★★擴充閱讀

《【硬體電路】AltiumDesigner18規則檢查含義》

《【硬體電路】N溝道、P溝道MOS管基本原理與應用案例》