蓝牙是一种不断创新发展的无线通信技术标准,采用了 2.4G ISM频段,在音频传输、数据传输、位置服务、设备组网这些场景有着广泛的应用。

1. 蓝牙概述

蓝牙技术分经典蓝牙(Classic BT)和低功耗蓝牙(BLE)。

低功耗蓝牙于2010年蓝牙4.0标准中引入,该部分由Nokia的Wibree标准发展而来,并专门在成本、带宽、功耗等方面进行了优化,适用于成本要求低、功耗要求低、实时性要求高的应用,如低功耗音频,遥控类的鼠标、键盘等,传感设备类的血压计、温度传感器、心率传感器等等。

经典蓝牙从蓝牙1.0引入基本码率(BR),蓝牙2.0引入增强码率(EDR),BR/EDR(Basic Rate/Enhanced Data Rate)传输速率在700Kbps~3Mbps。蓝牙3.0引入高速蓝牙,使用交替射频技术AMP(Alternate MAC/PHYs),让数据传输速率提高到24Mbps。高速蓝牙使用Wifi射频技术,对比Wifi技术并无优势,还额外增加成本以及功耗,因此蓝牙5.3标准已经删除高速蓝牙部分,专注于低功耗、低成本无线技术。经典蓝牙可用于数据量比较大的传输,如语音,音乐,文件传输等。

由于经典蓝牙技术与低功耗蓝牙技术差异比较大,两者之间不能互通,因此,蓝牙4.0标准以前均为经典蓝牙技术,之后的标准分经典蓝牙和低功耗蓝牙两个不同的技术。低功耗蓝牙架构简单,功耗和成本也比经典蓝牙低,因此现有蓝牙标准对经典蓝牙部分更新比较少,主要对低功耗蓝牙部分进行了显著增强,如mesh组网、定位、低功耗音频,传输速率达到2Mbps。

2. 蓝牙系统核心架构

03 蓝牙开发 - 图1

蓝牙可分为控制器(Controller)和主机(Host)两部分,它们之间彼此独立,通过HCI接口以及协议通信。控制器包括了射频PHY、链路控制(Link Controller)、基带管理(Baseband Resource Manager)、链路管理(Link Manager)、设备管理(Device Manager)等物理部分,实现蓝牙无线发射接收、包编码解码、收发时序管理、链路连接管理等等功能。BR/EDR主机至少包括了L2CAP、SDP、GAP规范,而LE主机至少包括了L2CAP、SMP、ATT/GATT、GAP规范,从而可以向应用层提供基础的接口,方便应用层对蓝牙系统的访问。

3. 蓝牙数据传输架构

03 蓝牙开发 - 图2

蓝牙数据传输架构采用分层结构,核心分为三层:L2CAP层、逻辑层、物理层

L2CAP层中L2CAP (Logical Link Control and Adaptation Protocol)为逻辑链路控制和适配协议,采用多路复用、分段和重组技术向应用层提供数据服务。

逻辑层提供了两个或多个设备间和物理无关的逻辑传输通道,包括两个子层:逻辑链接(Logical Links)、逻辑传输(Logical Transports)。

物理层负责提供数据传输的物理通道,包括三个子层:物理链接(Physical Links)、物理信道(Physical Channels)、物理传输(Physical Transports)。
03 蓝牙开发 - 图3

应用层数据和协议可以分为异步数据、恒定速率等时同步数据、广播数据这几类,针对这些传输需求,蓝牙核心提供了一系列标准的业务承载。

ACL (Asynchronous Connection-oriented [logical transport])提供双向、点对点、基于应答重传的可靠逻辑传输,支持ACL-C/ACL-U(BR/EDR蓝牙)或LE-C/LE-U(LE蓝牙),后缀-C表示控制逻辑链路,-U表示用户逻辑链路。LM (Link Manager)信令使用ACL-C逻辑链路,而LL (Link Layer)信令使用LE-C逻辑链路。应用层异步数据如ATT/GATT、A2DP、SPP等应用协议的帧数据、协议信令使用用户逻辑链路ACL-U/LE-U,数据流如下:

BR/EDR蓝牙:—> L2CAP —> ACL-U —> BR/EDR ACL。

LE蓝牙:—> L2CAP —> LE-U —> LE ACL。

BR/EDR蓝牙中SCO (Synchronous Connection-Oriented [logical transport]) 分配保留时隙提供双向对称、点对点传输,不会进行重传,支持SCO-S逻辑链路,后缀-S表示流逻辑链路。eSCO (Extended Synchronous logical link) 分配保留时隙提供双向对称或不对称、点对点传输,支持eSCO-S逻辑链路和有限重传。LE蓝牙中CIS (Connected Isochronous Stream)提供点对点等时同步、应答重传可靠流传输,支持LE-S、LE-F逻辑链路,后缀-F表示帧逻辑链路。BIS (Broadcast Isochronous Stream)提供单向广播、等时同步传输,支持LE-S、LE-F、LEB-C逻辑链路。应用层恒定速率的等时同步数据(如实时语音)的数据流如下:

BR/EDR蓝牙:—> SCO-S/eSCO-S —> SCO/eSCO。

LE蓝牙:—> LE-S/LE-F —> CIS/BIS。

BR/EDR蓝牙中APB (Active Peripheral Broadcast [logicaltransport])针对激活设备提供单向广播传输,支持APB-C、APB-U逻辑链路。CPB (Connectionless Peripheral Broadcast)针对多个设备提供配置广播传输,支持PBD逻辑链路。LE蓝牙中ADVB (LE Advertising Broadcast)针对扫描设备提供单向广播传输,支持ADVB-C、ADVB-U逻辑链路。PADVB (LE Periodic Advertising Broadcast)针对扫描设备提供周期性广播传输,支持ADVB-C、ADVB-U逻辑链路。

应用层广播数据的数据流如下:

BR/EDR蓝牙:—> L2CAP —> APB-U —> APB或 —> PBD —> CPB。

LE蓝牙:—> L2CAP —> ADVB-U —> ADVB/PADVB。

蓝牙数据传输总体层次结构以及每层的实体对应如下图所示:

03 蓝牙开发 - 图4

4. ESP32蓝牙

