通用步骤

[English]

本文档总结了 ESP-IDF 中 BLE 的公共实现流程,涵盖关键的系统与蓝牙初始化、GAP 与 GATT 配置、服务与特征注册、广播与扫描、连接管理及事件处理等通用操作。文档列出各阶段的基础知识、涉及的 API 及调用顺序,为不同 BLE 示例提供统一的实现参考。文档内容仅基于 Bluedroid 实现,不涉及 NimBLE。

掌握该文档内容有助于快速理解 BLE 的核心流程,在不同应用中复用公共代码,并结合示例文档高效定位模式差异与特有实现。

广播与扫描

广播与扫描参数

BLE 设备发现与可见性由广播参数和扫描参数控制,它们分别用于 GATT Server 广播和 GATT Client 扫描。合理配置这些参数可以平衡设备被发现的速度、连接响应和功耗消耗。

  1. 广播参数:广播参数是 BLE Server 在广播过程中用来控制广播行为的一组设置,主要包括:

  • 发送间隔:控制广播包发送频率,影响设备被扫描或连接的响应速度及功耗。

  • 广播行为:决定设备是否可被连接或扫描。

  • 广播地址:本地设备在广播时使用的地址类型(公有地址或随机地址),以及目标设备的地址。

  • 广播信道:选择用于发送广播包的物理信道(37、38、39)。

  • 过滤策略:控制哪些设备可以扫描或连接本设备。

在 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

    枚举结束值,用于广告过滤策略的值检查

  1. 扫描参数:扫描参数是 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 进行配置。

  1. 使用原始广播数据包:使用原始广播数据包时,需要理解每个字段的概念(长度、类型、内容)并按协议格式排列,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

  1. 通过结构体配置:通过结构体配置时,用户只需关注“放什么内容”,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) 操作,用于向服务器写入较长的数据或进行分包写入,以保证写入事务的一致性和可靠性。

在准备写操作中:

  • 客户端

    1. 将要写入的数据分成多个片段,依次发送 Prepare Write Request,每个片段包含特征句柄、数据片段以及偏移量(表示该片段在整个数据中的位置)。

    2. 在发送完所有片段后,发起 Execute Write Request,通知服务器将之前所有缓存的分片数据一次性写入特征值,从而完成整个写入事务。

  • 服务器

    1. 将接收到的片段缓存到临时缓冲区,并返回 Prepare Write Response,确认该片段已接收。

    2. 在收到客户端的 Execute Write Request 后,将所有缓存数据统一写入特征值,实现完整写入。

因此,在实际编程中,若设备需要作为服务器接收分片数据时,需要为“准备写”创建专用缓冲区,用于存储接收到的分片数据,以便在收到 Execute Write Request 后统一写入特征值。

管理 GATT Profile 信息和状态

GATT Profile 实例结构体用于集中管理 GATT 层信息,包括回调函数、接口 ID、服务、特征和描述符等数据,对于 Server 和 Client 示例均适用;其中,Server 关注本地属性和访问权限,Client 关注远端服务的发现和操作,以保证多 Profile、多连接场景下的正确性和高效访问。

  1. GATT Server Profile 实例结构体

Server 示例中的 gatts_profile_inst 结构体主要用于管理本地服务、特征和描述符的信息,以及回调函数和连接状态。其结构体成员如下:

gatts_profile_inst

GATT 层级

结构体成员

说明

Profile

gatts_cb

GATT Server 回调函数,用于绑定 Profile 与事件处理逻辑,方便处理该 Profile 下的所有 客户端请求事件

gatts_if

GATT 接口 ID,用于标识该 Profile,区分不同 Profile 或 GATT 接口。

app_id

应用 ID,用于标识该 Profile 所属应用,在注册多个 Profile 时区分不同实例。

conn_id

连接 ID,以区分不同客户端连接。

Service

service_handle

服务句柄,用于后续操作访问该服务。

service_id

服务标识,包括服务 UUID 和主/次服务信息。

Characteristic

char_handle

特征句柄,用于后续操作访问该特征值。

char_uuid

特征 UUID,便于客户端识别特征。

perm

特征权限,如可读、可写等,决定客户端对特征的访问方式。

