蓝牙主机(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: 只是简单的发起连接请求。