ESP32蓝牙版本为4.2,同时支持经典蓝牙和低功耗蓝牙。ESP-IDF中可对蓝牙控制器和蓝牙主机协议栈进行配置,控制器支持三种工作方式:仅BLE蓝牙、仅BR/EDR蓝牙和蓝牙双模。主机协议栈可使用ESP32已移植支持的Bluedroid或NimBLE,其中Bluedroid支持BR/EDR和BLE双模蓝牙,而NimBLE仅仅支持BLE蓝牙,此外可仅使用ESP32控制器部分,外部主机(如PC)集成主机协议栈,通过UART接口与控制器进行通信。

03 蓝牙开发 - 图5

5. 经典蓝牙(BR/EDR)

BR/EDR工作频段为2.402GHz~2.480GHz,频道间隔为1MHz,共使用79个频道。采用TDD时分双工、无线跳频工作方式,支持点对点和一点对多点通讯,最多支持7个从设备,从设备间不能互相通讯。

5.1. 蓝牙地址

每个蓝牙设备有一个48位唯一的蓝牙设备地址(BD_ADDR),其中低24位地址称为LAP (Lower Address Part),中间8位地址称为UAP (Upper Address Part),高16位地址称为NAP (Non-significant Address Part),UAP和NAP由IEEE分配给不同的厂商,LAP由厂商内部自由分配。
03 蓝牙开发 - 图6

5.2. 蓝牙时隙

蓝牙信道划分为时隙,每个时隙时间长度为625us,最大跳频速率为每秒1600跳。连接后,根据数据包的长度,可以分为单时隙包(占用1个时隙)、3时隙包(占用3个时隙)、5时隙包(占用5个时隙)这三种包类型。主设备(Central)总是从偶时隙开始发送数据,从设备(Peripheral)总是从奇时隙发送数据
03 蓝牙开发 - 图7

5.3. 蓝牙包格式

蓝牙数据传输架构采用分层结构,应用层的数据将被分组,在逻辑层和物理层进行封包。
03 蓝牙开发 - 图8

BR/EDR包可以分成三部分:接入码(Access Code)、包头(Packet Header)和载荷(Payload)。这三部分并非紧耦合,包可以只有接入码部分(如ID包),也可以只有接入码和包头部分(如NULL包、POLL包),能确定的是每个包都至少包含接入码。

5.3.1. 接入码(Access Code)

接入码为68位或72位,主要用于时序同步、偏移补偿和设备识别, 包含前导码、同步字、尾部这三部分。
03 蓝牙开发 - 图9

同步字64位由蓝牙地址低24位(LAP)通过汉明码信道编码而来,采用纠错码提高了传输效率,降低了误码率。接入码作为物理层部分,即使后续包头或载荷部分出错,也不会造成接入码的丢弃。

接入码可以分三种类型:设备接入码(DAC)、通道接入码(CAC)、查询接入码(IAC)。

当设备需要查询(inquiry)周边设备时,需要使用查询接入码,查询接入码使用保留的LAP地址,如GIAC(General Inquiry Access Code)固定值为0x9E8B33,具有广播寻址的性质,处于查询扫描(inquiry scan)状态的周边设备将响应该接入码。

当获取了对端设备蓝牙地址时,可产生对应的设备接入码,通过设备接入码寻呼(page)连接设备,处于寻呼扫描(page scan)状态的对应设备将响应该接入码。

对于已连接的主从设备,将使用主设备LAP地址产生的通道接入码,连接后的所有通信将基于该接入码。

5.3.2. 包头(Packet Header)

包头为54位,包含了链路控制(LC)信息,由六个字段组成:

LT_ADDR,3位逻辑传输地址,用于指定广播或者特定的从设备。
TYPE,4位类型码,用于指定包类型。
FLOW,1位流控,用于暂停某些数据分组的传输。
ARQN,1位确认指示,用于自动请求重发。
SEQN,1位序列号,用于区分是新的分组包还是重传包。
HEC,8位头错误校验,用于检测包头完整性。

03 蓝牙开发 - 图10

包头有效载荷为18位,通过1/3前向纠错信道编码生成54位包头,提高了包头传输效率,并降低了误码率。

5.3.3. 载荷(Payload)

载荷的解析依赖于包头中4位TYPE包类型指定,支持16种包类型。

03 蓝牙开发 - 图11

包类型跟使用的逻辑传输方式有关,如SCO逻辑传输时,可使用HV1、HV2、HV3、DV分组包,载荷为固定长度,无格式的数据。HV表示高质量语音,后面数字表示有效载荷采用的纠错编码方式,纠错编码方式纠错能力越强,数据传输越有效率,但同时冗余码越多,实际数据传输速率越低。

DM1、DM3、DM5和DH1、DH3、DH5分组包可用于ACL、CPB逻辑传输中,载荷为不定长数据。DM表示中等速率数据分组,使用2/3前向纠错信道编码,DH表示高速率数据分组,不采用纠错编码方式,后面数字表示分组包需要占用的时隙数。

5.4. 蓝牙连接

对于BR/EDR蓝牙,设备间数据传输必须先建立连接。蓝牙系统分为控制器(Controller)和主机(Host)两部分,因此蓝牙连接也可分为控制器连接以及主机连接。

5.4.1. 控制器连接

控制器包含基带部分以及链路管理器LM(Link Manager),由于蓝牙为高速跳频系统,通信必须保证双方跳频序列一致,收发时序严格同步。主从设备不可避免因频偏、温漂等原因造成时钟不同步,从设备可通过接入码时序实时校准时钟,跟主设备时钟保持同步。控制器连接即是指设备间无线时序处于同步状态,基带建立连接,设备间的链路管理器LM可以通过LMP协议互相通信。

5.4.1.1. 查询

如果设备间未配对过,需要通过查询获取对端设备的蓝牙地址。主设备需进入查询(inquiry)状态,从设备需进入查询扫描(inquiry scan)状态。

主设备在查询状态发送ID包,ID包只包含查询接入码(如GIAC、LIAC)。由于ID包很短,每个发送时隙可以发送2个ID包,每个接收时隙可以监听2个FHS包的接入码,查询状态跳频速率为每秒3200跳。
03 蓝牙开发 - 图12

从设备在查询扫描状态监听查询ID包,每个跳频点监听一定的时间,若未监听到查询ID包,则跳到下一个频点进行监听,如果接收到查询ID包,则进入查询响应(inquiry response)状态,回复FHS包给主设备。FHS包中包含蓝牙地址、本机时钟以及其它设备信息,共144位信息,加上16位CRC,通过2/3前向纠错编码,总包长为240位。

