深入淺出低功耗藍芽(BLE)協議棧

語言: CN / TW / HK

BLE協議棧為什麼要分層?怎麼理解BLE“連線”?如果BLE協議只有ATT層沒有GATT層會發生什麼?

協議棧框架

一般而言,我們把某個協議的實現程式碼稱為協議棧(protocol stack),BLE協議棧就是實現低功耗藍芽協議的程式碼,理解和掌握BLE協議是實現BLE協議棧的前提。在深入BLE協議棧各個組成部分之前,我們先看一下BLE協議棧整體架構。

 如上圖所述,要實現一個BLE應用,首先需要一個支援BLE射頻的晶片,然後還需要提供一個與此晶片配套的BLE協議棧,最後在協議棧上開發自己的應用。可以看出BLE協議棧是連線晶片和應用的橋樑,是實現整個BLE應用的關鍵。那BLE協議棧具體包含哪些功能呢?簡單來說,BLE協議棧主要用來對你的應用資料進行層層封包,以生成一個滿足BLE協議的空中資料包,也就是說,把應用資料包裹在一系列的幀頭(header)和幀尾(tail)中。具體來說,BLE協議棧主要由如下幾部分組成:

  • PHY(Physical layer物理層)。PHY層用來指定BLE所用的無線頻段,調製解調方式和方法等。PHY層做得好不好,直接決定整個BLE晶片的功耗,靈敏度以及selectivity等射頻指標。
  • LL(Link Layer鏈路層)。LL層是整個BLE協議棧的核心,也是BLE協議棧的難點和重點。像Nordic的BLE協議棧能同時支援20個link(連線),就是LL層的功勞。LL層要做的事情非常多,比如具體選擇哪個射頻通道進行通訊,怎麼識別空中資料包,具體在哪個時間點把資料包傳送出去,怎麼保證資料的完整性,ACK如何接收,如何進行重傳,以及如何對鏈路進行管理和控制等等。LL層只負責把資料發出去或者收回來,對資料進行怎樣的解析則交給上面的GAP或者GATT。
  • HCI(Host controller interface)。HCI是可選的(具體請參考文章: 三種藍芽架構實現方案(藍芽協議棧方案)),HCI主要用於2顆晶片實現BLE協議棧的場合,用來規範兩者之間的通訊協議和通訊命令等。
  • GAP(Generic access profile)。GAP是對LL層payload(有效資料包)如何進行解析的兩種方式中的一種,而且是最簡單的那一種。GAP簡單的對LL payload進行一些規範和定義,因此GAP能實現的功能極其有限。GAP目前主要用來進行廣播,掃描和發起連線等。
  • L2CAP(Logic link control and adaptation protocol)。L2CAP對LL進行了一次簡單封裝,LL只關心傳輸的資料本身,L2CAP就要區分是加密通道還是普通通道,同時還要對連線間隔進行管理。
  • SMP(Secure manager protocol)。SMP用來管理BLE連線的加密和安全的,如何保證連線的安全性,同時不影響使用者的體驗,這些都是SMP要考慮的工作。
  • ATT(Attribute protocol)。簡單來說,ATT層用來定義使用者命令及命令操作的資料,比如讀取某個資料或者寫某個資料。BLE協議棧中,開發者接觸最多的就是ATT。BLE引入了attribute概念,用來描述一條一條的資料。Attribute除了定義資料,同時定義該資料可以使用的ATT命令,因此這一層被稱為ATT層。
  • GATT(Generic attribute profile )。GATT用來規範attribute中的資料內容,並運用group(分組)的概念對attribute進行分類管理。沒有GATT,BLE協議棧也能跑,但互聯互通就會出問題,也正是因為有了GATT和各種各樣的應用profile,BLE擺脫了ZigBee等無線協議的相容性困境,成了出貨量最大的2.4G無線通訊產品。

我相信很多人看了上面的介紹,還是不懂BLE協議棧的工作原理,以及每一層具體幹什麼的,為什麼要這麼分層。下面我以如何傳送一個數據包為例來講解BLE協議棧各層是如何緊密配合,以完成傳送任務的。

如何通過無線傳送一個數據包

假設有裝置A和裝置B,裝置A要把自己目前的電量狀態83%(十六進位制表示為0x53)發給裝置B,該怎麼做呢?作為一個開發者,他希望越簡單越好,對他而言,他希望呼叫一個簡單的API就能完成這件事,比如send(0x53),實際上我們的BLE協議棧就是這樣設計的,開發者只需呼叫send(0x53)就可以把資料傳送出去了,其餘的事情BLE協議棧幫你搞定。很多人會想,BLE協議棧是不是直接在物理層就把0x53發出去,就如下圖所示:

 

