1. 程式人生 > >低功耗藍芽BLE之連線事件、連線引數和更新方法

低功耗藍芽BLE之連線事件、連線引數和更新方法

連線事件

在一個連線當中,主裝置會在每個連線事件裡向從裝置傳送資料包。一個連線事件是指主裝置和從裝置之間相互發送資料包的過程。連線事件的進行始終位於一個頻率,每個資料包會在上個數據包發完之後等待 150μs 再發送。

連線間隔決定了主裝置與從裝置的互動間隔;它是指兩個連續的連線事件開始處的時間距離,可以是7.5ms ~ 4s內的任意值,但必須為 1.25ms 的整數倍。要確定從裝置與主裝置的實際互動間隔,需要用到從裝置延遲這一引數,代表從裝置在必須偵聽之前可以忽略多少個連線事件。

如下圖所示,連線事件被一個個的連線間隔分開。從主裝置傳送資料包開始,每個連線事件可以持續進行,直至主裝置或從裝置停止響應。在連線事件之外,主從裝置之間不傳送任何資料包。


舉個例子,如果連線間隔為 100ms,從裝置延遲是 9,那麼從裝置可以忽略 9 個連結事件,但不得不偵聽第 10 個連線事件。換言之,從裝置必須每秒偵聽一次,而此時監控超時的最小值應為 1010ms。反過來,另一個極端的例子是,如果監控超時使用了 32s 的最大值,對於間隔為 100ms 的鏈路,從裝置延時必須小於等於 319。

雖然如此,如果將從裝置延遲設為可行的最大值,在監控超時發生前從裝置只能獲得唯一一次偵聽主裝置的機會,這可不是一個好主意。因此,建議至少給從裝置留出 次偵聽的機會。在前面的例子中,如果連線間隔為 100ms ,從裝置延遲為 9,那麼監控超時應該至少為 6s,這樣一來,鏈路在最終斷開前從裝置至少會有 

次偵聽的機會。

連線引數介紹

主裝置和從裝置建立連線之後,所有的資料通訊都是在連線事件(Connection Events)中進行的。


尖刺的波就是連線事件(Connection events),剩下的Sleeping是睡眠時間,裝置在建立連線之後的大多數時間都是處於Sleeping,這種情況下耗電量比較低,而在連線事件(Connection events)中,耗電量就相對高很多,這也是BLE為什麼省電的原因之一。

每個連線事件(Connection events)中,都需要由Master發起包,再由Slave回覆。

Master即主機,簡稱MSlave即從機,簡稱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為單元,連線間隔的範圍是63200既7.5ms4s之間。

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為 3Master發包,Slave沒有資料要回復的時候,就會忽略 個連線事件,在第 個連線事件接收到Master傳送的資料之後,回覆Master。如果Slave有資料要傳送就會喚醒,也就是說即使Slave Latency為 3,但是在Master發第二包的時候Slave有資料要回復,這個時候就會立即回覆Master而不是等到 個連線事件之後的第 個連線事件去回覆。

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,則從裝置每 個連線事件同步一次,實現其所期望的 600ms 間隔;如果主裝置選擇 300ms,則從裝置忽略每隔一個連線事件同步一次;如果主裝置選擇 400ms,則從裝置每 400ms 同步一次。

下面介紹一下在TICC2540CC2541中,連線引數修改的方法

(一)連線成功建立之後從裝置自動申請修改連線引數。

我們以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從裝置的預設申請的連線引數一定要滿足上述要求,並且連線過程中修改連線引數的時候也要滿足上述要求。