03 蓝牙开发 - 图13

FHS中EIR位为扩展查询响应(extended inquiry response),如果从设备主机注册了EIR,则EIR位为1,表示从设备后续将回复一个扩展查询响应包给主设备,提供更多的额外信息,如设备名、支持的UUID等。
03 蓝牙开发 - 图14

03 蓝牙开发 - 图15

5.4.1.2. 寻呼

主设备获取从设备蓝牙地址后,可以根据该地址产生设备接入码,在寻呼(page)状态发送ID包,ID包只包含设备接入码,寻呼时序可以参考查询时序。

从设备在寻呼扫描(page scan)状态监听寻呼ID包,每个跳频点监听一定的时间,若未监听到查询ID包,则跳到下一个频点进行监听,如果接收到寻呼ID包,则进入寻呼响应(page response)状态,回复第一个ID包给主设备。主设备收到第一个回复ID包后,发送FHS包给从设备,FHS包中包含主设备蓝牙地址、本机时钟等信息。从设备接收到FHS包后,根据主设备的蓝牙地址,时钟信息,产生通道接入码以及对应的跳频序列,从设备回复第二个ID包给主设备后,进入连接(connection)状态。主设备收到第二个回复ID包后,产生通道接入码以及对应的跳频序列,进入连接状态,并按通道接入码发送第一个POLL包确定是否有效连接。从设备可响应NULL/DM1/DH1包给主设备,从而完成连接过程。

03 蓝牙开发 - 图16

03 蓝牙开发 - 图17

寻呼建立连接以后,控制器基带即建立连接,同时ACL-C逻辑链路也建立连接,LM(Link Manager)可以在ACL-C逻辑链路上通过LMP协议互相通信。

5.4.2. 主机连接

主机连接需建立在控制器已连接的基础上,指的是建立ACL-U逻辑链路,从而应用层信令、协议数据可以通过ACL-U逻辑链路传输,也可进一步建立SCO/eSCO同步传输。

如下图所示,主机连接流程主要有以下几个步骤:
03 蓝牙开发 - 图18

步骤1:主机发送HCI_Create_Connection命令给控制器,控制器进行寻呼连接流程,可参考上一小节。
03 蓝牙开发 - 图19

步骤2:可选的,链路管理器LM可以交换彼此支持的特性,如是否支持自适应跳频、是否支持发送功率控制等等。
03 蓝牙开发 - 图20

步骤3:链路管理器LM向对端LM请求主机连接LMP_HOST_CONNECTION_REQ。
03 蓝牙开发 - 图21

步骤4a:如果对端主机拒绝连接,则连接终止。

03 蓝牙开发 - 图22

步骤4b:对端设备允许连接。
03 蓝牙开发 - 图23

步骤4c:对端设备允许连接,但需要成为主设备,则通过LMP_HOST_CONNECTION_REQ请求切换成主设备,然后再反馈接受连接。
03 蓝牙开发 - 图24

步骤5:如果交换特性后,主从设备均支持自适应跳频,则通过LMP_SET_AFH和LMP_CHANNEL_CLASSIFICATION_REQ使能并开始自适应跳频。

03 蓝牙开发 - 图25

步骤6:如果需要认证,LM将向主机请求链路密钥。
03 蓝牙开发 - 图26

步骤7a:如果主机没有链路密钥,则开始配对流程。LM将向主机请求一个PIN码,并基于PIN码进行双方的认证。
03 蓝牙开发 - 图27

步骤7b:如果存在链路密钥,则无需配对,直接用链路密钥进行认证。

03 蓝牙开发 - 图28

步骤8:认证或配对完成后,如果连接需要加密,则开始加密流程。
03 蓝牙开发 - 图29

步骤9:如果连接建立,LM发送LMP_SETUP_COMPLETE事件给主机,由上层对这一事件进行处理。
03 蓝牙开发 - 图30

步骤10:主机可以发送HCI_Disconnect命令给控制器,从而断开主机间连接。
03 蓝牙开发 - 图31

5.5. ESP32经典蓝牙例程

以ESP-IDF 中\examples\bluetooth\bluedroid\classic_bt\bt_discovery\main\ bt_discovery.c为例,该例程用于发现周边设备及其服务。ESP32使用bluedroid主机协议栈,有如下几个使用步骤:

5.5.1. 控制器初始化

esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();

ret = esp_bt_controller_init(&bt_cfg);

5.5.2. 控制器使能

使能经典蓝牙模式。

ret = esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT);

5.5.3. 初始化并使能bluedroid主机协议栈

ret = esp_bluedroid_init();

ret = esp_bluedroid_enable();

5.5.4. 初始化GAP

GAP用于发现及连接设备,设置设备可连接,可发现,事件处理回调函数为bt_app_gap_cb并进入查询状态,查询时间为1.28s x 10,不限定查询响应数。

char *dev_name = "ESP_GAP_INQRUIY";

esp_bt_dev_set_device_name(dev_name);

/* register GAP callback function */

esp_bt_gap_register_callback(bt_app_gap_cb);

/* set discoverable and connectable mode, wait to be connected */

esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);

/* inititialize device information and status */

bt_app_gap_init();

esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0);

5.5.5. GAP回调事件

当查询开始或查询结束,触发ESP_BT_GAP_DISC_STATE_CHANGED_EVT回调事件。

case ESP_BT_GAP_DISC_STATE_CHANGED_EVT:

if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STOPPED) {
ESP_LOGI(GAP_TAG, "Device discovery stopped.");

if ( (p_dev->state == APP_GAP_STATE_DEVICE_DISCOVER_COMPLETE ||

    p_dev->state == APP_GAP_STATE_DEVICE_DISCOVERING)

    && p_dev->dev_found) {
    p_dev->state = APP_GAP_STATE_SERVICE_DISCOVERING;

    ESP_LOGI(GAP_TAG, "Discover services ...");

    esp_bt_gap_get_remote_services(p_dev->bda);

 }

} else if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STARTED) {
 ESP_LOGI(GAP_TAG, "Discovery started.");

}

break;

当查询发现设备时,触发ESP_BT_GAP_DISC_RES_EVT回调事件。

case ESP_BT_GAP_DISC_RES_EVT:

update_device_info(param);

break;

通过esp_bt_gap_get_remote_services(p_dev->bda)开始SDP协议获取远端设备具有的服务,获取完成后触发ESP_BT_GAP_RMT_SRVCS_EVT回调事件。

case ESP_BT_GAP_RMT_SRVCS_EVT:

    if (memcmp(param->rmt_srvcs.bda, p_dev->bda, ESP_BD_ADDR_LEN) == 0 &&

        p_dev->state == APP_GAP_STATE_SERVICE_DISCOVERING) {
        p_dev->state = APP_GAP_STATE_SERVICE_DISCOVER_COMPLETE;

        if (param->rmt_srvcs.stat == ESP_BT_STATUS_SUCCESS) {
            ESP_LOGI(GAP_TAG, "Services for device %s found",  bda2str(p_dev->bda, bda_str, 18));

            for (int i = 0; i < param->rmt_srvcs.num_uuids; i++) {
                esp_bt_uuid_t *u = param->rmt_srvcs.uuid_list + i;

                ESP_LOGI(GAP_TAG, "--%s", uuid2str(u, uuid_str, 37));

                // ESP_LOGI(GAP_TAG, "--%d", u->len);

            }

        } else {
            ESP_LOGI(GAP_TAG, "Services for device %s not found",  bda2str(p_dev->bda, bda_str, 18));

        }

}

break;

编译、下载代码并运行,打开手机蓝牙,可以发现手机支持的服务。例如,服务0x1112为Headset - Audio Gateway,表示手机支持蓝牙耳机。
03 蓝牙开发 - 图32

6. 低功耗蓝牙(BLE)

BLE工作频段为2.402GHz~2.480GHz,频道间隔为2MHz,共使用40个频道,采用TDD时分双工、无线跳频工作方式。其中频道37、38、39这三个频道为主广播频道,只用于广播传输,剩余的37个频道为通用频道,用于点对点传输数据或作为次广播频道传输广播数据。

6.1. PHY

BLE定义了LE 1M、LE 2M和LE Coded这三种物理层接口(PHY),其中LE 2M和LE Coded由蓝牙5.0版本引入。

LE 1M需强制支持,未采用编码,传输速率为1Mbps。

LE 2M为可选支持,未采用编码,传输速率为2Mbps。

LE Coded为可选支持,数据采用前向纠错信道编码,降低误码率,实现更远的传输距离,但额外的冗余码同时降低了数据传输速率。有两种编码方案,传输速率为250Kbps或者125Kbps。

6.2. 设备地址

BLE蓝牙地址固定为48位,有两种类型:公开地址(public device address)和随机地址(random device address)。其中公开地址由IEEE分配,具有唯一性以及固定性,可用于设备身份识别。随机地址在每次上电时静态产生(static address)或动态定时更新(private address),地址可随意使用,成本更低,难以被跟踪,具有更高的安全性。蓝牙设备根据应用场景,可使用这两类或其中一类作为设备地址。

6.3. 链路层状态机

链路层定义了设备间如何利用无线进行信息传输,涉及到设备发现的流程、广播数据的收发及同步、连接的建立、连接后的数据传输等等阶段,需要有相应的状态来处理对应阶段的事务。
03 蓝牙开发 - 图33

待机状态(Standby State)不发送也不接收包,其它任意状态可切换到待机状态。

广播状态(Advertising State)发送广播报文,可监听其它设备的回复报文,并可进一步响应该回复报文,可由待机状态切换到该状态。

扫描状态(Scanning State)用于监听广播报文,可由待机状态切换到该状态。

发起状态(Initiating State)监听特定设备的广播报文,并通过请求报文发起对端设备的连接,可由待机状态切换到该状态。

连接状态(Connection State)可以由发起状态或广播状态切换进入,发起状态进入的设备为中心设备(Central Role),广播状态进入的设备为外围设备(Peripheral Role)。中心设备控制传输的时序,外围设备只能响应传输,实现可靠的点对点通信。

同步状态(Synchronization State)监听特定设备的周期性广播:周期广播队列(periodic advertising train)或等时广播流(broadcast isochronous streams)。设备通过监听并同步到这些固定间隔的广播序列中,实现同步广播数据的接收。该状态由蓝牙5.2版本引入,可应用于低功耗音频(LE Audio)的场景。

等时广播状态(Isochronous Broadcasting State)发送等时广播组BIG (Broadcast Isochronous Group),BIG由一个或多个BIS (Broadcast Isochronous Stream)组成,每个BIS携带了一路独立的等时数据。该状态由蓝牙5.2版本引入,针对低功耗音频(LE Audio),可以实现同步广播多路实时音频的应用。

6.4. 包格式

根据物理层接口(PHY)的差异,可以分两种包格式:未编码PHY包格式和编码PHY包格式。

6.4.1. 未编码PHY包格式

03 蓝牙开发 - 图34
前导码(Preamble)为8位(LE 1M)或16位(LE 2M),为0、1交替序列,用于频率同步、输入信号自动增益控制。
接入地址(Access Address)为32位,接收设备通过接入地址确定接收包是否属于本设备,过滤掉不匹配的干扰包。广播报文的接入地址固定为0x8E89BED6,允许所有接收设备接收处理,数据报文的接入地址为按规则产生的随机值,只在连接设备间有效。
PDU(Protocol Data Unit)为协议数据单元部分,广播通信时按广播通道PDU格式(advertising physical channel PDU),连接后点对点通信时按数据通道PDU格式(Data Physical Channel PDU),等时同步通信时按等时通道PDU格式(Isochronous Physical Channel PDU)。这三种类型PDU格式将在下一小节展开细述。
CRC为24位,用于包错误校验。
CTE (Constant Tone Extension) 固定频率扩展信号为可选项,由蓝牙5.1版本引入,用于接收信号相位的测量,从而计算出波达角(AoA)或发射角(AoD),实现厘米级高精定位。

6.4.2. 编码PHY包格式

03 蓝牙开发 - 图35

编码PHY包格式由前导码、FEC块1、FEC块2组成。其中前导码未采用编码,FEC块1采用S=8编码方式,FEC块2可采用S=1或S=8的编码方式,具体由FEC块1的CI部分2位指示。TERM1和TERM2为FEC块的终止符,3位长度。

6.4.3. 广播通道PDU(advertising physical channel PDU)

