通用步骤
本文档总结了 ESP-IDF 中 BLE 的公共实现流程,涵盖关键的系统与蓝牙初始化、GAP 与 GATT 配置、服务与特征注册、广播与扫描、连接管理及事件处理等通用操作。文档列出各阶段的基础知识、涉及的 API 及调用顺序,为不同 BLE 示例提供统一的实现参考。文档内容仅基于 Bluedroid 实现,不涉及 NimBLE。
掌握该文档内容有助于快速理解 BLE 的核心流程,在不同应用中复用公共代码,并结合示例文档高效定位模式差异与特有实现。
广播与扫描
广播与扫描参数
BLE 设备发现与可见性由广播参数和扫描参数控制,它们分别用于 GATT Server 广播和 GATT Client 扫描。合理配置这些参数可以平衡设备被发现的速度、连接响应和功耗消耗。
广播参数:广播参数是 BLE Server 在广播过程中用来控制广播行为的一组设置,主要包括:
在 ESP-IDF 中,可通过定义 esp_ble_adv_params_t
结构体来设置广播参数,其中与目标设备地址相关的成员仅在定向广播时需要配置。
其结构体成员如下:
adv_int_min
:最小广播间隔。数据类型为uint16_t
。
adv_int_max
:最大广播间隔。数据类型为uint16_t
。
广播间隔 取值范围
默认值
0x0020 到 0x4000
0x0800 对应 1.28 秒
备注
设置间隔和时间时间转化步骤如下:
将十六进制 0x0800 转为十进制,为 2048。
BLE 广播间隔的单位是 0.625 ms,乘以 0.625 ms 得到毫秒数:2048 × 0.625 = 1280 ms
转为秒:1280 ÷ 1000 = 1.28 秒
adv_type
:广播类型。数据类型为esp_ble_adv_type_t
。
esp_ble_adv_type_t
广播类型
说明
ADV_TYPE_IND
可被扫描和连接的普通广播
ADV_TYPE_DIRECT_IND_HIGH
高速可连接定向广播
ADV_TYPE_SCAN_IND
可被扫描但不可连接的广播
ADV_TYPE_NONCONN_IND
不可被扫描和连接的广播
ADV_TYPE_DIRECT_IND_LOW
低速可连接定向广播
own_addr_type
:本地设备在广播或扫描时所使用的蓝牙地址类型。数据类型为esp_ble_addr_type_t
。
esp_ble_addr_type_t
地址类型
说明
BLE_ADDR_TYPE_PUBLIC
公共地址
BLE_ADDR_TYPE_RANDOM
随机地址
BLE_ADDR_TYPE_RPA_PUBLIC
可解析私有地址(RPA),基于公有身份地址生成
BLE_ADDR_TYPE_RPA_RANDOM
可解析私有地址(RPA),基于随机身份地址生成
peer_addr
:目标设备在广播或扫描时所使用的蓝牙地址。数据类型为esp_bd_addr_t
,本质是一个 6 字节数组,用于存储蓝牙设备的 MAC 地址,需要通过扫描回调获取或手动赋值为具体地址。
peer_addr_type
:目标设备在广播或扫描时所使用的蓝牙地址类型。数据类型为esp_ble_addr_type_t
。但仅支持公共地址和随机地址。
channel_map
:广播信道。数据类型为esp_ble_adv_channel_t
。
esp_ble_adv_channel_t
信道选择
说明
ADV_CHNL_37
选择 37 号信道进行广播
ADV_CHNL_38
选择 38 号信道进行广播
ADV_CHNL_39
选择 39 号信道进行广播
ADV_CHNL_ALL
选择以上三条信道进行广播
adv_filter_policy
:广播过滤策略。数据类型为esp_ble_adv_filter_t
。
esp_ble_adv_filter_t
过滤策略
说明
ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY
允许所有设备的扫描和连接请求
ADV_FILTER_ALLOW_SCAN_WLST_CON_ANY
只允许白名单设备的扫描请求,连接请求允许所有设备
ADV_FILTER_ALLOW_SCAN_ANY_CON_WLST
扫描请求允许所有设备,连接请求只允许白名单设备
ADV_FILTER_ALLOW_SCAN_WLST_CON_WLST
枚举结束值,用于广告过滤策略的值检查
扫描参数:扫描参数是 BLE Client 在扫描周围设备时用来控制扫描行为的一组设置,主要包括:
在 ESP-IDF 中,可通过定义 esp_ble_scan_params_t
结构体来设置扫描参数。
其结构体成员如下:
scan_type
:扫描类型。数据类型为esp_ble_scan_type_t
。
esp_ble_scan_type_t
扫描类型
说明
BLE_SCAN_TYPE_PASSIVE
被动扫描
BLE_SCAN_TYPE_ACTIVE
主动扫描
own_addr_type
:本地设备在广播或扫描时所使用的蓝牙地址类型。数据类型为esp_ble_addr_type_t
。
scan_filter_policy
:扫描过滤策略。数据类型为esp_ble_scan_filter_t
。
esp_ble_adv_filter_t
过滤策略
说明
BLE_SCAN_FILTER_ALLOW_ALL
接受除了未定向且未针对本设备的定向广播包之外的所有广播包(默认)
BLE_SCAN_FILTER_ALLOW_ONLY_WLST
仅接受广播包来自白名单中的设备
BLE_SCAN_FILTER_ALLOW_UND_RPA_DIR
接受所有未定向广播包,发起者地址为可解析私有地址的定向广播包,以及针对本设备的定向广播包
BLE_SCAN_FILTER_ALLOW_WLIST_RPA_DIR
接受所有广播包来自白名单中的设备,发起者地址为可解析私有地址的定向广播包,针对本设备的定向广播包
scan_interval
:扫描间隔,即扫描频率。数据类型为uint16_t
。
scan_window
:扫描窗口,即每个扫描周期内实际执行扫描的时间长度。数据类型为uint16_t
。
扫描间隔和窗口 取值范围
默认值
0x0004 到 0x4000
0x0010 对应 10 毫秒
备注
设置间隔和时间时间转化步骤如下:
将十六进制 0x0800 转为十进制,为 2048。
BLE 广播间隔的单位是 0.625 ms,乘以 0.625 ms 得到毫秒数:2048 × 0.625 = 1280 ms
转为秒:1280 ÷ 1000 = 1.28 秒
scan_duplicate
:决定是否为每个接收到的广播包生成广播报告。数据类型为esp_ble_scan_duplicate_t
。
esp_ble_scan_duplicate_t
扫描重复过滤
说明
BLE_SCAN_DUPLICATE_DISABLE
为每个接收到的广播包生成广播报告
BLE_SCAN_DUPLICATE_ENABLE
过滤重复的广播报告
BLE_SCAN_DUPLICATE_ENABLE_RESET
启用重复过滤,每个扫描周期重置,仅在 BLE 5.0 支持
BLE_SCAN_DUPLICATE_MAX
保留以备将来使用
广播标志
广播标志是 BLE 广播包的一个标准字段,扫描端可通过该标志快速判断目标设备是否支持连接、是否为 BLE 设备,以及是否支持双模(BR/EDR 与 LE)。
同一广播包中仅允许出现一个发现模式标志(限制可发现或一般可发现),否则会导致协议栈解析冲突。此外,选择双模支持标志(DMT)并不意味着设备一定会工作在双模模式,还需配合硬件与协议栈配置。
以下为 ESP-IDF 提供的宏定义,可直接使用并通过按位或(|
)组合配置:
ESP_BLE_ADV_FLAG_LIMIT_DISC
:一般限制可发现模式,表示设备在限定时间内可被发现,常用于降低功耗。
ESP_BLE_ADV_FLAG_GEN_DISC
:一般可发现模式,表示设备可持续被发现,适合长期可连接设备。
ESP_BLE_ADV_FLAG_BREDR_NOT_SPT
:不支持 BR/EDR(经典蓝牙),仅支持 BLE 模式。
ESP_BLE_ADV_FLAG_DMT_CONTROLLER_SPT
:控制器支持双模。
ESP_BLE_ADV_FLAG_DMT_HOST_SPT
:主机协议栈支持双模。
ESP_BLE_ADV_FLAG_NON_LIMIT_DISC
:非限制可发现模式,表示未设置发现模式相关标志。
常用配置:
ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT
:可持续被发现且仅支持 BLE 模式。
广播数据包
一个完整的广播数据包由多个部分组成,详细介绍见 广播数据包结构。在实际编写代码时,只需要编写 广播数据 和 扫描响应数据 部分,两者结构完全相同。
在配置广播数据时,可以选择使用原始广播数据包或使用结构体 esp_ble_adv_data_t
进行配置。
使用原始广播数据包:使用原始广播数据包时,需要理解每个字段的概念(长度、类型、内容)并按协议格式排列,ESP-IDF 封装类型可以简化操作,但核心仍是正确打包每个字节。
原始广播数据包即直接按照 BLE 广播协议格式手工打包的字节数组,每个字段都按字节排列。
每个广播数据条目,即字段,都由三部分组成,并且字节顺序需要严格遵守 BLE 协议规范,确保其他 BLE 设备能够正确解析。
广播数据结构 序号
名称
字节数
说明
1
数据长度 (AD Length)
1
当前字段占用的字节数,不包括自身字节
2
数据类型 (AD Type)
n
标识字段类型,如 Flags、服务 UUID、设备名称等,通常占用 1 字节
3
数据 (AD Data)
(AD Length - n)
字段实际承载的信息,如具体的设备名称或服务 UUID
对于常用数据类型(如 Flags、TX Power、服务 UUID、设备名称),ESP-IDF 提供了宏定义,可直接使用,避免手动计算或填写每个字节。全部封装类型可查看 esp_ble_adv_data_type。
通过结构体配置:通过结构体配置时,用户只需关注“放什么内容”,ESP-IDF 会帮你处理“怎么放”的协议格式,避免手工打包字节的复杂性。
ESP-IDF 提供了结构体
esp_ble_adv_data_t
,只需填写各个字段即可,BLE 协议栈会自动将其打包成符合广播协议格式的字节数组。
结构体成员 结构体成员
说明
配置
set_scan_rsp
选择当前配置的数据是用于扫描响应数据还是广播数据。
true
表示作为扫描响应数据,false
表示作为广播数据。
include_name
是否在广播数据中包含设备名称。
true
表示包含,false
表示不包含。
include_txpower
是否包含 TX 功率,用于指示设备发送功率大小。
true
表示包含,false
表示不包含。
min_interval
设备建议的最小连接间隔,连接时间 =
min_interval
× 1.25 ms。取址范围为 0x0006–0x0C80,0xFFFF 表示未指定。
max_interval
设备建议的最大连接间隔,连接时间 =
max_interval
× 1.25 ms。必须大于或等于
min_interval
,0xFFFF 表示未指定。
appearance
设备外观类型,如心率监测器、键盘等,用于标识设备类别。
使用 Bluetooth SIG 定义的 16 位整数值,如 0x0340 表示心率监测器。
manufacturer_len
厂商自定义数据长度。
调用
sizeof()
获取,取值不得超过 BLE 广播数据总长度(31 字节)减去其他已占用字段的长度;配置为 0 时表示不在广播数据中包含厂商自定义数据,此时p_manufacturer_data
需配置为NULL
。
p_manufacturer_data
指向厂商自定义数据的指针。
当设备需要在广播中发送厂商特定信息,例如硬件版本号、设备序列号、私有协议标识或特定状态信息时可传入自定义字节数组,否则传入
NULL
并将manufacturer_len
配置为 0。
service_data_len
服务数据长度,用于广播特定服务信息。
调用
sizeof()
获取,取值不得超过 BLE 广播数据总长度(31 字节)减去其他已占用字段的长度;配置为 0 时表示不在广播数据中包含服务相关数据,此时p_service_data
需配置为NULL
。
p_service_data
指向服务数据的指针。
当设备需要在广播中发送特定服务相关的信息时,例如传感器当前读数、服务版本号、服务特定标识符或初始配置信息等,可传入自定义字节数组;否则传入 NULL 并将
service_data_len
配置为 0。
service_uuid_len
广播的服务 UUID 数组长度。
根据广播的服务数量填写,可调用
sizeof()
获取。
p_service_uuid
指向服务 UUID 数组,用于告诉扫描设备当前广播的服务类型。
可使用标准 16 位 UUID 或自定义 16/128 位 UUID。
flag
广播标志,用于表示当前设备的可发现模式和支持的协议类型。
ESP-IDF 提供了对应的 宏定义,用户可直接使用并按位或(
|
)进行组合配置。
GATT
GATT 数据操作
GATT 数据操作指的是对 GATT 服务器上的特征数据进行访问,包括由客户端发起和由服务器发起的操作。有关 GATT 数据操作的详细内容和示例,可参考原文档 GATT 数据操作。
除了原文档中提及的读、写和写(无需响应)操作外,GATT 还支持准备写(Prepare Write) 操作,用于向服务器写入较长的数据或进行分包写入,以保证写入事务的一致性和可靠性。
在准备写操作中:
客户端:
将要写入的数据分成多个片段,依次发送 Prepare Write Request,每个片段包含特征句柄、数据片段以及偏移量(表示该片段在整个数据中的位置)。
在发送完所有片段后,发起 Execute Write Request,通知服务器将之前所有缓存的分片数据一次性写入特征值,从而完成整个写入事务。
服务器:
将接收到的片段缓存到临时缓冲区,并返回 Prepare Write Response,确认该片段已接收。
在收到客户端的 Execute Write Request 后,将所有缓存数据统一写入特征值,实现完整写入。
因此,在实际编程中,若设备需要作为服务器接收分片数据时,需要为“准备写”创建专用缓冲区,用于存储接收到的分片数据,以便在收到 Execute Write Request 后统一写入特征值。
管理 GATT Profile 信息和状态
GATT Profile 实例结构体用于集中管理 GATT 层信息,包括回调函数、接口 ID、服务、特征和描述符等数据,对于 Server 和 Client 示例均适用;其中,Server 关注本地属性和访问权限,Client 关注远端服务的发现和操作,以保证多 Profile、多连接场景下的正确性和高效访问。
GATT Server Profile 实例结构体
Server 示例中的 gatts_profile_inst
结构体主要用于管理本地服务、特征和描述符的信息,以及回调函数和连接状态。其结构体成员如下:
GATT 层级 |
结构体成员 |
说明 |
---|---|---|
Profile |
|
GATT Server 回调函数,用于绑定 Profile 与事件处理逻辑,方便处理该 Profile 下的所有 客户端请求事件。 |
|
GATT 接口 ID,用于标识该 Profile,区分不同 Profile 或 GATT 接口。 |
|
|
应用 ID,用于标识该 Profile 所属应用,在注册多个 Profile 时区分不同实例。 |
|
|
连接 ID,以区分不同客户端连接。 |
|
Service |
|
服务句柄,用于后续操作访问该服务。 |
|
服务标识,包括服务 UUID 和主/次服务信息。 |
|
Characteristic |
|
特征句柄,用于后续操作访问该特征值。 |
|
特征 UUID,便于客户端识别特征。 |
|
|
特征权限,如可读、可写等,决定客户端对特征的访问方式。 |
|
|
特征属性,由程序指定特征支持的操作,如读、写、通知、指示。 |
|
Descriptor |
|
描述符句柄,用于后续操作访问该描述符。 |
|
描述符 UUID,常用于标识 CCCD 等描述符。 |
GATT Client Profile 实例结构体
Client 示例中的 gattc_profile_inst
结构体主要用于管理对远端设备服务、特征和描述符的操作,以及连接状态和回调逻辑。其主要成员如下:
GATT 层级 |
结构体成员 |
说明 |
---|---|---|
Profile |
|
GATT Server 回调函数,绑定 Profile 与事件处理逻辑,用于处理该 Profile 下的所有 客户端请求事件。 |
|
GATT 接口 ID,标识该 Profile,用于区分不同 Profile 或 GATT 接口。 |
|
|
应用 ID,用于标识该 Profile 所属应用,在注册多个 Profile 时区分不同实例。 |
|
|
连接 ID,以区分不同客户端连接。 |
|
|
远端设备蓝牙地址,用于标识连接目标。 |
|
Service |
|
远端服务起始句柄,用于在后续操作中标识服务的起始位置。 |
|
远端服务结束句柄,用于在后续操作中标识服务的结束位置。 |
|
Characteristic |
|
特征句柄,用于后续操作访问该特征值。 |
结构体中的大部分成员都是在运行时通过协议栈生成或分配的,注册服务或特征时由协议栈返回。在定义结构体时,只需预先声明这些成员,初始化可使用默认值或 0,后续在注册服务、特征或描述符的回调中将实际值赋入结构体。
以上 Profile 实例结构体具有较强的普遍性,适用于大多数 GATT Server 和 Client 示例及应用场景,因为每个 Profile 都需要管理回调、接口 ID、服务、特征和描述符等信息。只有在特殊需求下才需要修改或扩展结构体成员,例如:
多特征或多描述符管理:需要将对应的句柄和 UUID 变量修改为数组或链表,以保存多个特征或描述符的信息。
缓存或状态标志:需要新增结构体成员,用于将长数据写入缓冲区、通知状态或自定义状态标志。
多客户端或复杂逻辑:需要新增结构体成员,用于保存连接上下文信息、事件队列或定时器指针。
Client 特殊需求:可能需要保存远端服务发现结果、特征元素数组或描述符元素数组指针,以便快速查找和操作目标服务或特征。
GATT 属性表
GATT 属性表 是一组按顺序排列的“属性(Attribute)”。每个属性代表一条可被客户端发现/读写的数据项。所有服务、特征及其描述符都以“属性”的形式存放在这张表里,由协议栈分配句柄进行唯一标识。
一个属性由 5 个部分组成:
Handle:运行时由协议栈一次性分配的递增编号,用来唯一标识和访问该属性。需要将它们存入一个
handle_table
以备在事件回调里使用。声明 UUID:属性的唯一标识符,可以是 16 位、32 位或 128 位 UUID,决定该属性的具体用途和含义。
Permissions:定义客户端对该属性的访问权限,例如可读、可写、需加密、需认证等。
Value:属性承载的实际数据或指向数据的指针,包括属性值所允许的最大字节数和属性当前实际存储的字节数。
Type:属性类型,用于说明该属性在 GATT 层的角色。
Primary Service Declaration(主服务声明):用于定义一个完整的功能集合,例如心率服务、设备信息服务。
Secondary Service(次服务声明):属于可选项,与主服务结构相同,但通常被其他服务包含使用而不直接对外暴露。
Include Declaration(包含服务声明):属于可选项,用于在当前服务中引用另一个服务的定义。包含服务允许重用已有服务的功能,而无需重复定义相关特征。
Characteristic Declaration(特征声明):用于说明一个特征的元信息,包括属性(Properties)、该特征值的 Handle 以及特征的 UUID,其中特征值的 Handle 是特征值在 GATT 数据库中的编号,不同于特征本身的声明 Handle。
Characteristic Value(特征值):存储该特征的实际数据内容,例如心率数值或温度读数。
Characteristic Descriptors(特征描述符):属于可选项,用于补充说明特征的属性。
Client Characteristic Configuration(CCC/CCCD):用于让客户端控制某个特征是否启用。
Characteristic User Description(CUD):特征的用户可读描述,即用自然语言解释特征用途的文本,例如“设备序列号”或“电池电量百分比”,方便客户端直接显示。
Characteristic Presentation Format(CPF):数值的格式(单位、分辨率等)。
Characteristic Extended Properties(CEP):扩展属性。
在 ESP-IDF 中属性表定义步骤如下:
定义属性索引:使用枚举定义所有属性条目及属性总数,后续可通过索引访问对应属性的 Handle,避免在代码中使用魔法数字。
备注
“魔法数字”通常指在代码中直接写出的没有解释含义的数字常量。
创建属性数组:定义一个
esp_gatts_attr_db_t
类型的数组,数组长度等于属性总数,每个数组元素对应一个属性条目。排列属性顺序:数组中的每个元素代表一个属性。典型的属性顺序是:Service 声明 >(特征 A:声明 > 值 > 描述符)>(特征 B:声明 > 值 > 描述符),每个条目的 Handle 由栈统一创建并返回。
配置属性字段:每个属性条目需要配置的字段如下:
属性响应控制字段:指定属性在被客户端进行读写操作时由谁负责响应。数据类型为
esp_attr_control_t
,见 GATT API。
可选值
说明
ESP_GATT_AUTO_RSP
由协议栈自动生成响应,不需要用户手动处理,适合静态、简单的属性值。
ESP_GATT_RSP_BY_APP
由应用程序在回调函数中自行构造并发送响应,适合动态生成或需要权限判断的属性值。
属性元信息字段:描述属性类型和访问控制。数据类型为
esp_attr_desc_t
,见 GATT API。
属性类型 UUID 的长度:如果是标准 GATT 类型(如 Primary Service),用 16 位 UUID;如果是自定义服务或特征,通常用 128 位 UUID 以避免冲突。
可选值
说明
ESP_UUID_LEN_16
UUID 长度为 2 字节
ESP_UUID_LEN_32
UUID 长度为 4 字节
ESP_UUID_LEN_128
UUID 长度为 16 字节
UUID 指针:指向该属性类型的 UUID 数据。16、32 或 128 位数据都需强制转换为
uint8_t*
,以统一处理字节数组。访问权限:对于 Service 声明,权限一般只设置为可读。
可选值
说明
ESP_GATT_PERM_READ
只读
ESP_GATT_PERM_WRITE
只写
ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE
可读可写
ESP_GATT_PERM_READ_ENCRYPTED
属性可读,但必须在加密连接下才能读取,即需要 BLE 链路加密。
ESP_GATT_PERM_WRITE_ENCRYPTED
属性可写,但必须在加密连接下才能写入,保证数据传输安全。
该属性值的最大长度:该属性值可扩展的上限。
该属性值的当前长度:初始化时已被占用的数据长度。
属性类型
属性值内容
最大长度
当前长度
服务声明
服务 UUID
服务 UUID 的长度
等于最大长度
特征声明
特征属性(例如读、写、通知等)
1 字节
等于最大长度
特征值
自定义数据
自定义长度,可根据需求设置
初始化时写入的数据长度,可以小于或等于最大长度
服务声明
根据描述符类型而定(如 CCCD、用户自定义描述符)
由描述符类型决定
初始化值长度,固定描述符通常等于最大长度,可变描述符可能小于最大长度
初始值指针:指向该属性的初始数据,可以是常量数组或动态分配的内存。
GATT 事件回调函数
在 GATT Server 和 GATT Client 示例中都定义了事件回调函数,用于接收并处理 BLE 协议栈上报的事件,作为协议栈与应用层之间的桥梁。二者在形式上高度一致:都由 ESP-IDF 框架注册,统一作为 GATT 层与用户应用的接口入口,然后在函数内部根据事件类型调用对应的用户逻辑,实现事件分发和处理。
Server 回调函数主要处理被动响应事件,多与本地属性表相关,例如服务与特征的注册、客户端的读写请求处理、通知和指示的发送,重点在于如何提供和维护本地 GATT 服务。
Client 回调函数用于主动发起操作并处理结果,处理的事件主要涉及远端服务的发现、特征和描述符的读写、通知或指示的订阅与接收等,重点在于如何操作和管理远端设备资源。
示例中的事件回调函数均为 static
类型、无返回值,用于处理和分发 GATT 事件。传参形式如下:
传入参数 |
参数功能 |
参数说明 |
---|---|---|
|
事件类型。 |
可根据事件类型在回调中进行不同处理。 |
|
GATT 服务器访问接口。 |
由协议栈分配,用于区分不同的 GATT Profile 服务实例,在事件回调中识别对应的 Profile。 |
|
指向事件参数的结构体指针,包含该事件的详细信息。 |
不同事件对应不同的联合体成员,例如广播数据、扫描结果、连接信息等。 |
函数功能可以分为两个阶段:
注册事件处理:
当事件类型为注册完成时,检查注册是否成功。
如果注册成功,将协议栈分配的接口标识符
gatts_if
存入 Profile 表中,供后续事件识别使用;如果注册失败,则打印错误日志并终止分发。
事件分发:
如果传入的接口标识符
gatts_if
未指定接口(ESP_GATT_IF_NONE
),则事件会广播给所有 Profile,让每个 Profile 自行判断是否处理。如果接口标识符已指定,则事件仅分发给与之匹配的 Profile,避免无关 Profile 接收。
分发的事件涵盖了 GATT 协议层的完整交互:
Server 侧处理服务注册、读写请求、通知发送等。
Client 侧处理服务发现、特征访问、订阅通知等。
每个 Profile 回调函数通过
gatts_if
和event
判断是否响应该事件,实现对多个 Profile 的逻辑隔离与管理。
客户端配置描述符 (CCCD)
CCCD(Client Characteristic Configuration Descriptor) 是 GATT 协议规定的一个标准描述符,用来存储客户端对某个特征的订阅状态。
CCC(Client Characteristic Configuration) 指标准描述符中的内容,即 CCCD 中保存的值。
客户端通过写入 CCC 控制服务器特征的通知和指示功能:
通知:服务器发送数据给客户端,但不等待确认;客户端收到数据后可以直接处理,不反馈服务器。
指示:服务器发送数据并等待客户端确认;客户端收到数据后必须回复确认,服务器在收到确认后才认为发送成功。
在接收到客户端写入的 CCC 时,服务器根据写入值判断要执行的操作:
0x0001:开启通知,服务器构造将要发送的数据,并调用
esp_ble_gatts_send_indicate()
向客户端发送通知。0x0002:开启指示,服务器构造将要发送的数据,并调用
esp_ble_gatts_send_indicate()
向客户端发送指示,需要客户端返回 ACK 进行确认,保证数据可靠到达。0x0000:关闭通知和指示。停止对该特征的通知和指示发送,服务器不再主动向客户端推送数据
其他值:打印未知描述符错误。
调用 esp_ble_gatts_send_indicate()
向指定客户端发送特征数据时需要传入服务器实例、连接 ID、特征句柄、数据内容和是否需要客户端确认。更多参数说明请参考 GATT 服务器 API。
主函数说明
非易失性存储(NVS)
初始化非易失性存储:
非易失性存储是设备中用于存储持久化数据的关键模块,断电后数据依然保留。
调用
nvs_flash_init()
初始化 NVS,完成对存储介质的准备和挂载。相关参数说明请参考 非易失性存储库 API。
检查返回值:
及时发现初始化过程中可能出现的问题,比如 NVS 分区已满或版本不匹配等异常情况,避免程序在后续读写数据时出现错误,导致功能异常甚至崩溃。
若返回异常,调用
nvs_flash_erase()
擦除 NVS 分区。相关参数说明请参考 非易失性存储库 API。
ESP_ERR_NVS_NO_FREE_PAGES
:表示 NVS 分区已满,没有可用的空闲页,无法写入新数据。
ESP_ERR_NVS_NEW_VERSION_FOUND
:表示当前 NVS 分区的数据版本与库期望版本不匹配,可能需要升级或格式化分区。擦除分区后需要重新调用初始化函数,并再次检查返回值,确保初始化成功。
释放蓝牙内存
系统启动时,会为“全功能蓝牙”(BR/EDR + BLE)预留一部分内存池。
实际使用中需要根据需求 释放 不需要的内存,避免浪费。
备注
必须在调用 esp_bt_controller_init()
初始化蓝牙控制器前释放,否则内存分区将被固定,无法释放。
创建/初始化蓝牙控制器
启动蓝牙控制器
启动 已初始化的蓝牙控制器,并设置其模式。
初始化/启用协议栈
注册回调函数
注册 回调函数用于处理不同事件。
传入对应函数注册 GATT Server 回调函数,用于处理读写、连接、断开等事件。
传入对应函数注册 GAP 回调函数,用于处理广播和扫描事件。
注册 GATT 应用
注册 GATT 应用,将用户定义的服务逻辑接入协议栈,并获取事件回调通道。
注册完成后协议栈会触发
ESP_GATTS_REG_EVT
事件。
设置本地 GATT MTU(最大传输单元)
设置 本地 GATT MTU 的大小。
注册完成后会触发
ESP_GATTS_MTU_EVT
事件。备注
MTU 决定一次能传输的最大数据量,默认 23 字节,可根据需要进行调整。
API 汇总
蓝牙控制器初始化与使能
esp_bt_controller_init()
:用于初始化蓝牙控制器以分配任务和其他资源。参考 控制器 API。
参数解释 传入参数
参数说明
cfg
初始蓝牙控制器配置。
esp_bt_controller_enable()
:用于启用蓝牙控制器。参考 控制器 API。
参数解释 传入参数
参数说明
mode
要启用的蓝牙控制器模式。
esp_bluedroid_init()
:用于初始化并分配蓝牙所需资源。参考 Bluetooth API。
esp_bluedroid_enable()
:用于启用蓝牙。参考 Bluetooth API。
esp_bt_controller_mem_release
:用于释放指定模式的控制器内存。参考 控制器 API。
参数解释 传入参数
参数说明
mode
要释放内存的蓝牙控制器模式。
GATT 服务注册与操作
esp_ble_gatts_register_callback()
:用于注册 GATT 服务器应用回调函数。参考 GATT 服务器 API。
参数解释 传入参数
参数说明
callback
指向应用回调函数的指针。
esp_ble_gatts_app_register()
:用于注册 GATT 服务器应用。参考 GATT 服务器 API。
参数解释 传入参数
参数说明
app_id
不同应用的 UUID。
esp_ble_gatts_create_attr_tab()
:用于创建属性表。参考 GATT 服务器 API。
参数解释 传入参数
参数说明
gatts_attr_db
指向服务属性表的指针。
gatts_if
GATT 服务器访问接口
max_nb_attr
要添加到服务数据库的属性数量。
srvc_inst_id
服务实例 ID。
esp_ble_gatts_start_service()
:用于启动服务。参考 GATT 服务器 API。
参数解释 传入参数
参数说明
service_handle
要启动的目标服务句柄。
esp_ble_gatts_send_response()
:用于向客户端发送写入响应。参考 GATT 服务器 API。
参数解释 传入参数
参数说明
gatts_if
GATT 服务器访问接口。
conn_id
连接 ID。
trans_id
传输 ID。
status
响应状态。
rsp
指向响应数据的指针。
esp_ble_gatt_set_local_mtu()
:用于设置本地 MTU 的大小,需在建立 BLE 连接之前调用。
参数解释 传入参数
参数说明
mtu
期望的 MTU 的大小。
esp_ble_gattc_enh_open()
:用于创建 ACL 连接。参考 GATT Client API。
参数解释 传入参数
参数说明
gattc_if
GATT 客户端访问接口。
esp_gatt_create_conn
指向包含连接参数的结构体的指针。
备注
ACL(异步无连接)连接是蓝牙中一种基础的数据链路连接类型,用于在两个设备之间传输数据。
esp_ble_gattc_send_mtu_req()
:用于配置 GATT 通道的 MTU 大小。参考 GATT Client API。
参数解释 传入参数
参数说明
gattc_if
GATT 客户端访问接口。
conn_id
连接 ID。
esp_ble_gattc_search_service()
:用于从本地 GATTC 缓存中搜索服务。参考 GATT Client API。
参数解释 传入参数
参数说明
gattc_if
GATT 客户端访问接口。
conn_id
连接 ID。
filter_uuid
目标服务的 UUID。如果传入 NULL,则返回所有服务。
esp_ble_gattc_get_attr_count()
:用于获取本地 GATTC 缓存中指定服务或特征的属性数量。参考 GATT Client API。
参数解释 传入参数
参数说明
gattc_if
GATT 客户端访问接口。
conn_id
连接 ID。
type
属性类型。
start_handle
属性起始句柄。
end_handle
属性结束句柄。
char_handle
特征句柄。
count
在本地 GATTC 缓存中找到的指定属性类型的属性数量。
esp_ble_gattc_get_char_by_uuid()
:用于获取本地 GATTC 缓存中指定特征 UUID 的特征。参考 GATT Client API。
参数解释 传入参数
参数说明
gattc_if
GATT 客户端访问接口。
conn_id
连接 ID。
start_handle
属性起始句柄。
end_handle
属性结束句柄。
char_uuid
特征 UUID。
result
指向服务中找到的特征。
count
在本地 GATTC 缓存中找到的指定属性类型的属性数量。
esp_ble_gattc_register_for_notify()
:注册以接收特征的通知或指示。参考 GATT Client API。
参数解释 传入参数
参数说明
gattc_if
GATT 客户端访问接口。
server_bda
目标 GATT 服务器设备地址。
handle
目标 GATT 特征句柄。
esp_ble_gattc_get_descr_by_char_handle()
:用于获取本地 GATTC 缓存中指定特征句柄的描述符。参考 GATT Client API。
参数解释 传入参数
参数说明
gattc_if
GATT 客户端访问接口。
conn_id
连接 ID。
char_handle
特征句柄。
descr_uuid
描述符 UUID。
result
指向服务中找到的特征。
count
在本地 GATTC 缓存中找到的指定属性类型的属性数量。
esp_ble_gattc_write_char_descr()
:用于写入指定描述符句柄的特征描述符值。参考 GATT Client API。
参数解释 传入参数
参数说明
gattc_if
GATT 客户端访问接口。
conn_id
连接 ID。
handle
要写入的描述符句柄。
value_len
写入值的字节长度。
value
要写入的值。
write_type
属性写操作类型。
auth_req
认证请求类型。
esp_ble_gattc_write_char()
:用于写入指定特征句柄的特征值。参考 GATT Client API。
参数解释 传入参数
参数说明
gattc_if
GATT 客户端访问接口。
conn_id
连接 ID。
handle
要写入的描述符句柄。
value_len
写入值的字节长度。
value
要写入的值。
write_type
属性写操作类型。
auth_req
认证请求类型。
GAP 广播与设备信息
esp_ble_gap_set_device_name()
:用于设置本地蓝牙设备的名称。参考 GAP API。
参数解释 传入参数
参数说明
name
设备名称。
esp_ble_gap_config_adv_data_raw()
:用于设置原始广播数据。参考 GAP API。esp_ble_gap_config_scan_rsp_data_raw()
:用于设置原始扫描响应数据。参考 GAP API。
参数解释 传入参数
参数说明
raw_data
原始扫描响应数据。
raw_data_len
原始扫描响应数据长度。
esp_ble_gap_config_adv_data()
:用于覆盖 BTA 默认的广播参数。参考 GAP API。
参数解释 传入参数
参数说明
adv_data
指向用户自定义广播数据结构的指针。
esp_ble_gap_register_callback()
:用于触发 GAP 事件,例如扫描结果。参考 GAP API。
参数解释 传入参数
参数说明
callback
指向应用回调函数的指针。
esp_ble_gap_update_conn_params
:用于更新连接参数,仅在连接已建立时可用。参考 GAP API。
参数解释 传入参数
参数说明
params
连接更新参数。
esp_ble_gap_start_advertising()
:用于启动广播。参考 GAP API。
参数解释 传入参数
参数说明
adv_params
指向用户自定义的广播数据结构的指针。
esp_ble_gap_start_scanning()
:持续扫描周围的广播设备。用于参考 GAP API。
参数解释 传入参数
参数说明
duration
扫描持续时间,单位为秒。
esp_ble_gap_stop_scanning()
:用于停止扫描。参考 GAP API。
esp_ble_resolve_adv_data_by_type()
:用于获取指定数据类型的广播数据。参考 GAP API。
参数解释 传入参数
参数说明
adv_data
指向要解析的广播数据的指针。
adv_data_len
广播数据的长度。
type
要查找的广播数据类型。
length
返回对应类型的广播数据长度(不包括类型字段)。
esp_ble_gap_set_scan_params()
:用于设置扫描参数。参考 GAP API。
参数解释 传入参数
参数说明
scan_params
指向用户自定义的扫描数据结构的指针。