property

特征属性,由程序指定特征支持的操作,如读、写、通知、指示。

Descriptor

descr_handle

描述符句柄,用于后续操作访问该描述符。

descr_uuid

描述符 UUID,常用于标识 CCCD 等描述符。

  1. GATT Client Profile 实例结构体

Client 示例中的 gattc_profile_inst 结构体主要用于管理对远端设备服务、特征和描述符的操作,以及连接状态和回调逻辑。其主要成员如下:

gattc_profile_inst

GATT 层级

结构体成员

说明

Profile

gatts_cb

GATT Server 回调函数,绑定 Profile 与事件处理逻辑,用于处理该 Profile 下的所有 客户端请求事件

gatts_if

GATT 接口 ID,标识该 Profile,用于区分不同 Profile 或 GATT 接口。

app_id

应用 ID,用于标识该 Profile 所属应用,在注册多个 Profile 时区分不同实例。

conn_id

连接 ID,以区分不同客户端连接。

remote_bda

远端设备蓝牙地址,用于标识连接目标。

Service

service_start_handle

远端服务起始句柄,用于在后续操作中标识服务的起始位置。

service_end_handle

远端服务结束句柄,用于在后续操作中标识服务的结束位置。

Characteristic

char_handle

特征句柄,用于后续操作访问该特征值。

结构体中的大部分成员都是在运行时通过协议栈生成或分配的,注册服务或特征时由协议栈返回。在定义结构体时,只需预先声明这些成员,初始化可使用默认值或 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 中属性表定义步骤如下:

  1. 定义属性索引:使用枚举定义所有属性条目及属性总数,后续可通过索引访问对应属性的 Handle,避免在代码中使用魔法数字。

备注

“魔法数字”通常指在代码中直接写出的没有解释含义的数字常量。

  1. 创建属性数组:定义一个 esp_gatts_attr_db_t 类型的数组,数组长度等于属性总数,每个数组元素对应一个属性条目。

  2. 排列属性顺序:数组中的每个元素代表一个属性。典型的属性顺序是:Service 声明 >(特征 A:声明 > 值 > 描述符)>(特征 B:声明 > 值 > 描述符),每个条目的 Handle 由栈统一创建并返回。

  3. 配置属性字段:每个属性条目需要配置的字段如下:

  • 属性响应控制字段:指定属性在被客户端进行读写操作时由谁负责响应。数据类型为 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 事件。传参形式如下:

参数解释

传入参数

参数功能

参数说明

event

事件类型。

可根据事件类型在回调中进行不同处理。

gatts_if

GATT 服务器访问接口。

由协议栈分配,用于区分不同的 GATT Profile 服务实例,在事件回调中识别对应的 Profile。

param

指向事件参数的结构体指针,包含该事件的详细信息。

不同事件对应不同的联合体成员,例如广播数据、扫描结果、连接信息等。

函数功能可以分为两个阶段:

  1. 注册事件处理:

  • 当事件类型为注册完成时,检查注册是否成功。

  • 如果注册成功,将协议栈分配的接口标识符 gatts_if 存入 Profile 表中,供后续事件识别使用;如果注册失败,则打印错误日志并终止分发。

  1. 事件分发:

  • 如果传入的接口标识符 gatts_if 未指定接口(ESP_GATT_IF_NONE),则事件会广播给所有 Profile,让每个 Profile 自行判断是否处理。

  • 如果接口标识符已指定,则事件仅分发给与之匹配的 Profile,避免无关 Profile 接收。

  • 分发的事件涵盖了 GATT 协议层的完整交互:

    • Server 侧处理服务注册、读写请求、通知发送等。

    • Client 侧处理服务发现、特征访问、订阅通知等。

  • 每个 Profile 回调函数通过 gatts_ifevent 判断是否响应该事件,实现对多个 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)

  1. 初始化非易失性存储

  • 非易失性存储是设备中用于存储持久化数据的关键模块,断电后数据依然保留。

  • 调用 nvs_flash_init() 初始化 NVS,完成对存储介质的准备和挂载。相关参数说明请参考 非易失性存储库 API

  1. 检查返回值

  • 及时发现初始化过程中可能出现的问题,比如 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() 初始化蓝牙控制器前释放,否则内存分区将被固定,无法释放。