广播通道PDU包括广播状态、扫描状态和发起状态下的报文PDU。包格式包含16位的Header字段以及可变长的Payload字段。

03 蓝牙开发 - 图36

PDU Type为4位,指示广播通道PDU的类型。

03 蓝牙开发 - 图37

RFU为1位保留位。
ChSel为1位跳频算法选择,蓝牙5.0版本引入了#2跳频算法(ChSel=1),在高通量情况下,能更好的避免干扰以及抗多路径衰落。
TxAdd为1位,Payload中如果有发射设备的地址字段,则指示设备地址是公共地址(TxAdd=0)还是随机地址(TxAdd=1)。
RxAdd位1位,Payload中如果有接收设备的地址字段,则指示设备地址是公共地址(RxAdd=0)还是随机地址(RxAdd=1)。
Length为8位,指示Payload字段长度,1~255字节。
广播状态下PDU类型包括ADV_IND、ADV_DIRECT_IND、ADV_NONCONN_IND、ADV_SCAN_IND这4个已有旧广播PDU类型以及ADV_EXT_IND、AUX_ADV_IND、AUX_SYNC_IND、AUX_CHAIN_IND这4个由蓝牙5.0版本引入的扩展广播PDU类型。扩展广播PDU在次广播频道(37个通用频道)传输,避免了主广播频道的拥挤冲突,同时也增加了广播数据的容量。其中旧广播PDU从PDU类型就指明了广播设备是否可连接、可扫描这些属性,但扩展广播PDU类型值均为0b0111,需要在Payload中指明广播设备的这些属性。扩展广播PDU使用的Payload格式称为通用扩展广播载荷格式(Common Extended Advertising Payload Format)。

03 蓝牙开发 - 图38

Extended Header Length为扩展头长度,指明扩展头(Extended Header)字段的字节大小,0~63字节。
AdvMode为广播模式,指明广播设备是否可连接、可扫描。

03 蓝牙开发 - 图39

AdvData字段为扩展广播数据,0~254字节。
Extended Header为扩展头,是一个可变长字段,可用于指明一些广播的关键信息,有以下几个字段。

03 蓝牙开发 - 图40

1) Extended Header Flags为8位,位掩码指明扩展头相应字段的信息是否存在。如Bit 0为1则表明扩展头具有AdvA字段,Bit 2为0则表明扩展头不存在CTEInfo字段。

03 蓝牙开发 - 图41

2) AdvA字段为6字节广播设备的设备地址,由广播通道PDU header的TxAdd字段指定是公开地址还是随机地址。

3) TargetA字段为6字节接收设备的设备地址,由广播通道PDU header的RxAdd字段指定是公开地址还是随机地址。

4) CTEInfo字段指定了固定频率扩展信号(CTE)的相关信息,如CTE信号的持续时间,AoA还是AoD信号。

03 蓝牙开发 - 图42

5) AdvDataInfo字段包含广播数据ID(Advertising Data ID)以及广播集ID(Advertising Set ID)。设备针对不同的应用,可能同时存在多个不同类型的广播,通过SID区分广播的ID。扫描设备通过DID是否更新确定广播数据AdvData是新的数据还是重发的重复数据。

03 蓝牙开发 - 图43

6) AuxPtr字段指定了下一个分段的扩展广播参数,如发送频道、发送的倒计时间、使用的PHY。广播数据较大时,可以将数据分段,通过多个扩展广播分段传输,接收设备通过AuxPtr字段指定的参数、时序接收下一个附加广播。
03 蓝牙开发 - 图44

Channel Index字段指定下一分段广播使用的通用频道号。
CA字段指定广播设备的时钟精度。
Offset Units字段指定下一分段广播发送倒计时间的单位,值0表示30us,值1表示300us。
Aux Offset字段指定下一分段广播发送倒计时间。
Aux PHY字段指定下一分段广播发送使用的PHY,0b000时表示LE 1M,0b001时表示LE 2M,0b010时表示LE Coded。
7) SyncInfo字段指定周期广播的信息,接收设备可以根据同步信息在准确时间内接收广播数据,避免经常性接收扫描,节省了功耗。周期广播可认为是接收设备单向连接到广播设备,因此SyncInfo具有SCA、AA、CRCInit等这些跟连接相关的描述字段。
03 蓝牙开发 - 图45

Offset Base字段指定同步广播AUX_SYNC_IND发送倒计时间。
Offset Units字段指定Offset Base字段时间的单位,值0表示30us,值1表示300us。
Offset Adjust字段为1表示倒计时间加2.4576秒,0表示倒计时间在2.4576秒内。
RFU字段为保留位。
Interval字段指定同步广播的周期间隔时间,单位为1.25ms,周期间隔必须大于7.5ms。
ChM字段为信道图,指定37个通用频道哪些是可用的,哪些是不可用的。
SCA(Sleep Clock Accuracy)字段指定设备休眠时钟精度范围,精度越高,提前监听的不确定窗口越小,从而可以降低功耗。
AA (Access Address)字段指定同步通信的接入地址。
CRCInit字段指定CRC计算的初始值。
PeriodicEventCounter字段为周期广播标识,每个广播周期加1。
8) TxPower字段指定发射的功率。

9) ACAD(Additional Controller Advertising Data)字段为额外的控制器广播数据,用于控制器间的广播通信。

6.4.3.1. ADV_IND

ADV_IND为通用广播指示,是一种可被连接、扫描的非定向广播。

03 蓝牙开发 - 图46

AdvA字段为广播者设备地址,PDU首部的TxAdd字段指定该地址为公共地址还是随机地址。

AdvData字段为广播数据,0~31字节。

6.4.3.2. ADV_DIRECT_IND

ADV_DIRECT_IND为定向广播指示,指定目标设备可快速建立连接。

03 蓝牙开发 - 图47

AdvA字段为广播者设备地址,PDU首部的TxAdd字段指定该地址为公共地址还是随机地址。

TargetA字段为接收者设备地址,PDU首部的RxAdd字段指定该地址为公共地址还是随机地址。

6.4.3.3. ADV_NONCONN_IND

ADV_NONCONN_IND为不可连接广播指示,是一种只发射不接收的广播,无法被扫描或连接。
03 蓝牙开发 - 图48

AdvA字段为广播者设备地址,PDU首部的TxAdd字段指定该地址为公共地址还是随机地址。