這種方式初看起來挺美的,但由於很多細節沒有考慮到,實際是不可行的。首先,它沒有考慮用哪一個射頻通道來進行傳輸,在不更改API的情況下,我們只能對協議棧進行分層,為此引入LL層,開發者還是呼叫send(0x53),send(0x53)再呼叫send_LL(0x53,2402M)(注:2402M為通道頻率)。這裡還有一個問題,裝置B怎麼知道這個資料包是發給自己的還是其他人的,為此BLE引入access address概念,用來指明接收者身份,其中,0x8E89BED6這個access address比較特殊,它表示要發給周邊所有裝置,即廣播。如果你要一對一的進行通訊(BLE協議將其稱為連線),即裝置A的資料包只能裝置B接收,同樣裝置B的資料包只能裝置A接收,那麼就必須生成一個獨特的隨機access address以標識裝置A和裝置B兩者之間的連線。

廣播方式

我們先來看一下簡單的廣播情況,這種情況下,我們把裝置A叫advertiser(廣播者),裝置B叫scanner或者observer(掃描者)。廣播狀態下裝置A的LL層API將變成send_LL(0x53,2402M, 0x8E89BED6)。由於裝置B可以同時接收到很多裝置的廣播,因此資料包還必須包含裝置A的device address(0xE1022AAB753B)以確認該廣播包來自裝置A,為此send_LL引數需要變成(0x53,2402M, 0x8E89BED6, 0xE1022AAB753B)。LL層還要檢查資料的完整性,即資料在傳輸過程中有沒有發生竄改,為此引入CRC24對資料包進行檢驗 (假設為0xB2C78E) 。同時為了調製解調電路工作更高效,每一個數據包的最前面會加上1個位元組的preamble(前導幀),preamble一般為0x55或者0xAA。這樣,整個空中包就變成(注:空中包用小端模式表示!):

 

 上面這個資料包還有如下問題:

  1. 沒有對資料包進行分類組織,裝置B無法找到自己想要的資料0x53。為此我們需要在access address之後加入兩個欄位:LL header和長度位元組。LL header用來表示資料包的LL型別,長度位元組用來指明payload的長度
  2. 裝置B什麼時候開啟射頻視窗以接收空中資料包?如上圖case1所示,當裝置A的資料包在空中傳輸的時候,裝置B把接收視窗關閉,此時通訊將失敗;同樣對case2來說,當裝置A沒有在空中傳送資料包時,裝置B把接收視窗開啟,此時通訊也將失敗。只有case3的情況,通訊才能成功,即裝置A的資料包在空中傳輸時,裝置B正好開啟射頻接收視窗,此時通訊才能成功,換句話說,LL層還必須定義通訊時序
  3. 當裝置B拿到資料0x53後,該如何解析這個資料呢?它到底表示溼度還是電量,還是別的意思?這個就是GAP層要做的工作,GAP層引入了LTV(Length-Type-Value)結構來定義資料,比如020105,02-長度,01-型別(強制欄位,表示廣播flag,廣播包必須包含該欄位),05-值。由於廣播包最大隻能為31個位元組,它能定義的資料型別極其有限,像這裡說的電量,GAP就沒有定義,因此要通過廣播方式把電量資料發出去,只能使用供應商自定義資料型別0xFF,即04FF590053,其中04表示長度,FF表示資料型別(自定義資料),0x0059是供應商ID(自定義資料中的強制欄位),0x53就是我們的資料(裝置雙方約定0x53就是表示電量,而不是其他意思)。

最終空中傳輸的資料包將變成:

  • AAD6BE898E600E3B75AB2A02E102010504FF5900538EC7B2
    • AA – 前導幀(preamble)
    • D6BE898E – 訪問地址(access address)
    • 60 – LL幀頭欄位(LL header)
    • 0E – 有效資料包長度(payload length)
    • 3B75AB2A02E1 – 廣播者裝置地址(advertiser address)
    • 02010504FF590053 – 廣播資料
    • 8EC7B2 – CRC24值

有了PHY,LL和GAP,就可以傳送廣播包了,但廣播包攜帶的資訊極其有限,而且還有如下幾大限制:

  1. 無法進行一對一雙向通訊 (廣播是一對多通訊,而且是單方向的通訊)
  2. 由於不支援組包和拆包,因此無法傳輸大資料
  3. 通訊不可靠及效率低下。廣播通道不能太多,否則將導致掃描端效率低下。為此,BLE只使用37(2402MHz) /38(2426MHz) /39(2480MHz)三個通道進行廣播和掃描,因此廣播不支援跳頻。由於廣播是一對多的,所以廣播也無法支援ACK。這些都使廣播通訊變得不可靠。
  4. 掃描端功耗高。由於掃描端不知道裝置端何時廣播,也不知道裝置端選用哪個頻道進行廣播,掃描端只能拉長掃描視窗時間,並同時對37/38/39三個通道進行掃描,這樣功耗就會比較高。