创建/初始化蓝牙控制器

  • 通过 esp_bt_controller_config_t 结构体配置蓝牙控制器参数,通常使用 BT_CONTROLLER_INIT_CONFIG_DEFAULT() 快速加载默认配置。

  • 配置内容涵盖任务调度、内存缓冲、低功耗模式、射频参数和协议安全特性等,保证蓝牙控制器初始化时的性能、兼容性和稳定性。具体结构体成员可参考 控制器 API

  • 初始化 蓝牙控制器,启动内部任务和资源。

启动蓝牙控制器

  • 启动 已初始化的蓝牙控制器,并设置其模式。

初始化/启用协议栈

  • 初始化 Bluedroid 协议栈。

  • 启用 Bluedroid 协议栈,使其进入工作状态。

注册回调函数

  • 注册 回调函数用于处理不同事件。

  • 传入对应函数注册 GATT Server 回调函数,用于处理读写、连接、断开等事件。

  • 传入对应函数注册 GAP 回调函数,用于处理广播和扫描事件。

注册 GATT 应用

  • 注册 GATT 应用,将用户定义的服务逻辑接入协议栈,并获取事件回调通道。

  • 注册完成后协议栈会触发 ESP_GATTS_REG_EVT 事件。

设置本地 GATT MTU(最大传输单元)

  • 设置 本地 GATT MTU 的大小。

  • 注册完成后会触发 ESP_GATTS_MTU_EVT 事件。

备注

MTU 决定一次能传输的最大数据量,默认 23 字节,可根据需要进行调整。

API 汇总

蓝牙控制器初始化与使能

  1. esp_bt_controller_init():用于初始化蓝牙控制器以分配任务和其他资源。参考 控制器 API

参数解释

传入参数

参数说明

cfg

初始蓝牙控制器配置。

  1. esp_bt_controller_enable():用于启用蓝牙控制器。参考 控制器 API

参数解释

传入参数

参数说明

mode

要启用的蓝牙控制器模式。

  1. esp_bluedroid_init():用于初始化并分配蓝牙所需资源。参考 Bluetooth API

  1. esp_bluedroid_enable():用于启用蓝牙。参考 Bluetooth API

  1. esp_bt_controller_mem_release:用于释放指定模式的控制器内存。参考 控制器 API

参数解释

传入参数

参数说明

mode

要释放内存的蓝牙控制器模式。

GATT 服务注册与操作

  1. esp_ble_gatts_register_callback():用于注册 GATT 服务器应用回调函数。参考 GATT 服务器 API

参数解释

传入参数

参数说明

callback

指向应用回调函数的指针。

  1. esp_ble_gatts_app_register():用于注册 GATT 服务器应用。参考 GATT 服务器 API

参数解释

传入参数

参数说明

app_id

不同应用的 UUID。

  1. esp_ble_gatts_create_attr_tab():用于创建属性表。参考 GATT 服务器 API

参数解释

传入参数

参数说明

gatts_attr_db

指向服务属性表的指针。

gatts_if

GATT 服务器访问接口

max_nb_attr

要添加到服务数据库的属性数量。

srvc_inst_id

服务实例 ID。

  1. esp_ble_gatts_start_service():用于启动服务。参考 GATT 服务器 API

参数解释

传入参数

参数说明

service_handle

要启动的目标服务句柄。

  1. esp_ble_gatts_send_response():用于向客户端发送写入响应。参考 GATT 服务器 API

参数解释

传入参数

参数说明

gatts_if

GATT 服务器访问接口。

conn_id

连接 ID。

trans_id

传输 ID。

status

响应状态。

rsp

指向响应数据的指针。

  1. esp_ble_gatt_set_local_mtu():用于设置本地 MTU 的大小,需在建立 BLE 连接之前调用。

参数解释

传入参数

参数说明

mtu

期望的 MTU 的大小。

  1. esp_ble_gattc_enh_open():用于创建 ACL 连接。参考 GATT Client API

参数解释

传入参数

参数说明

gattc_if

GATT 客户端访问接口。

esp_gatt_create_conn

