轉自:http://blog.csdn.net/zzfenglin/article/details/51304084
連接事件
在一個連接當中,主設備會在每個連接事件里向從設備發送數據包。一個連接事件是指主設備和從設備之間相互發送數據包的過程。連接事件的進行始終位於一個頻率,每個數據包會在上個數據包發完之后等待 150μs 再發送。
連接間隔決定了主設備與從設備的交互間隔;它是指兩個連續的連接事件開始處的時間距離,可以是7.5ms ~ 4s內的任意值,但必須為 1.25ms 的整數倍。要確定從設備與主設備的實際交互間隔,需要用到從設備延遲這一參數,代表從設備在必須偵聽之前可以忽略多少個連接事件。
如下圖所示,連接事件被一個個的連接間隔分開。從主設備發送數據包開始,每個連接事件可以持續進行,直至主設備或從設備停止響應。在連接事件之外,主從設備之間不發送任何數據包。
舉個例子,如果連接間隔為 100ms,從設備延遲是 9,那么從設備可以忽略 9 個鏈接事件,但不得不偵聽第 10 個連接事件。換言之,從設備必須每秒偵聽一次,而此時監控超時的最小值應為 1010ms。反過來,另一個極端的例子是,如果監控超時使用了 32s 的最大值,對於間隔為 100ms 的鏈路,從設備延時必須小於等於 319。
雖然如此,如果將從設備延遲設為可行的最大值,在監控超時發生前從設備只能獲得唯一一次偵聽主設備的機會,這可不是一個好主意。因此,建議至少給從設備留出 6 次偵聽的機會。在前面的例子中,如果連接間隔為 100ms ,從設備延遲為 9,那么監控超時應該至少為 6s,這樣一來,鏈路在最終斷開前從設備至少會有 6 次偵聽的機會。
連接參數介紹
主設備和從設備建立連接之后,所有的數據通信都是在連接事件(Connection Events)中進行的。
尖刺的波就是連接事件(Connection events),剩下的Sleeping是睡眠時間,設備在建立連接之后的大多數時間都是處於Sleeping,這種情況下耗電量比較低,而在連接事件(Connection events)中,耗電量就相對高很多,這也是BLE為什么省電的原因之一。
每個連接事件(Connection events)中,都需要由Master發起包,再由Slave回復。
Master即主機,簡稱M;Slave即從機,簡稱S。抓包過程中看到的M->S或者S->M即主機到從機或者從機到主機。
連接參數 (Connection Parameters):
通過修改下面三個參數,就可以設置BLE連接過程中的傳輸速度和功耗。
1.Connection Interval(連接間隔)
Connection Interval(GAPROLE_MIN_CONN_INTERVAL && GAPROLE_MAX_CONN_INTERVAL)連接間隔,在BLE的兩個設備的連接中使用跳頻機制。兩個設備使用特定的信道發送和接收數據,然后過一段時間后再使用新的信道(BLE協議棧的鏈路層處理信道的切換)。兩個設備在切換信道后發送和接收數據稱為一個連接事件。盡管沒有應用數據被發送和接收,兩個設備仍舊會交換鏈路層數據(空包 Empty PDU)來維持連接。
這個連接間隔就是指在一個連接事件(Connection events)的開始到下一個連接事件(Connection events)的開始的時間間隔。連接間隔以1.25ms為單元,連接間隔的范圍是6 ~ 3200既7.5ms ~ 4s之間。
2.Slave Latency(從設備延遲或者從設備時延)

允許Slave(從設備)在沒有數據要發的情況下,跳過一定數目的連接事件(Connection events),在這些連接事件(Connection events)中不必回復Master(主設備)的包,這樣就能更加省電。
范圍可以是0 ~ 499
更詳細的使用解析如下:

Slave Latency = OFF也就是Slave Latency為0時,Master發包,Slave必須回復,如果不回復,Master就會認為Slave那邊接收不正常。
Slave Latency = ON也就是Slave Latency不為0的時候,圖中Slave Latency為 3。Master發包,Slave沒有數據要回復的時候,就會忽略 3 個連接事件,在第 4 個連接事件接收到Master發送的數據之后,回復Master。如果Slave有數據要發送就會喚醒,也就是說即使Slave Latency為 3,但是在Master發第二包的時候Slave有數據要回復,這個時候就會立即回復Master而不是等到 3 個連接事件之后的第 4 個連接事件去回復。
3.Supervision Timeout(超時時間或者監控超時)

這個參數設定了一個超時時間,如果BLE在這個時間內沒有發生通信的話,就會自動斷開。
單位是 10ms,該變量的范圍是10 ~ 3200,折算成時間范圍是100ms ~ 32s 。
連接間隔、從機時延以及超時時間這三者必須滿足如下公式:
Supervision Timeout > (1 +slaveLatency)* (connectionInterval)
上述公式必須滿足,否則連接就會不正常斷開。
這三個連接參數不同情況下對通信速率和功耗的影響:
1.Connection Interval縮短,Master和Slave通信更加頻繁,提高數據吞吐速度,縮短了數據發送的時間,當然也增加了功耗。
2.Connection Interval增長,通信頻率降低,數據吞吐速度降低,增加了數據發送的時間,當然,這種設置降低了功耗。
3.Slave Latency減少或者設置為 0,每次Connection Events中都需要回復Master的包,功耗會上升,數據發送速度會提高。
4.Slave Latency加長,功耗下降,數據發送速度降低。
連接參數更新規程
連接建立時,主設備通過鏈接請求數據包發送連接參數。當連接活躍了一段時間,連接參數也許不再適用於當前使用的服務。出於提高效率的目的,連接參數需要進行更新。較之首先斷開連接、接着更換新參數重新連接,還有一種在鏈路中更新參數更為簡單的途徑,如下圖所示:

為此,主設備向從設備發送連接更新請求,即LL_CONNECTION_UPDATE_REQ,當中攜帶了新的參數。這些參數不必進行協商,從設備或者接受和使用它們,或者斷開鏈路。連接更新請求中包含了早先創建連接時用過的一部分參數,還有一個稱為瞬時(instant)的新參數:
1.傳輸窗口大小
2.傳輸窗口偏移量
3.連接間隔
4.從設備延遲
5.監控超時
6.瞬時
瞬時參數決定了連接更新的開始時刻。發送消息時,主設備為連接更新選定一個未來的時間點,並且放在消息中。接到消息后,從設備會記住這個未來的時刻,屆時再切換至新的連接參數。這有助於解決無線系統里的一個最大問題----報文重傳。只要數據包的重傳次數足夠,並最終在瞬時之前傳輸成功,上述過程執行起來就不會有問題。但是,如果該數據包屆時沒能完成傳輸,鏈路就有可能丟失。
由於低功耗藍牙沒有時鍾,要決定瞬時時刻只有依靠計算連接事件的個數。因此,每一個連接事件都會被計數,鏈路上的第一個連接事件,也就是在連接請求之后的位於首個傳輸窗口里的連接事件記為 0。因此,瞬時實際上是一個連接事件的計數器,相應的連接事件到來時就使用新的參數。為了讓從設備收到數據包,主設備必須為其提供足夠的機會。不過從設備延遲是多少,都應該至少保證 6 次數據發送機會。也就是說,如果從設備延遲為 500ms,那么瞬時通常被設定在 3s 之后的某個未來時刻。
瞬時到來時,從設備開始偵聽發送窗口,就好像連接建立的過程那樣。主設備能夠調整從設備的計時,總體而言不超過 1.25ms。不過,由於主設備可能還是一個經典藍牙設備,上述調整使其得以協調低功耗藍牙從設備,從而更好地完成調度。一旦該過程結束,新的連接間隔、監控超時、從設備延遲值將投入使用。
連接參數的修改
“連接參數更新請求”命令可以讓從設備更新鏈路層的連接參數,如下圖所示。這些參數包括連接間隔(從設備希望主設備允許從設備發送數據包的頻率)、從設備延遲(從設備能夠忽略主設備的連接事件的最大值)以及監控超時。
在連接中,如果從設備希望修改當前的連接參數則可以使用該命令。比方說,如果連接事件的間隔有可能太快了,導致過多的電量浪費。這在從設備時延很大時沒有問題,但如果不是這樣,從設備將會頻繁的偵聽鏈路。這在一些情況下是必要的,例如設備間首次綁定、互發多個數據包、探索服務和設備特性等。但在很多其他情況下,盡可能地減少從設備必須偵聽連接事件的數量對提高電池壽命至關重要。
連接參數更新請求命令僅用於從設備向主設備發送,這是由於主設備隨時都能啟動鏈路層連接參數更新控制(Connection Parameter Update Control)規程。如果該命令由主設備發送,從設備會將其視為一個錯誤,並返回帶有“命令不理解”原因代碼的“命令拒絕”命令。
從設備可以在任何時候發送該命令;收到該信息的主設備如果可以修改連接參數,則將返回“連接參數更新響應”(Connection Parameter Update Response),其中的結果代碼設為“接受(accepted)”。隨后,主設備將會啟動鏈路層連接參數更新控制規程。
當然,如果主設備不同意從設備的請求參數,它可以發送結果代碼為“拒絕(rejected)”的連接參數更新響應命令以拒絕請求。此時從設備有兩個選擇:要么接受主設備希望的正在使用的連接參數,要么終止連接。終止連接的做法咋看起來可能讓人覺得很激進,但是,假如使用當前的參數從設備將會在一周內耗盡電量,而使用請求的參數則可以持續數年,很明顯,合理的選擇只有一個。
修改連接參數時,如果要減少主設備拒絕從設備請求的可能性,可以在請求里設置一個可接受的參數范圍。經過精心設計的從設備會樂意接受很寬的參數范圍。由於主設備可能正忙於實時會話音頻連接或者高質量語音連接等任務,它可以接受一定范圍內的連接間隔參數。設備可接受的間隔參數會根據當前任務的不同而不同,可能有別於上一次設備連接時的參數。
要提高主設備接受連接參數的機率,還有個方法是從設備提供一個合理的從設備延遲。主設備可以選擇最合適的連接事件間隔,從設備則使用最佳功耗的從設備延遲參數。
舉個例子,如果從設備想每 600ms 同步一次,它可以請求范圍 100ms ~ 750ms 的連接間隔參數,並帶上從設備延遲5。如果主設備選擇 100ms,則從設備每6個連接事件同步一次;如果主設備選擇 200ms,則從設備每 3 個連接事件同步一次,實現其所期望的 600ms 間隔;如果主設備選擇 300ms,則從設備忽略每隔一個連接事件同步一次;如果主設備選擇 400ms,則從設備每 400ms 同步一次。
下面介紹一下在TI的CC2540和CC2541中,連接參數修改的方法。
(一)連接成功建立之后從設備自動申請修改連接參數。
我們以TI 1.4.0協議棧中的“simpleBLEPeripheral”工程為例來進行講解,在這個工程的“Projects\ble\SimpleBLEPeripheral\Source\simpleBLEPeripheral.c”應用文件中定義了如下的宏:
- // Whether to enable automatic parameter update request when a connection is formed
- #define DEFAULT_ENABLE_UPDATE_REQUEST TRUE
從上面的注釋中,我們可以看出這個宏的作用是當一個連接建立的時候,是否需要自動申請連接參數更新。當設置為“TRUE”的時候就是需要,當設置為“FALSE”的時候就是不需要。那這個宏到底是如何起作用的呢?下面我們來看一下。
1.“Projects\ble\SimpleBLEPeripheral\Source\simpleBLEPeripheral.c”應用文件中的“SimpleBLEPeripheral_Init”初始化方法里對該宏進行了處理,源碼如下:
- uint8 enable_update_request = DEFAULT_ENABLE_UPDATE_REQUEST;
- GAPRole_SetParameter( GAPROLE_PARAM_UPDATE_ENABLE, sizeof( uint8 ), &enable_update_request );
2.我們看下“GAPRole_SetParameter”方法里面的相關的操作,相關操作在“Projects\ble\Profiles\Roles\peripheral.c”文件里,源碼如下:
- case GAPROLE_PARAM_UPDATE_ENABLE:
- if ( (len == sizeof ( uint8 )) && (*((uint8*)pValue) <= TRUE) )
- {
- gapRole_ParamUpdateEnable = *((uint8*)pValue);
- }
- else
- {
- ret = bleInvalidRange;
- }
- break;
3.從上面的代碼不難看出,將我們設置的宏賦給了“gapRole_ParamUpdateEnable”全局變量,下面我們在本文件中搜索一下該全局變量使用的地方,發現在如下源碼中用到了:
- // 連接成功建立之后底層返回的事件
- case GAP_LINK_ESTABLISHED_EVENT:
- {
- gapEstLinkReqEvent_t *pPkt = (gapEstLinkReqEvent_t *)pMsg;
- if ( pPkt->hdr.status == SUCCESS )
- {
- VOID osal_memcpy( gapRole_ConnectedDevAddr, pPkt->devAddr, B_ADDR_LEN );
- gapRole_ConnectionHandle = pPkt->connectionHandle;
- gapRole_state = GAPROLE_CONNECTED;
- if ( gapRole_RSSIReadRate )
- {
- // Start the RSSI Reads
- VOID osal_start_timerEx( gapRole_TaskID, RSSI_READ_EVT, gapRole_RSSIReadRate );
- }
- // Store connection information
- // 保存連接剛建立時的連接參數
- gapRole_ConnInterval = pPkt->connInterval;
- gapRole_ConnSlaveLatency = pPkt->connLatency;
- gapRole_ConnTimeout = pPkt->connTimeout;
- // Check whether update parameter request is enabled
- // 檢測更新連接參數請求是否被使能
- if ( gapRole_ParamUpdateEnable == TRUE )
- {
- // Get the minimum time upon connection establishment before the
- // peripheral can start a connection update procedure.
- // 獲取設置的時間間隔,從機將在連接建立之后
- // 延時至少該時間間隔之后觸發連接參數更新事
- // 件。
- uint16 timeout = GAP_GetParamValue( TGAP_CONN_PAUSE_PERIPHERAL );
- // 在延時timeout*1000 ms之后觸發連接參數更新事件
- osal_start_timerEx( gapRole_TaskID, START_CONN_UPDATE_EVT, timeout*1000 );
- }
4.上面的注釋非常清楚了,在連接成功建立返回的事件中判斷我們設置的宏,如果設置為“TRUE”,那就獲取我們設置的時間間隔,在延時我們設置的時間間隔(上面注釋中提到至少,因為用的是系統定時器,有可能在執行別的事件,所以實際的延時時間會大於我們設置的時間,當然,一般情況下偏移的那點時間是可以忽略的)之后,觸發連接參數更新事件,進行連接參數的更新。那上面源碼中獲取的時間間隔以及后面要更新的連接參數是在什么地方設置的呢?下面我們繼續回到應用層文件中查看相關設置。
5.在“Projects\ble\SimpleBLEPeripheral\Source\simpleBLEPeripheral.c”文件中定義了如下宏用來設置自動更新連接參數時,相關連接參數的值,源碼如下:
- // Minimum connection interval (units of 1.25ms, 80=100ms) if automatic parameter update request is enabled
- // 如果自動更新連接參數請求被使能的話,用到的最小連接間隔,單位1.25 ms
- #define DEFAULT_DESIRED_MIN_CONN_INTERVAL 80
- // Maximum connection interval (units of 1.25ms, 800=1000ms) if automatic parameter update request is enabled
- // 如果自動更新連接參數請求被使能的話,用到的最大連接間隔,單位1.25 ms
- #define DEFAULT_DESIRED_MAX_CONN_INTERVAL 800
- // Slave latency to use if automatic parameter update request is enabled
- // 如果自動更新連接參數請求被使能的話,用到的從機時延
- #define DEFAULT_DESIRED_SLAVE_LATENCY 0
- // Supervision timeout value (units of 10ms, 1000=10s) if automatic parameter update request is enabled
- // 如果自動更新連接參數請求被使能的話,用到的超時時間,單位10 ms
- #define DEFAULT_DESIRED_CONN_TIMEOUT 1000
- // Connection Pause Peripheral time value (in seconds)
- // 如果自動更新連接參數請求被使能的話,用到的時間間隔,單位s
- #define DEFAULT_CONN_PAUSE_PERIPHERAL 6
6.通過上述宏進行相應設置的地方在“Projects\ble\SimpleBLEPeripheral\Source\simpleBLEPeripheral.c”文件的“SimpleBLEPeripheral_Init”初始化方法中,源碼如下:
- VOID GAP_SetParamValue( TGAP_CONN_PAUSE_PERIPHERAL, DEFAULT_CONN_PAUSE_PERIPHERAL );
- uint16 desired_min_interval = DEFAULT_DESIRED_MIN_CONN_INTERVAL;
- uint16 desired_max_interval = DEFAULT_DESIRED_MAX_CONN_INTERVAL;
- uint16 desired_slave_latency = DEFAULT_DESIRED_SLAVE_LATENCY;
- uint16 desired_conn_timeout = DEFAULT_DESIRED_CONN_TIMEOUT;
- GAPRole_SetParameter( GAPROLE_MIN_CONN_INTERVAL, sizeof( uint16 ), &desired_min_interval );
- GAPRole_SetParameter( GAPROLE_MAX_CONN_INTERVAL, sizeof( uint16 ), &desired_max_interval );
- GAPRole_SetParameter( GAPROLE_SLAVE_LATENCY, sizeof( uint16 ), &desired_slave_latency );
- GAPRole_SetParameter( GAPROLE_TIMEOUT_MULTIPLIER, sizeof( uint16 ), &desired_conn_timeout );
上述操作在“Projects\ble\Profiles\Roles\peripheral.c”文件里的具體實現我們就不一起看了,因為里面其實就是一個賦值的過程,所以大家自行查看即可。
(二)連接成功建立之后從設備在需要的時候去修改某個連接參數或者全部的連接參數。
1.修改單個連接參數的方法
修改最小連接間隔
- uint16 desired_min_interval = DEFAULT_DESIRED_MIN_CONN_INTERVAL;
- GAPRole_SetParameter( GAPROLE_MIN_CONN_INTERVAL, sizeof( uint16 ), &desired_min_interval );
修改最大連接間隔
- uint16 desired_max_interval = DEFAULT_DESIRED_MAX_CONN_INTERVAL;
- GAPRole_SetParameter( GAPROLE_MAX_CONN_INTERVAL, sizeof( uint16 ), &desired_max_interval );
修改從設備延遲
- uint16 desired_slave_latency = DEFAULT_DESIRED_SLAVE_LATENCY;
- GAPRole_SetParameter( GAPROLE_SLAVE_LATENCY, sizeof( uint16 ), &desired_slave_latency );
修改超時時間
- uint16 desired_conn_timeout = DEFAULT_DESIRED_CONN_TIMEOUT;
- GAPRole_SetParameter( GAPROLE_TIMEOUT_MULTIPLIER, sizeof( uint16 ), &desired_conn_timeout );
2.所有連接參數一起修改的方法
- uint16 minConnInterval;
- uint16 maxConnInterval;
- uint16 slaveLatency;
- uint16 timeoutMultiplier;
- // Update connection parameters
- GAPRole_SendUpdateParam( minConnInterval, maxConnInterval, slaveLatency, timeoutMultiplier, GAPROLE_TERMINATE_LINK);
GAPRole_SendUpdateParam傳入的前四個參數在之前都已經介紹過了,下面介紹下最后一個參數,最后一個參數設置的是連接參數更新失敗后的操作,可取值定義在peripheral.h文件中,如下:
- /**
- * Possible actions the peripheral device may take if an unsuccessful parameter
- * update is received.
- *
- * Parameters for GAPRole_SendUpdateParam() only
- */
- #define GAPROLE_NO_ACTION 0 // Take no action upon unsuccessful parameter updates
- #define GAPROLE_RESEND_PARAM_UPDATE 1 // Continue to resend request until successful update
- #define GAPROLE_TERMINATE_LINK 2 // Terminate link upon unsuccessful parameter updates
GAPROLE_NO_ACTION:沒有任何動作
GAPROLE_RESEND_PARAM_UPDATE:重新發送參數更新請求
GAPROLE_TERMINATE_LINK:斷開連接
對於上述介紹的兩種修改連接參數的方法,個人建議還是采用第二種方法,因為第一種方法在修改某一個參數的時候,可能會導致其他參數的變化,比如我們只修改了連接間隔,但從機延時可能會隨之改變,這樣我們就無法根據自己的需求控制連接參數的更新。
第二種方法在使用的時候有可能碰到一種情況,就是我們只想修改某一個或者某兩個連接參數,剩下的參數想保持原有的,這樣的話,我們需要在修改連接參數之前先去讀取連接參數,然后將需要修改的參數進行重新設置即可,讀取連接參數的代碼如下:
- uint16 interval;
- uint16 latency;
- uint16 timeout;
- GAPRole_GetParameter(GAPROLE_CONN_INTERVAL, &interval);
- GAPRole_GetParameter(GAPROLE_CONN_LATENCY, &latency);
- GAPRole_GetParameter(GAPROLE_CONN_TIMEOUT, &timeout);
下面我們通過一個實例來具體了解下連接參數修改的方法,需求是將連接間隔修改為25,從機延遲修改為8,超時時間不修改,更新失敗后重新發送參數更新請求,本實例中主設備是安卓設備,從設備是CC2541。代碼實現如下:
- uint16 interval;
- uint16 latency;
- uint16 timeout;
- GAPRole_GetParameter(GAPROLE_CONN_INTERVAL, &interval);
- GAPRole_GetParameter(GAPROLE_CONN_LATENCY, &latency);
- GAPRole_GetParameter(GAPROLE_CONN_TIMEOUT, &timeout);
- GAPRole_SendUpdateParam( 25, 25, 8, timeout, GAPROLE_RESEND_PARAM_UPDATE);
該過程抓包顯示如下:

從抓到的包中我們看到首先是S->M,即從設備發送連接參數更新請求,請求中帶有申請的連接參數,然后M->S,即主設備返回連接參數更新響應,Result為0,表示同意修改更新。最后M->S發送Data type為Control的鏈路層連接參數更新控制規程,攜帶同意的連接參數,這樣,新的連接參數就會投入使用。
注意修改連接參數的時候要滿足一定的要求:
1.安卓設備作主設備時,連接參數滿足的要求見本篇博文第二節“連接參數介紹”中提到的內容。另外實際開發過程中發現安卓設備作主設備時存在一個問題,就是部分安卓設備連接BLE設備之后,只能進行一次連接參數的修改。
2. 蘋果系統設備作主設備時,連接參數更新的要求比較苛刻,如下:
Interval Max * (Slave Latency + 1) ≤ 2 seconds
Interval Min ≥ 20 ms
Interval Min + 20 ms ≤ Interval Max
Slave Latency ≤ 4
connSupervisionTimeout ≤ 6 seconds
Interval Max * (Slave Latency + 1) * 3 < connSupervisionTimeout
即:
最大連接間隔時間 *(從機延遲 + 1) ≤ 2s
最小連接間隔時間 ≥ 20 ms
最小連接間隔時間 + 20 ms ≤ 最大連接間隔時間
從機延遲 ≤ 4
超時時間 ≤ 6s
最大連接間隔時間 *(從機延遲 + 1)* 3 < 超時時間
所以如果你的BLE從設備需要被iOS主設備連接,那你的BLE從設備的默認申請的連接參數一定要滿足上述要求,並且連接過程中修改連接參數的時候也要滿足上述要求。