AdvData字段为广播数据,0~31字节。

6.4.3.4. ADV_SCAN_IND

ADV_SCAN_IND为可扫描广播指示,是一种不能被连接,但可被扫描的广播。该设备可广播数据,又可响应扫描,允许被其它设备扫描发现。
03 蓝牙开发 - 图49

AdvA字段为广播者设备地址,PDU首部的TxAdd字段指定该地址为公共地址还是随机地址。

AdvData字段为广播数据,0~31字节。

6.4.3.5. ADV_EXT_IND

ADV_EXT_IND为扩展广播指示,使用通用扩展广播载荷格式(Common Extended Advertising Payload Format)。该广播使用主广播频道,不具有广播数据,AdvMode字段指明广播设备是否可连接、可扫描,主要通过AuxPtr字段引导接收设备在次广播频道接收广播数据。

6.4.3.6. AUX_ADV_IND

AUX_ADV_IND为辅助广播指示,使用通用扩展广播载荷格式(Common Extended Advertising Payload Format)。该广播使用37个次广播频道,需由ADV_EXT_IND在主广播频道进行引导,AdvMode字段指明广播设备是否可连接、可扫描,可携带广播数据0~254字节。如果主机广播数据过长,将会被分段,通过AuxPtr字段引导接收设备接收后续的分段广播AUX_CHAIN_IND。如果需要周期广播,则通过SyncInfo字段引导接收设备进入同步状态接收周期广播AUX_SYNC_IND。

6.4.3.7. AUX_SYNC_IND

AUX_SYNC_IND为周期广播指示,使用通用扩展广播载荷格式(Common Extended Advertising Payload Format)。AdvMode字段必须为不可连接、不可扫描,用于在相对精确时间内周期广播数据,这种方式可使同步状态下的接收设备更节能。如果主机广播数据过长,将会被分段,通过AuxPtr字段引导接收设备接收后续的分段广播AUX_CHAIN_IND。

6.4.3.8. AUX_CHAIN_IND

AUX_CHAIN_IND为分段广播指示,使用通用扩展广播载荷格式(Common Extended Advertising Payload Format)。AdvMode字段必须为不可连接、不可扫描,如果主机广播数据过长,将会被分段,通过一个或多个AUX_CHAIN_IND分包广播传输,最大能支持1650字节的主机广播数据。

6.4.3.9. SCAN_REQ

SCAN_REQ为主动扫描请求,当接收设备在主动扫描状态收到可被扫描的广播报文时(ADV_IND或ADV_SCAN_IND),可在主广播频道通过SCAN_REQ向广播设备请求更多的信息,从而发现该广播设备。

03 蓝牙开发 - 图50

ScanA字段为扫描设备地址,PDU首部的TxAdd字段指定该地址为公共地址还是随机地址。

AdvA字段为广播者设备地址,PDU首部的RxAdd字段指定该地址为公共地址还是随机地址。

6.4.3.10. AUX_SCAN_REQ

AUX_SCAN_REQ为辅助主动扫描请求,当接收设备在主动扫描状态收到可被扫描的扩展广播报文时(AUX_ADV_IND),可在次广播频道通过AUX_SCAN_REQ向广播设备请求更多的信息,从而发现该广播设备。Payload格式参考SCAN_REQ。

6.4.3.11. SCAN_RSP

SCAN_RSP为主动扫描响应,当广播设备接收到相应设备的SCAN_REQ主动扫描请求时,将对该设备进行SCAN_RSP扫描响应。

03 蓝牙开发 - 图51

SCAN_RSP载荷

AdvA字段为广播者设备地址,PDU首部的TxAdd字段指定该地址为公共地址还是随机地址。

ScanRspData字段为来自主机的扫描响应数据,0~31字节。

6.4.3.12. AUX_SCAN_RSP

AUX_SCAN_RSP为辅助主动扫描响应,当广播设备接收到相应设备的AUX_SCAN_REQ辅助主动扫描请求时,将对该设备进行AUX_SCAN_RSP扫描响应。使用通用扩展广播载荷格式(Common Extended Advertising Payload Format)。

6.4.3.13. CONNECT_IND

CONNECT_IND为连接请求指示,当接收设备在发起状态收到可被连接的广播报文时(ADV_IND或ADV_DIRECT_IND),可在主广播频道通过CONNECT_IND向广播设备请求连接,并进入连接状态。
03 蓝牙开发 - 图52

1) InitA字段为发起者设备地址,PDU首部的TxAdd字段指定该地址为公共地址还是随机地址。

2) AdvA字段为广播者设备地址,PDU首部的RxAdd字段指定该地址为公共地址还是随机地址。

3) LLData字段为连接参数,包括了第一次连接通信的时间窗口,连接通信间隔等等参数。

03 蓝牙开发 - 图53

AA (Access Address)字段指定连接通信的接入地址。
CRCInit字段指定CRC计算的初始值。
WinSize字段用于指定发送窗口时间大小transmitWindowSize = WinSize * 1.25 ms。
WinOffset字段用于指定发送窗口的倒计时间transmitWindowOffset = WinOffset * 1.25 ms。
Interval字段指定连接通信的周期间隔时间connInterval = Interval * 1.25 ms。
Latency字段指定外围设备延迟数connPeripheralLatency。每个连接事件,中心设备首先发起通信,外围设备如果没有数据交互,可以继续休眠不响应通信,从而降低外围设备功耗,最大可忽略连接事件数为connPeripheralLatency。
Timeout字段指定连接监督超时connSupervisionTimeout = Timeout * 10 ms。连接后,若设备双方超过connSupervisionTimeout时间未成功交互通信,则认为连接断开。
ChM字段为信道图,指定37个通用频道哪些是可用的,哪些是不可用的。
Hop字段为跳频算法跳数值,5~16范围随机产生。
SCA(Sleep Clock Accuracy)字段指定设备休眠时钟精度范围,精度越高,提前监听的不确定窗口越小,从而可以降低功耗。

03 蓝牙开发 - 图54

6.4.3.14. AUX_CONNECT_REQ

AUX_CONNECT_REQ为辅助连接请求,当接收设备在发起状态收到可被连接的扩展广播报文时(ADV_EXT_IND+ AUX_ADV_IND),可在次广播频道通过AUX_CONNECT_REQ向广播设备请求连接,并监听辅助连接响应AUX_CONNECT_RSP。Payload格式参考CONNECT_IND。
03 蓝牙开发 - 图55