而連線則可以很好解決上述問題,下面我們就來看看連線是如何將0x53傳送出去的。

連線方式

到底什麼叫連線(connection)?像有線UART,很容易理解,就是用線(Rx和Tx等)把裝置A和裝置B相連,即為連線。用“線”把兩個裝置相連,實際是讓2個裝置有共同的通訊媒介,並讓兩者時鐘同步起來。藍芽連線有何嘗不是這個道理,所謂裝置A和裝置B建立藍芽連線,就是指裝置A和裝置B兩者一對一“同步”成功,其具體包含以下幾方面:

  • 裝置A和裝置B對接下來要使用的物理通道達成一致
  • 裝置A和裝置B雙方建立一個共同的時間錨點,也就是說,把雙方的時間原點變成同一個點
  • 裝置A和裝置B兩者時鐘同步成功,即雙方都知道對方什麼時候傳送資料包什麼時候接收資料包
  • 連線成功後,裝置A和裝置B通訊流程如下所示:

如上圖所示,一旦裝置A和裝置B連線成功(此種情況下,我們把裝置A稱為Master或者Central,把裝置B稱為Slave或者Peripheral),裝置A將週期性以CI(connection interval)為間隔向裝置B傳送資料包,而裝置B也週期性地以CI為間隔開啟射頻接收視窗以接收裝置A的資料包。同時按照藍芽spec要求,裝置B收到裝置A資料包150us,裝置B切換到傳送狀態,把自己的資料發給裝置A;裝置A則切換到接收狀態,接收裝置B發過來的資料。由此可見,連線狀態下,裝置A和裝置B的射頻傳送和接收視窗都是週期性地有計劃地開和關,而且開的時間非常短,從而大大降低系統功耗並大大提高系統效率。

 

現在我們看看連線狀態下是如何把資料0x53傳送出去的,從中大家可以體會到藍芽協議棧分層的妙處。

  • 對開發者來說,很簡單,他只需要呼叫send(0x53)
  • GATT層定義資料的型別和分組,方便起見,我們用0x0013表示電量這種資料型別,這樣GATT層把資料打包成130053(小端模式!)
  • ATT層用來選擇具體的通訊命令,比如讀/寫/notify/indicate等,這裡選擇notify命令0x1B,這樣資料包變成了:1B130053
  • L2CAP用來指定connection interval(連線間隔),比如每10ms同步一次(CI不體現在資料包中),同時指定邏輯通道編號0004(表示ATT命令),最後把ATT資料長度0x0004加在包頭,這樣資料就變為:040004001B130053
  • LL層要做的工作很多,首先LL層需要指定用哪個物理通道進行傳輸(物理通道不體現在資料包中),然後再給此連線分配一個Access address(0x50655DAB)以標識此連線只為裝置A和裝置B直連服務,然後加上LL header和payload length欄位,LL header標識此packet為資料packet,而不是control packet等,payload length為整個L2CAP欄位的長度,最後加上CRC24欄位,以保證整個packet的資料完整性,所以資料包最後變成:
    • AAAB5D65501E08040004001B130053D550F6
      • AA – 前導幀(preamble)
      • 0x50655DAB – 訪問地址(access address)
      • 1E – LL幀頭欄位(LL header)
      • 08 – 有效資料包長度(payload length)
      • 04000400 – ATT資料長度,以及L2CAP通道編號
      • 1B – notify command
      • 0x0013 – 電量資料handle
      • 0x53 – 真正要傳送的電量資料
      • 0xF650D5 – CRC24值
      • 雖然開發者只調用了 send(0x53),但由於低功耗藍芽協議棧層層打包,最後空中實際傳輸的資料將變成下圖所示的模樣,這就既滿足了低功耗藍芽通訊的需求,又讓使用者API變得簡單,可謂一箭雙鵰!

上面只是對BLE協議棧實現原理做了一個簡單概述,即便如此,由於都是關於BLE協議棧底層的東西,很多開發者還是會覺得比較枯燥和晦澀,而且對很多開發者來說,他們也不關心BLE協議棧是如何實現的,他們更關心的是BLE協議棧的使用,即怎麼開發一個BLE應用。BLE應用是實打實的東西,不能像上面講述協議棧一樣泛泛而談,必須結合具體的藍芽晶片和藍芽協議棧來講解,為此後面將以Nordic晶片及協議棧作為範例,來具體講解如何開發BLE應用,以及如何通過程式碼去理解BLE協議中定義的一些概念和術語。

轉載於:https://www.cnblogs.com/iini/p/8969828.html