藍牙主機(Central),顧名思義,就是一個藍牙主設備,與從機(Peripheral)建立連接進行通信,可以接收從機通知,也可以給從機發送信息,通常Central和Peripheral結合使用。
一、官方例程Central的工作流程
從官方例程中,我們可以看到,Central的工作流程大致如下:
一、初始化完成開啟掃描,
二、獲取掃描信息
三、將掃描到的mac地址與目標連接mac地址作比較,掃描到目標mac就發起連接否則繼續開啟掃描
四、枚舉服務進行通信測試
上圖標注1:開始掃描
標注2將掃描獲取的從機MAC地址加入掃描列表
標注3與目標連接MAC地址比較
標注4沒有找到目標,重新開始掃描
標注5找到目標mac,發起連接
從上述描述中我們知道,要想與Peripheral建立連接,必須知道Peripheral的MAC地址,但實際應用中,我們很難知道Peripheral的MAC的地址,就算知道了,也很難輸入Central中,畢竟大多數情況下,每個Peripheral的MAC地址是不同的,尤其是我們針對的是現有的產品時。
那么我們如何應對這種問題呢?
二、BLE廣播數據中的AD Type詳解
?一般來說,同一種產品,廣播數據是相同的,甚至同一個廠家的同一種類型的產品,廣播數據也會有一些共同的特征,我們可以通過研究產品的廣播數據來解決上面提到的問題。所以我們先來了解一下廣播數據中的AD Type。
AD Type是廣播數據單元(AD Structure)的核心字段,用于定義后續數據(AD Data)的類型和格式。以下是常見AD Type的分類及說明:
一)、基礎設備信息類
-
?Flags(類型=0x01)?
- ?功能?:標識設備的發現模式和兼容性,如是否支持BLE/BR/EDR雙模。
- ?數據格式?:1字節,各bit位含義:
- Bit 0:LE有限發現模式(僅臨時可連接)
- Bit 1:LE普通發現模式(持續可連接)
- Bit 2:不支持BR/EDR(純BLE設備)
- Bit 3-4:控制器/主機支持雙模
- ?示例?:
0x06
表示支持普通發現模式且不支持BR/EDR。
-
?完整設備名稱(類型=0x09)?
- ?功能?:聲明設備完整名稱(如
Nordic_HRM
)。 - ?數據格式?:UTF-8字符串,長度由Len字段定義。
- ?功能?:聲明設備完整名稱(如
-
?縮短設備名稱(類型=0x08)?
- ?功能?:設備名稱的縮寫形式,用于節省廣播數據空間。
二)、服務聲明類
-
?完整16位服務UUID列表(類型=0x03)?
- ?功能?:廣播設備支持的所有16位標準服務UUID(如心率服務
0x180D
)。 - ?數據格式?:多個2字節UUID連續排列。
- ?功能?:廣播設備支持的所有16位標準服務UUID(如心率服務
-
?非完整服務UUID列表(類型=0x02)?
- ?功能?:僅聲明部分服務,需通過掃描響應或連接后獲取完整列表。
-
?32位/128位服務UUID(類型=0x04-0x07)?
- ?功能?:聲明長格式服務UUID(如自定義服務)。
三)、設備能力與參數類
-
?發射功率等級(類型=0x0A)?
- ?功能?:廣播設備的發射功率值(單位dBm),用于距離估算。
- ?數據格式?:1字節有符號整數(如
0xF6
表示-10 dBm)。
-
?設備類別(類型=0x0D)?
- ?功能?:標識設備類型(如手機、傳感器)。
- ?數據格式?:3字節,按藍牙標準分類編碼。
四)、廠商自定義數據類
- ?廠商特定數據(類型=0xFF)?
- ?功能?:攜帶廠商自定義數據(如iBeacon、Eddystone協議)。
- ?數據格式?:前2字節為廠商ID(如蘋果為
0x004C
),后續為自定義內容。
五)、其他類型
- ?可連接間隔(類型=0x12)?:聲明設備建議的連接參數。
- ?服務請求(類型=0x14)?:主動請求特定服務(如定位服務)。
- 還有更多AD Type,這里就不細說,畢竟與我們的主題關系不大,有興趣的朋友可以很容易從網上搜索到相關的解釋。
六)、關鍵限制與注意事項
- ?數據長度限制?:單個廣播包載荷(Payload)總長度不超過31字節。
- ?組合使用?:一個廣播包可包含多個AD Structure,需合理分配類型優先級(如優先Flags和服務聲明)。
- ?動態更新?:部分AD Type(如設備名稱)支持動態修改以適應場景需求。
- 以上AD Type并非全部必需,可以根據產品的特性及實際需要來提供。
?三、實例講解
根據”BLE廣播數據中的AD Type詳解“一節所述,以及上面三張廣播包圖,我們很容易知道,廣播包包含的內容比較隨意,沒有強制要求,但AD Type 0x09,也就是設備名稱通常會包含,所以藍牙主機(Central)在掃描時,可以根據AD Type 0x09來判斷是否是目標連接。當然我們根據廣播包的信息,很容易知道,可以用AD Type 0x07(自定議服務UUID)或用AD TypxFF(廠家自定義的數據類型)來判斷是否是目標連接。
接下來,我們以佳能相機藍牙遙控器為例來講解如何根據AD Type 0x09及AD Type 0x07來判斷是否是目標連接,如果是,則發起連接請求。(因為該項目是商用項目,我們沒辦法所完整的源碼上傳,所以只會貼一部分與本主題有關的代碼)。
#define PAIR_MODE_TYPE 0x07const uint8_t Serv_uuid[ATT_UUID_SIZE] = {0x21,0xa8,0xff,0x2f,0x49,0xd8,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x00,0x05,0x00};const uint8_t cannon_EOSM50[DEVICE_NAME_MAX_LEN] = {0x45,0x4F,0x53,'M',0x35,0x30,0,0,0,0,0,0,0,0,0,0};
const uint8_t cannon_EOS800D[DEVICE_NAME_MAX_LEN] = {0x45,0x4F,0x53,0x38,0x30,0x30,0x44,0,0,0,0,0,0,0,0,0};
const uint8_t cannon_SX70[DEVICE_NAME_MAX_LEN] = {0x53,0x78,0x37,0x30,0,0,0,0,0,0,0,0,0,0,0,0};
const uint8_t * Device_List[DEVICE_COUNT] = {cannon_EOSM50,cannon_EOS800D,cannon_SX70};case GAP_DEVICE_INFO_EVENT:{// Add device to list
// centralAddDeviceInfo(pEvent->deviceInfo.addr, pEvent->deviceInfo.addrType);if (IsPaired)break;uint8_t cmystata;uint8_t UserNamelen,len;uint8_t EvtData[31];#ifdef DEBUGsprintf(EvtData,"%s",pEvent->deviceInfo.pEvtData); //將掃描的數據格式化成字符串進行子字符串匹配
#endif//以下為字符串匹配for (uint8_t i = 0; i < pEvent->deviceInfo.dataLen;){if(pEvent->deviceInfo.pEvtData[i]>=2){len = pEvent->deviceInfo.pEvtData[i];if(pEvent->deviceInfo.pEvtData[i+1] == PAIR_MODE_TYPE){
#if 0for (uint8_t idx = 0; idx < DEVICE_COUNT; idx ++){cmystata = tmos_memcmp(&pEvent->deviceInfo.pEvtData[i+2],Device_List[idx],len-1);if (cmystata == TRUE){
// Device_Ctrl.Device_Idx = idx;break;}}
#elsecmystata = tmos_memcmp(&pEvent->deviceInfo.pEvtData[i+2],Serv_uuid,len-1);
#endifif(cmystata == TRUE) // 找到了{
// StrMatchingFlag = TRUE;GAPRole_CentralCancelDiscovery(); //取消設備掃描發現centralAddDeviceInfo(pEvent->deviceInfo.addr, pEvent->deviceInfo.addrType);}PRINT("\r\n");return;}else{i += len+1;}}else{i++;}}}break;case GAP_DEVICE_DISCOVERY_EVENT:{PRINT("Device found...\n");GAPRole_CentralEstablishLink(DEFAULT_LINK_HIGH_DUTY_CYCLE,DEFAULT_LINK_WHITE_LIST,centralDevList[0].addrType,centralDevList[0].addr);// BLEConnected = BLE_CONNECTING;// Start establish link timeout eventtmos_start_task(centralTaskId, ESTABLISH_LINK_TIMEOUT_EVT, ESTABLISH_LINK_TIMEOUT);PRINT("Connecting...\n");}break;
以上代碼,簡單闡述一下,本來我們開始是打算根據AD Type 0x09(也就是設備名稱)來判斷是否是目標連接,但后來客戶不斷的增加相機類型,甚至還要求支持沒有提供的設備,所以轉為根據AD Type 0x07(自定議服務UUID)來判斷。
#define PAIR_MODE_TYPE 0x07就是指AD Type 0x07,如果要根據AD Type 0x09,這個地方需要修改,程序代碼可能也需要略作修改,畢竟我們后面的代碼全部是根據AD Type 0x07(自定議服務UUID)來開發的。
代碼本身比較簡單,與官方例程不同的是,?case GAP_DEVICE_INFO_EVENT:官方例程只是把掃描到的信息添加到相應的列表中,?case GAP_DEVICE_DISCOVERY_EVENT: 在這里才判斷是否是目標連接。而我們的實例則是在case GAP_DEVICE_INFO_EVENT:就判斷是否是目標連接,而在case GAP_DEVICE_DISCOVERY_EVENT: 只是簡單的發起連接請求。