指向包含连接参数的结构体的指针。

备注

ACL(异步无连接)连接是蓝牙中一种基础的数据链路连接类型,用于在两个设备之间传输数据。

  1. esp_ble_gattc_send_mtu_req():用于配置 GATT 通道的 MTU 大小。参考 GATT Client API

参数解释

传入参数

参数说明

gattc_if

GATT 客户端访问接口。

conn_id

连接 ID。

  1. esp_ble_gattc_search_service():用于从本地 GATTC 缓存中搜索服务。参考 GATT Client API

参数解释

传入参数

参数说明

gattc_if

GATT 客户端访问接口。

conn_id

连接 ID。

filter_uuid

目标服务的 UUID。如果传入 NULL,则返回所有服务。

  1. 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 缓存中找到的指定属性类型的属性数量。

  1. 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 缓存中找到的指定属性类型的属性数量。

  1. esp_ble_gattc_register_for_notify():注册以接收特征的通知或指示。参考 GATT Client API

参数解释

传入参数

参数说明

gattc_if

GATT 客户端访问接口。

server_bda

目标 GATT 服务器设备地址。

handle

目标 GATT 特征句柄。

  1. 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 缓存中找到的指定属性类型的属性数量。

  1. esp_ble_gattc_write_char_descr():用于写入指定描述符句柄的特征描述符值。参考 GATT Client API

参数解释

传入参数

参数说明

gattc_if

GATT 客户端访问接口。

conn_id

连接 ID。

handle

要写入的描述符句柄。

value_len

写入值的字节长度。

value

要写入的值。

write_type

属性写操作类型。

auth_req

认证请求类型。

  1. esp_ble_gattc_write_char():用于写入指定特征句柄的特征值。参考 GATT Client API

参数解释

传入参数

参数说明

gattc_if

GATT 客户端访问接口。

conn_id

连接 ID。

handle

要写入的描述符句柄。

value_len

写入值的字节长度。

value

要写入的值。

write_type

属性写操作类型。

auth_req

认证请求类型。

GAP 广播与设备信息

  1. esp_ble_gap_set_device_name():用于设置本地蓝牙设备的名称。参考 GAP API

参数解释

传入参数

参数说明

name

设备名称。

  1. esp_ble_gap_config_adv_data_raw():用于设置原始广播数据。参考 GAP API

  2. esp_ble_gap_config_scan_rsp_data_raw():用于设置原始扫描响应数据。参考 GAP API

参数解释

传入参数

参数说明

raw_data

原始扫描响应数据。

raw_data_len

原始扫描响应数据长度。

  1. esp_ble_gap_config_adv_data():用于覆盖 BTA 默认的广播参数。参考 GAP API

参数解释

传入参数

参数说明

adv_data

指向用户自定义广播数据结构的指针。

  1. esp_ble_gap_register_callback():用于触发 GAP 事件,例如扫描结果。参考 GAP API

参数解释

传入参数

参数说明

callback

指向应用回调函数的指针。

  1. esp_ble_gap_update_conn_params:用于更新连接参数,仅在连接已建立时可用。参考 GAP API

参数解释

传入参数

参数说明

params

连接更新参数。

  1. esp_ble_gap_start_advertising():用于启动广播。参考 GAP API

参数解释

传入参数

参数说明

adv_params

指向用户自定义的广播数据结构的指针。

  1. esp_ble_gap_start_scanning():持续扫描周围的广播设备。用于参考 GAP API

参数解释

传入参数

参数说明

duration

扫描持续时间,单位为秒。

  1. esp_ble_gap_stop_scanning():用于停止扫描。参考 GAP API

  1. esp_ble_resolve_adv_data_by_type():用于获取指定数据类型的广播数据。参考 GAP API

参数解释

传入参数

参数说明

adv_data

指向要解析的广播数据的指针。

adv_data_len

广播数据的长度。

type

要查找的广播数据类型。

length

返回对应类型的广播数据长度(不包括类型字段)。

  1. esp_ble_gap_set_scan_params():用于设置扫描参数。参考 GAP API

参数解释

传入参数

参数说明

scan_params

指向用户自定义的扫描数据结构的指针。