接上一篇整體架構(gòu)介紹后,相信大家對(duì)NXP低功耗協(xié)議的使用還是沒有什么概念(我沒寫錯(cuò)你也沒看錯(cuò))。由于第一篇博文信息量過大,沒有在NXP BLE SDK上做過一定開發(fā)的的同學(xué)看起來肯定是云里霧里。從本篇開始將BLE SDK中逐個(gè)功能進(jìn)行剖析,并且盡可能按照由淺入深的順序發(fā)布。
前置條件
由于網(wǎng)上有大量的文章介紹BLE技術(shù),這兒就不從零講起基本概念了,假定各位同學(xué)對(duì)BLE廣播與掃描功能有基本的了解,至少知道得以下幾個(gè)方面:
- 廣播和掃描的意義(為什么雙方要做這件事)
- 雙方的角色扮演(簡(jiǎn)單說主機(jī)掃描,從機(jī)廣播)
- 基本的廣播參數(shù):時(shí)間間隔,廣播通道,掃描占空比等
- 廣播中帶有一些數(shù)據(jù),如果知道他們的格式就更好了
雖然文章主要目的是要在介紹如何在NXP低功耗藍(lán)牙SDK中進(jìn)行廣播和掃描,但如果一上來就講API又過于干澀。在涉及到筆者認(rèn)為有必要展開的協(xié)議內(nèi)容的時(shí)候我還是會(huì)將一下原理的。
BLE 5對(duì)廣播和掃描這部分有一些比較大的變化,目前市面上相關(guān)的應(yīng)用還比較少,因此本篇仍以BLE 4.2規(guī)范中定義的功能作為出發(fā)點(diǎn),后續(xù)如有必要再單獨(dú)介紹BLE 5帶來的廣播擴(kuò)展功能。
什么時(shí)候可以開始廣播 & 掃描?
在BLE協(xié)議規(guī)范中,對(duì)主機(jī)(Host)和控制器(Controller)初始化的流程有一個(gè)清晰定義,用戶要走完這個(gè)流程才能向協(xié)議棧提交請(qǐng)求,同時(shí)BLE芯片也需要對(duì)射頻部分寄存器進(jìn)行初始化來保證RF電路的正常工作。NXP BLE SDK中的系統(tǒng)入口main_task()任務(wù)體實(shí)現(xiàn)里,在正式進(jìn)入事件loop之前將會(huì)調(diào)用Ble_Initialize()來準(zhǔn)備以上兩項(xiàng)工作。注意!是準(zhǔn)備,而非完成。也就是說該函數(shù)返回后,協(xié)議??赡苋匀粵]有準(zhǔn)備好。記得千萬不要直接在這個(gè)函數(shù)后面開啟廣播和掃描!
那何時(shí)才可以呢?初始化過程在調(diào)用Ble_Initialize()時(shí)SDK會(huì)讓安裝一個(gè)默認(rèn)的回調(diào)函數(shù)App_GenericCallback(),回調(diào)觸發(fā)后再由她轉(zhuǎn)而觸發(fā)用戶層的BleApp_GenericCallback()。當(dāng)協(xié)議棧完成所有初始化工作后,用戶將在這個(gè)回調(diào)函數(shù)里收到gInitializationComplete_c事件,這才標(biāo)志著用戶可以正常使用BLE協(xié)議棧提供的服務(wù)了。
廣播(advertising)
在NXP BLE SDK中涉及到廣播主要是4個(gè)API:
// 設(shè)置廣播數(shù)據(jù)(和掃描回復(fù)數(shù)據(jù))
bleResult_t Gap_SetAdvertisingData(gapAdvertisingData_t* pAdvertisingData,
gapScanResponseData_t* pScanResponseData);
// 設(shè)置廣播參數(shù)
bleResult_t Gap_SetAdvertisingParameters(gapAdvertisingParameters_t*
pAdvertisingParameters);
// 開啟廣播
bleResult_t Gap_StartAdvertising(gapAdvertisingCallback_t advertisingCallback,
gapConnectionCallback_t connectionCallback);
// 停止廣播
bleResult_t Gap_StopAdvertising(void);
以上的4個(gè)API都是異步的,就意味著調(diào)用后會(huì)立刻得到返回值,該返回值僅表示函數(shù)調(diào)用(如參數(shù)傳遞是否正確,內(nèi)存是否充足)的結(jié)果,實(shí)際功能的執(zhí)行完成,用戶應(yīng)等到各個(gè)的回調(diào)事件到來作為判斷。
下表列出了相關(guān)的事件:
| 事件Tag | 觸發(fā)函數(shù)&事件 | 用戶回調(diào)函數(shù) |
|---|---|---|
| gAdvertisingDataSetupComplete_c | Gap_SetAdvertisingData | 通用回調(diào)函數(shù)BleApp_GenericCallback
|
| gAdvertisingParametersSetupComplete_c | Gap_SetAdvertisingParameters | 通用回調(diào)函數(shù)BleApp_GenericCallback
|
| gAdvertisingStateChanged_c * | Gap_StartAdvertising Gap_StopAdvertising | 廣播回調(diào)函數(shù)BleApp_AdvertisingCallback
|
注意:用戶需要自己記錄當(dāng)該事件產(chǎn)生時(shí)廣播是被打開了還是被關(guān)閉了。
通常用戶需要先設(shè)置好廣播數(shù)據(jù)和廣播參數(shù)再開啟廣播,需要按照一定順序調(diào)用這幾個(gè)API(利用回調(diào)作為銜接)。在使用NXP BLE SDK的時(shí),ble conn manager和每一份例程代碼都已經(jīng)幫用戶把廣播流程規(guī)劃好了,用戶只需要在合適的時(shí)候調(diào)用例程代碼中的 BleApp_Advertise()即可。如果例程帶的廣播策略符合用戶的需求,則用戶只需要關(guān)心app_config.c中所填充的廣播數(shù)據(jù)和廣播參數(shù)即可。
廣播數(shù)據(jù)
根據(jù)BLE協(xié)議規(guī)定,廣播包可以發(fā)送最多31個(gè)字節(jié)的數(shù)據(jù),如果設(shè)備支持掃描請(qǐng)求(Scan Request),還可以在掃描回復(fù)(Scan Response)里在回復(fù)31個(gè)字節(jié),這樣最長(zhǎng)也就是62字節(jié)的信息量。這些信息不是隨便填的,必須按照BLE協(xié)議規(guī)范定義的格式,主機(jī)才能正確的解析其中的內(nèi)容。一個(gè)廣播包(或者Scan Response包)由如若干AD Structure結(jié)構(gòu)組成,在代碼層面,NXP協(xié)議棧提供了一個(gè)由gapAdStructure_t組成的結(jié)構(gòu)體數(shù)組,用戶可以將AD數(shù)據(jù)段依次填入。下面是一個(gè)有3個(gè)AD Structure的示例:
static const gapAdStructure_t advScanStruct[3] = {
{
.length = NumberOfElements(adData0) + 1,
.adType = gAdFlags_c,
.aData = (uint8_t *)adData0
},
{
.length = NumberOfElements(uuid_service_qpps) + 1,
.adType = gAdComplete128bitServiceList_c,
.aData = (uint8_t *)uuid_service_qpps
},
{
.adType = gAdShortenedLocalName_c,
.length = 8,
.aData = (uint8_t*)"NXP_QPPS"
}
};
詳細(xì)格式請(qǐng)參照Core Spec中GAP章節(jié)以及CSS(核心規(guī)范補(bǔ)充)。
廣播參數(shù)說明
typedef struct gapAdvertisingParameters_tag {
uint16_t minInterval;
uint16_t maxInterval;
bleAdvertisingType_t advertisingType;
bleAddressType_t ownAddressType
bleAddressType_t peerAddressType;
bleDeviceAddress_t peerAddress;
gapAdvertisingChannelMapFlags_t channelMap;
gapAdvertisingFilterPolicy_t filterPolicy;
} gapAdvertisingParameters_t;
| 成員 | 取值范圍 | 說明 |
|---|---|---|
| minInterval | 0x20 - 0x4000 | 20ms - 10.24s(步進(jìn)0.625ms),最小建議廣播間隔 |
| maxInterval | 0x20 - 0x4000 | 20ms - 10.24s(步進(jìn)0.625ms),最大建議廣播間隔 |
| advertisingType | gAdvConnectableUndirected_c, gAdvDirectedHighDutyCycle_c, gAdvScannable_c, gAdvNonConnectable_c, gAdvDirectedLowDutyCycle_c | 四種廣播策略(其中定向廣播還分兩種) |
| ownAddressType | gBleAddrTypePublic_c 或 gBleAddrTypeRandom_c | 該參數(shù)決定廣播包的地址類型 |
| peerAddressType | gBleAddrTypePublic_c 或 gBleAddrTypeRandom_c | 該參數(shù)只在定向廣播時(shí)有效,決定了定向廣播包中填寫的對(duì)方地址類型 |
| peerAddress | 48位藍(lán)牙地址 | 該參數(shù)只在定向廣播時(shí)有效,決定了定向廣播包中填寫的對(duì)方藍(lán)牙地址 |
| channelMap | gAdvChanMapFlag37_c, gAdvChanMapFlag38_c,gAdvChanMapFlag39_c | 廣播通道的bitmap |
| filterPolicy | gProcessAll_c,gProcessConnAllScanWL_c,gProcessScanAllConnWL_c,gProcessWhiteListOnly_c | 廣播白名單策略 |
掃描(scan)
協(xié)議棧涉及到掃描有3個(gè)API:
// 設(shè)置掃描策略
bleResult_t Gap_SetScanMode(gapScanMode_t scanMode,
gapAutoConnectParams_t* pAutoConnectParams);
// 開啟掃描
bleResult_t Gap_StartScanning(gapScanningParameters_t* ScanningParameters,
gapScanningCallback_t scanningCallback,
bool_t enableFilterDuplicates);
// 停止掃描
bleResult_t Gap_StopScaning(void);
同廣播API,他們也都是異步執(zhí)行的。其中Gap_SetScanMode是一個(gè)可選的API,用戶可以在開啟掃描之前通過這個(gè)函數(shù)來配置掃描的策略,以決定是否將所有掃描到的從設(shè)備都拋給應(yīng)用層(默認(rèn)),或者僅對(duì)‘LimitedDiscovery’或'GeneralDiscovery'的廣播進(jìn)行上報(bào)。同時(shí)還定義是否主機(jī)自動(dòng)連接指定的從機(jī),無需用戶在應(yīng)用層顯示調(diào)用第三部分要介紹的Gap_Connect動(dòng)作,在此模式下不會(huì)上報(bào)被掃描的從機(jī)。枚舉類型gapScanMode_tag的定義如下:
typedef enum gapScanMode_tag {
gDefaultScan_c,
gLimitedDiscovery_c,
gGeneralDiscovery_c,
gAutoConnect_c
} gapScanMode_t;
Gap_StartScanning 的參數(shù)是自解釋的,主要工作也是在app_config.c中填寫掃描參數(shù)結(jié)構(gòu)體ScanningParameters,下面小節(jié)對(duì)每個(gè)參數(shù)作了說明。
掃描參數(shù)說明
typedef struct gapScanningParameters_tag {
bleScanType_t type;
uint16_t interval;
uint16_t window;
bleAddressType_t ownAddressType;
bleScanningFilterPolicy_t filterPolicy;
} gapScanningParameters_t;
| 成員 | 取值范圍 | 說明 |
|---|---|---|
| type | gScanTypePassive_c 或 gScanTypeActive_c | 選擇被動(dòng)或者主動(dòng)掃描 |
| interval | 0x04 - 0x4000 | 2.5ms - 10.24s(步進(jìn)0.625ms),兩次掃描的間隔 |
| window | 0x04 - 0x4000 | 2.5ms - 10.24s(步進(jìn)0.625ms),每次掃描的持續(xù)事件,當(dāng)window = interval時(shí)連續(xù)掃描 |
| ownAddressType | gBleAddrTypePublic_c 或 gBleAddrTypeRandom_c | 該參數(shù)決定在Scan request時(shí)主機(jī)發(fā)出的包的地址類型 |
| filterPolicy | gScanAll_c 或 gScanWithWhiteList_c | 是否啟用Scan白名單 |
連接 (connect)
主設(shè)備在完成掃描流程后,獲取了周圍設(shè)備的列表和基本信息。如果要進(jìn)一步與某一個(gè)設(shè)備進(jìn)行用戶數(shù)據(jù)交互則雙方需要進(jìn)入連接狀態(tài)(非beacon的應(yīng)用)。
建立(和斷開)BLE連接只涉及到2個(gè)API和2個(gè)Event:
// 建立連接,僅主設(shè)備允許調(diào)用
bleResult_t Gap_Connect(gapConnectionRequestParameters_t* pParameters,
gapConnectionCallback_t connCallback);
// 斷開連接,主從都可以調(diào)用
bleResult_t Gap_Disconnect(deviceId_t deviceId);
這兩個(gè)API各自對(duì)應(yīng)了一個(gè)'連接'和'斷開連接'事件,通知應(yīng)用層示連接已經(jīng)建立或者已經(jīng)斷開:
| 事件Tag | 觸發(fā)函數(shù) | 回調(diào)函數(shù) |
|---|---|---|
| gConnEvtConnected_c * | Gap_Connect | 連接回調(diào)函數(shù)BleApp_ConnectionCallback
|
| gConnEvtDisconnected_c * | Gap_Disconnect | 連接回調(diào)函數(shù)BleApp_ConnectionCallback
|
注意:這兩個(gè)事件并非只有當(dāng)調(diào)用函數(shù)才會(huì)產(chǎn)生,如果對(duì)端是主設(shè)備與我們建立連接,作為從設(shè)備我們也會(huì)得gConnEvtConnected_c 。同樣,如果對(duì)端主動(dòng)與我們斷開,或者鏈路因某些異常原因斷開,協(xié)議棧也會(huì)拋出gConnEvtDisconnected_c事件。這種會(huì)被協(xié)議棧主動(dòng)觸發(fā)的事件在SDK中還有不少,我們可以稱為異步事件。
在NXP BLE SDK開發(fā)時(shí),Gap_Connect的API被包在App_Connect內(nèi)的,用戶一般是調(diào)用App_Connect,她傳入的連接回調(diào)函數(shù)會(huì)在用戶線程上下文中被調(diào)用。在實(shí)時(shí)操作系統(tǒng)環(huán)境下,事件處理中即使有長(zhǎng)時(shí)間占用CPU的行為(比如打印出事件參數(shù)),也不會(huì)對(duì)協(xié)議棧造成影響。這個(gè)我們?cè)谇懊婕軜?gòu)介紹中有講解這個(gè)機(jī)制,有困惑的同學(xué)可以回去上一篇文章。
連接請(qǐng)求參數(shù)
調(diào)用Gap_Connect時(shí)需要主設(shè)備填充一組連接參數(shù),這組參數(shù)將由協(xié)議棧送給Controller,由Controller最終決定如何與對(duì)端設(shè)備建立連接(Controller將考慮其他連接的時(shí)序要求)
typedef struct gapConnectionRequestParameters_tag {
uint16_t scanInterval;
uint16_t scanWindow;
bleInitiatorFilterPolicy_t filterPolicy;
bleAddressType_t ownAddressType;
bleAddressType_t peerAddressType;
bleDeviceAddress_t peerAddress;
uint16_t connIntervalMin;
uint16_t connIntervalMax;
uint16_t connLatency;
uint16_t supervisionTimeout;
uint16_t connEventLengthMin;
uint16_t connEventLengthMax;
bool_t usePeerIdentityAddress;
} gapConnectionRequestParameters_t;
| 成員 | 取值范圍 | 說明 |
|---|---|---|
| scanInterval | 0x04 - 0x4000 | 2.5ms - 10.24s(步進(jìn)0.625ms),兩次掃描的間隔 |
| scanWindow | 0x04 - 0x4000 | 2.5ms - 10.24s(步進(jìn)0.625ms),每次掃描的持續(xù)事件,當(dāng)window = interval時(shí)連續(xù)掃描 |
| filterPolicy | gUseDeviceAddress_c, gUseWhiteList_c | 是否啟動(dòng)Initiator白名單 |
| ownAddressType | gBleAddrTypePublic_c 或 gBleAddrTypeRandom_c | 該參數(shù)決定在Connect request中自己的地址類型 |
| peerAddressType | gBleAddrTypePublic_c 或 gBleAddrTypeRandom_c | 該參數(shù)決定在Connect request時(shí)被連接設(shè)備的地址類型 |
| peerAddress | 48位藍(lán)牙地址 | 該參數(shù)決定在Connect request時(shí)被連接設(shè)備的地址 |
| connIntervalMin | 0x06 - 0x0C80 | 7.5ms - 4s(步進(jìn)1.25ms)建議最小的連接間隔 |
| connIntervalMax | 0x06 - 0x0C80 | 7.5ms - 4s(步進(jìn)1.25ms)建議最大的連接間隔 |
| connLatency | 0 - 499 | 從機(jī)可忽略的連接事件數(shù)量 |
| supervisionTimeout | 0x0A - 0x0C80 | 100ms - 32s(步進(jìn)10ms)最大超時(shí)斷鏈間隔 |
| connEventLengthMin | 0 - 0xFFFF | 連接事件最小長(zhǎng)度 |
| connEventLengthMax | 0 - 0xFFFF | 連接事件最大長(zhǎng)度 |
| usePeerIdentityAddress | TRUE or FALSE | 當(dāng)Controller Privacy開啟時(shí)是否使用 |
NXP BLE SDK作為主設(shè)備類型的例程在app_config.c都會(huì)給出一個(gè)連接參數(shù)配置gConnReqParams,用戶直接根據(jù)自己需求修改即可,peerAddress和peerAddressType兩個(gè)參數(shù)則來自于掃描得到的數(shù)據(jù)。至于主設(shè)備連接哪一個(gè)掃描到的從設(shè)備,這完全是用戶的定義的行為了,通過廣播數(shù)據(jù)的字段來判斷是一種常用的做法。
同時(shí)廣播與掃描
有的產(chǎn)品擔(dān)任雙角色,既可以作為主設(shè)備掃描并連接其他從設(shè)備,同時(shí)也可以作為從設(shè)備,廣播被其他主設(shè)備發(fā)現(xiàn)并連接。甚至需要同一時(shí)刻做這兩件事(同時(shí)廣播和掃描)。
雖然通常藍(lán)牙芯片只有一個(gè)RF端口,但廣播和掃描這兩項(xiàng)功能一個(gè)是只發(fā)送數(shù)據(jù)(Tx),一個(gè)僅接收數(shù)據(jù)(Rx),因此射頻上不存在問題。從BLE 4.1協(xié)議規(guī)范后,定義了BLE芯片在鏈路層支持多個(gè)狀態(tài)機(jī),這樣就完整的支持了同時(shí)進(jìn)行廣播和掃描,以及建立多個(gè)連接,和混合的拓?fù)浣Y(jié)構(gòu)。
在NXP BLE SDK中實(shí)現(xiàn)這個(gè)功能是非常簡(jiǎn)單的,首先確認(rèn)使用的協(xié)議棧庫(kù)文件是支持主從設(shè)備的,而非僅支持從設(shè)備(peripheral)的庫(kù),然后按照文章前面介紹的廣播與掃描API調(diào)用和Event處理流程操作即可,二者并不沖突。
連接之后
建立完連接只是萬里長(zhǎng)征第一步,連接建立后雙方通常還不能直接進(jìn)行用戶數(shù)據(jù)交互,后面還要進(jìn)行一系列BLE協(xié)議規(guī)范所要求的過程,如配對(duì)加密,客戶端對(duì)服務(wù)器端的服務(wù)進(jìn)行查詢,若知后事如何,請(qǐng)聽下回分解。