6.4.3.15. AUX_CONNECT_RSP

AUX_CONNECT_RSP为辅助连接响应,当接收设备在次广播频道通过AUX_CONNECT_REQ向广播设备请求连接后,广播设备需通过AUX_CONNECT_RSP响应接收设备,告之已建立连接。

6.4.3.16. 广播数据格式

GAP协议针对广播报文、周期广播报文和扫描响应报文的数据部分(Payload中的AdvData或ScanRspData字段)定义了标准的格式,分有效数据以及无效数据两部分,无效部分填充0,有效部分由多个AD结构(AD structures)组成。
03 蓝牙开发 - 图56

Length定义了这个AD structure的长度,AD Type定义了数据的类型,GAP协议定义了相应的类型值,如设备名,uuid等,AD Data为AD Type的解析数据。

static uint8_t raw_adv_data[] = {
0x02, 0x01, 0x06,

0x02, 0x0a, 0xeb,

0x03, 0x03, 0xab, 0xcd

};

第一个AD structure的Type为0x01,表示Flags,数据0x06表示LE通用发现模式,不支持BR/EDR。

第二个AD structure的Type为0x0a,表示发射功率等级,数据0xeb表示功率等级为-21。

第三个AD structure的Type为0x03,表示完整的16位uuid列表,数据0xab 0xcd表示uuid为0xcdab。

6.4.4. 数据通道PDU(data physical channel PDU)

设备建立连接处于连接状态,将使用数据通道PDU进行通信。包格式包含16/24位的Header字段、可变长的Payload字段以及可选的32位消息完整性校验(MIC)字段。
03 蓝牙开发 - 图57

LLID字段为逻辑链路标识符。0b01: L2CAP消息数据分段延续或空PDU,0b10: L2CAP消息数据段开始或无分段的完整消息PDU,0b11: 用于连接管理的控制PDU。
NESN字段为下一个预期序列号,用于数据包的确认,如果设备成功接收序列号为0的报文,在其确认报文中,应将NESN设为1,告知对端序列号0的数据包已被接收,否则对端将重传序列号0的数据包。
SN字段为序列号,用于数据传输的可靠,发送新的数据包时,序列号应与上个数据包序列号不同,否则为重传包。
MD字段为更多数据,用于告知对端设备还有其它的数据准备发送,需继续维持当前连接事件的通信,一旦不在有数据发送,MD字段为0,设备可以快速结束本次连接事件,从而节省功耗。
CP(CTEInfo Present)字段指定Header是否有CTEInfo字段。
RFU字段为保留位。
Length字段指定Payload和MIC字段的长度,0255字节。
CTEInfo字段为可选项,指定了固定频率扩展信号(CTE)的相关信息,如CTE信号的持续时间,AoA还是AoD信号。
Payload分数据PDU或控制PDU,数据PDU的Payload包含L2CAP数据,如果数据过长,则进行分段传输。控制PDU用于控制器间的连接管理通信,Payload包含1字节操作码以及0
250的控制数据。

03 蓝牙开发 - 图58
控制PDU载荷

MIC字段为消息完整性校验,用于加密连接通信中,如果空数据或者未加密连接,无此字段。

6.4.5. 等时同步通道PDU(isochronous physical channel PDU)

设备需要传输等时同步数据(如实时音频),将使用等时同步通道PDU。如果是一对多广播传输,则使用BIS PDU(Broadcast Isochronous PDU),发送设备需进入等时广播状态,并由AUX_SYNC_IND的ACAD(Additional Controller Advertising Data)字段提供等时同步参数信息BIGInfo,接收设备需进入同步状态,从监听接收的AUX_SYNC_IND报文ACAD字段获取BIGInfo信息等时同步到发送设备。如果是一对一可靠传输,则连接后使用CIS PDU(Connected Isochronous PDU)。
03 蓝牙开发 - 图59

等时同步通道PDU包格式包含16位的Header字段、可变长的Payload字段以及可选的32位消息完整性校验(MIC)字段。

6.5. 包时序

6.5.1. 帧间隔

6.5.1.1. 帧内部间隔

同一频道两个相邻包必须有一个空闲间隔,该时间间隔固定为T_IFS=150us。

03 蓝牙开发 - 图60

6.5.1.2. 最小扩展帧间隔

当使用扩展广播报文时,Payload使用通用扩展广播载荷格式(Common Extended Advertising Payload Format)。如果具有AuxPtr字段引导下一个扩展广播报文时,则扩展帧间的最小间隔定义为T_MAFS=300us。
03 蓝牙开发 - 图61

6.5.1.3. 最小子事件间隔

子事件的最后一个包到下一个子事件第一个包的包间隔定义为最小子事件间隔T_MSS=150us。
03 蓝牙开发 - 图62

6.5.2. 事件

6.5.2.1. 广播事件

设备每次广播时,通常依次在每个主广播频道(37、38、39)发送相同的报文,这些报文被称为一个广播事件。
03 蓝牙开发 - 图63

广播事件间隔T_advEvent由广播间隔advInterval以及广播随机延时advDelay决定,计算公式为T_advEvent = advInterval + advDelay。其中广播间隔advInterval由主机控制,0.625ms时间单位,范围在20ms10485.759375s。advDelay随机延时值在010ms,用于避免设备间很长一段时间同时广播造成的干扰。
03 蓝牙开发 - 图64

6.5.2.2. 扩展广播事件

扩展广播事件开始于ADV_EXT_IND PDU,结束于最后一个扩展广播PDU,如AUX_ADV_IND、AUX_CHAIN_IND。
03 蓝牙开发 - 图65

6.5.2.3. 周期广播事件

周期广播事件开始于ADV_SYNC_IND PDU,结束于最后一个扩展广播PDU,如AUX_CHAIN_IND。两个ADV_SYNC_IND间隔定义为周期广播间隔,1.25ms时间单位,范围在7.5ms~81.91875s

03 蓝牙开发 - 图66

6.5.3. 时序图

1) 对于可被扫描的广播报文ADV_IND或ADV_SCAN_IND,在扫描状态下满足条件的接收设备可以在主广播频道发起扫描请求SCAN_REQ,广播设备进行扫描响应SCAN_RSP提供更多的设备信息。

03 蓝牙开发 - 图67

2) 如果是可被扫描的扩展广播报文ADV_EXT_IND+AUX_ADV_IND,在扫描状态下满足条件的接收设备可以在次广播频道发起扫描请求AUX_SCAN_REQ,广播设备进行扫描响应AUX_SCAN_RSP提供更多的设备信息。

03 蓝牙开发 - 图68

3) 对于可被连接的广播报文ADV_IND或ADV_DIRECT_IND,在发起状态下满足条件的接收设备可以在主广播频道发起连接请求CONNECT_IND,并进入连接状态。
03 蓝牙开发 - 图69

4) 如果是可被连接的扩展广播报文ADV_EXT_IND+ AUX_ADV_IND,在发起状态下满足条件的接收设备可以在次广播频道发起连接请求AUX_CONNECT_REQ,并监听辅助连接响应AUX_CONNECT_RSP。
03 蓝牙开发 - 图70

5) 周期广播报文AUX_SYNC_IND可以在比较精确的周期时间广播数据,同步接收设备在确定的周期时间进行监听,降低了功耗。使用次广播频道,避免主广播频道的拥挤,可传输更大容量的广播数据。

03 蓝牙开发 - 图71

6) 如果主机广播数据过长,将会被分段,通过一个或多个AUX_CHAIN_IND分包广播传输,最大能支持1650字节的主机广播数据。
03 蓝牙开发 - 图72

6.6. GATT

为了尽可能减少协议的数量,精简协议,BLE蓝牙使用了属性协议ATT(Attribute Protocol)。GATT(Generic Attribute Profile)描述了一种使用ATT的服务框架,该框架定义了服务(Service)及其特性(Characteristic)的过程和格式,过程包括特性的发现、读、写、通知、指示以及广播配置。

属性(Attribute)对应着一个结构数据,该结构数据组成如下:

03 蓝牙开发 - 图73

属性句柄(Attribute Handle),范围值为0x0001~0xFFFF,可通过属性句柄访问该属性。例如多个温度属性,通过属性句柄可以区分访问不同的温度属性。
属性类型(Attribute Type),用uuid区分各种各样的数据类型,如温度、压强、体积、功率、时间等等,可以为16位也可以为128位。蓝牙组织对常用的一些数据类型进行了归类,赋予不同的数据类型不同的uuid,分成如下5组。例如0x2A09表示电池信息,0x2902表示客户端特性配置。
0x1800 ~ 0x26FF,用作服务类通用唯一识别码。

0x2700 ~ 0x27FF,用于标识计量单位。

0x2800 ~ 0x28FF,用于区分属性类型。

0x2900 ~ 0x29FF,用于特性描述。

0x2A00 ~ 0x7FFF,用于区分特性类型。

属性值(Attribute Value),用于承载属性的信息,属性值长度根据不同的属性可能为固定长度,也可以是可变长度。
属性权限(Attribute Permissions),每个属性对各自的属性值有相应的访问权限,比如有些属性是可读的、有些是可写的、有些是可读可写的等等。
通常把存有属性的设备称为服务器(Server),将获取对端数据的设备称为客户端(Client)。

客户端给服务器发数据,需要通过对服务器数据写操作(Write),来完成数据发送。写操作分写入请求(Write Request)和写入命令(Write Command),其中写入请求需对方回复响应(Write Response),写入命令无需对方回复响应。

服务器给客户端发数据,主要通过服务器指示(Indication)或通知(Notification)将服务器端更新的数据发给客户端。服务器指示需对方回复,服务器通知无需对方回复。客户端也可以主动通过读操作读取服务器的数据。

GATT规范由一个或多个必要的服务(Service)组成,每个服务可以引用其他的服务以及包含相应的特性(Characteristic),每个特性包含一个特性值属性(Properties),如可读可写信息;一个特性值(Value),承载特性的真正内容;可能的描述符(Descriptor),用于对特性进一步描述,可以没有描述符,也可以有多个描述符。

03 蓝牙开发 - 图74

6.7. ESP32 BLE蓝牙程序

以ESP-IDF 中\examples\bluetooth\bluedroid \ble\gatt_server\main\gatts_demo.c为例,该例程实现GATT服务器数据的读写。ESP32使用bluedroid主机协议栈,有如下几个使用步骤:

6.7.1. 控制器初始化

esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();

ret = esp_bt_controller_init(&bt_cfg);

6.7.2. 控制器使能

使能低功耗蓝牙模式。

ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);

6.7.3. 初始化并使能bluedroid主机协议栈

ret = esp_bluedroid_init();

ret = esp_bluedroid_enable();

6.7.4. 注册GAP、GATT回调函数

GAP用于发现及连接设备,事件处理回调函数为gap_event_handler,GATT事件处理回调函数为gatts_event_handler。

esp_ble_gatts_register_callback(gatts_event_handler);

esp_ble_gap_register_callback(gap_event_handler);

6.7.5. 注册GATT应用

esp_ble_gatts_app_register(PROFILE_A_APP_ID);

esp_ble_gatts_app_register(PROFILE_B_APP_ID);

6.7.6. 应用处理

GATT创建服务,添加特性、描述符,对特性值的读写访问,在GATT回调函数中处理。

6.7.7. 启动运行

烧录并运行gatt_server例程代码,手机端安装BLE蓝牙调试程序nRF Connect,打开蓝牙并进入APP,可以发现BLE蓝牙广播信息。

03 蓝牙开发 - 图75

点击RAW,显示原始的广播数据及扫描响应数据,数据为AD structure格式:

03 蓝牙开发 - 图76
点击连接后,可以获取gatt server支持的服务uuid,其中0x00EE以及0x00FF为自定义uuid。

03 蓝牙开发 - 图77

以0x00FF服务为例,该服务具有一个0xFF01的特性,特性属性为可读、可写、可通知,特性描述符为0x2902(客户端特性配置)。

03 蓝牙开发 - 图78
点击“向下箭头”,读取的特征值为0xdeedbeef。

03 蓝牙开发 - 图79
点击“向上箭头”,可以写入特征值0x123456。

03 蓝牙开发 - 图80
点击“三个向下箭头”,可以监视服务器上特性值变化的通知,此时0x2902描述符值为0x0001,表示特性通知使能。
03 蓝牙开发 - 图81