ESP32_H2(IDF)学习系列-蓝牙基础学习(上)
一、简介
参考:[乐鑫-蓝牙]
Bluetooth LE 的分层架构
Bluetooth LE 协议定义了三层软件结构,自上而下分别是
应用层 (Application Layer) 应用层(Application Layer)
主机层 (Host Layer)
控制器层 (Controller Layer)
应用层即以 Bluetooth LE 为底层通信技术所构建的应用,依赖于主机层向上提供的 API 接口。
主机层负责实现 L2CAP、GATT/ATT、SMP、GAP 等底层蓝牙协议,向上对应用层提供 API 接口,向下通过主机控制器接口 (Host Controller Interface, HCI) 与控制器层通信。
控制器层包括物理层 (Physical Layer, PHY) 和链路层 (Link Layer, LL) 两层,向下直接与控制器硬件进行交互,向上通过 HCI 与主机层进行通信。
值得一提的是,蓝牙核心规范 (Core Specification) 允许主机层和控制器层在物理上分离,此时 HCI 体现为物理接口,包括 SDIO、USB 以及 UART 等;当然,主机层和控制器层可以共存于同一芯片,以实现更高的集成度,此时 HCI 体现为逻辑接口,常被称为虚拟主机控制器接口 (Virtual Host Controller Interface, VHCI)。一般认为,主机层和控制器层组成了 Bluetooth LE 协议栈 (Bluetooth LE Stack)。
下图展示了 Bluetooth LE 的分层结构。
Bluetooth LE 分层结构
Bluetooth LE 分层结构
作为应用开发者,在开发过程中我们主要与主机层提供的 API 接口打交道,这要求我们对主机层中的蓝牙协议有一定的了解。接下来,我们会从连接和数据交换两个角度,对 GAP 和 GATT/ATT 层的基本概念进行介绍。
GAP 层 - 定义设备的连接
GAP 层的全称为通用访问规范 (Generic Access Profile, GAP),定义了 Bluetooth LE 设备之间的连接行为以及设备在连接中所扮演的角色。
GAP 状态与角色
GAP 中共定义了三种设备的连接状态以及五种不同的设备角色,如下
空闲 (Idle)
此时设备无角色,处于就绪状态 (Standby)
设备发现 (Device Discovery)
广播者 (Advertiser)
扫描者 (Scanner)
连接发起者 (Initiator)
连接 (Connection) 连接(Connection)
外围设备 (Peripheral)
中央设备 (Central)
广播者向外广播的数据中包含设备地址等信息,用于向外界设备表明广播者的存在,并告知其他设备是否可以连接。扫描者则持续接收环境中的广播数据包。若某一个扫描者发现了一个可连接的广播者,并希望与之建立连接,可以将角色切换为连接发起者。当连接发起者再次收到该广播者的广播数据,会立即发起连接请求 (Connection Request);在广播者未开启白名单 (Filter Accept List, 又称 White List) 或连接发起者在广播者的白名单之中时,连接将被成功建立。
进入连接以后,原广播者转变为外围设备(旧称从设备 Slave ),原扫描者或连接初始化者转变为中央设备(旧称主设备 Master )。
GAP 角色之间的转换关系如下图所示
Bluetooth LE 网络拓扑
Bluetooth LE 设备可以同时与多个 Bluetooth LE 设备建立连接,扮演多个外围设备或中央设备角色,或同时作为外围设备和中央设备。以 Bluetooth LE 网关为例,这种设备可以作为中央设备,与智能开关等外围设备连接,同时作为外围设备,与形如手机等中央设备连接,实现数据中转。
在一个 Bluetooth LE 网络中,若所有设备都在至少一个连接中,且仅扮演一种类型的角色,则称这种网络为连接拓扑 (Connected Topology);若存在至少一个设备同时扮演外围设备和中央设备,则称这种网络为多角色拓扑 (Multi-role Topology)。
Bluetooth LE 同时也支持无连接的网络拓扑,即广播拓扑 (Broadcast Topology)。在这种网络中,存在两种角色,其中发送数据的被称为广播者 (Broadcaster),接收数据的被称为观察者 (Observer)。广播者只广播数据,不接受连接;观察者仅接受广播数据,不发起连接。例如,某个智能传感器的数据可能在一个网络中被多个设备共用,此时维护多个连接的成本相对较高,直接向网络中的所有设备广播传感器数据更加合适。
了解更多
如果你想了解更多设备发现与连接的相关信息,请参考 设备发现 与 连接 。
GATT/ATT 层 - 数据表示与交换
GATT/ATT 层定义了进入连接状态后,设备之间的数据交换方式,包括数据的表示与交换过程。
ATT 层 ATT 层间
ATT 的全称是属性协议 (Attribute Protocol, ATT),定义了一种称为属性 (Attribute) 的基本数据结构,以及基于服务器/客户端架构的数据访问方式。
简单来说,数据以属性的形式存储在服务器上,等待客户端的访问。以智能开关为例,开关量作为数据,以属性的形式存储在智能开关内的蓝牙芯片(服务器)中,此时用户可以通过手机(客户端)访问智能开关蓝牙芯片(服务器)上存放的开关量属性,获取当前的开关状态(读访问),或控制开关的闭合与断开(写访问)。
属性这一数据结构一般由以下三部分构成
句柄 (Handle)
类型 (Type)
值 (Value) 价值(Value)
访问权限 (Permissions)
在协议栈实现中,属性一般被放在称为属性表 (Attribute Table) 的结构体数组中管理。一个属性在这张表中的索引,就是属性的句柄,常为一无符号整型。
属性的类型由 UUID 表示,可以分为 16 位、32 位与 128 位 UUID 三类。 16 位 UUID 由蓝牙技术联盟 (Bluetooth Special Interest Group, Bluetooth SIG) 统一定义,可以在其公开发布的 Assigned Numbers 文件中查询;其他两种长度的 UUID 用于表示厂商自定义的属性类型,其中 128 位 UUID 较为常用。
GATT 层 GATT 层间协议
GATT 的全称是通用属性规范 (Generic Attribute Profile),在 ATT 的基础上,定义了以下三个概念
特征数据 (Characteristic)
服务 (Service)
规范 (Profile)
这三个概念之间的层次关系如下图所示
GATT 中的层次关系
GATT 中的层次关系
特征数据和服务都是以属性为基本数据结构的复合数据结构。一个特征数据往往由两个以上的属性描述,包括
特征数据声明属性 (Characteristic Declaration Attribute)
特征数据声明属性(Characteristic Declaration Attribute)
特征数据值属性 (Characteristic Value Attribute)
特征数据值属性(Characteristic Value Attribute)
除此以外,特征数据中还可能包含若干可选的描述符属性 (Characteristic Descriptor Attribute)。
一个服务本身也由一个属性进行描述,称为服务声明属性 (Service Declaration Attribute)。一个服务中可以存在一个或多个特征数据,它们之间体现为从属关系。另外,一个服务可以通过 Include 机制引用另一个服务,复用其特性定义,避免如设备名称、制造商信息等相同特性的重复定义。
规范是一个预定义的服务集合,实现了某规范中所定义的所有服务的设备即满足该规范。例如 Heart Rate Profile 规范由 Heart Rate Service 和 Device Information Service 两个服务组成,那么可以称实现了 Heart Rate Service 和 Device Information Service 服务的设备符合 Heart Rate Profile 规范。
广义上,我们可以称所有存储并管理特征数据的设备为 GATT 服务器,称所有访问 GATT 服务器以访问特征数据的设备为 GATT 客户端。
二、参考例程解析学习
参考官方API解析:
乐鑫-蓝牙API
一个GATT 服务器应用程序架构(由Application Profiles组织起来)如下:
参考该播主:
程序解析
部分重点解析
GATT回调函数
esp_gatts_cb_event_t结构体
GAP回调函数
esp_gap_cb_event_t
uuid相关介绍
参考博客:UUID
BLE 服务源文件是 ESP-AT 工程创建低功耗蓝牙服务所依据的文件,文件位于 ble_data/example.csv ,可以通过自定义该文件实现 BLE 服务的自定义。
默认的 BLE 服务源文件定义如下:
以下内容是对上表的说明:
uuid_len:UUID 的长度,上表中 uuid_len 值为 16,代表 16-bit UUID
uuid:表明属性类型,其中,0x2800 为主服务声明,0x2803 为特征声明,0x2901 为特征用户描述描述符,0x2902 为客户端特征配置描述符(CCCD),可以参考下表:
perm:权限,用于描述属性是否可读或者可写。
定义如下:
#define ESP_GATT_PERM_READ (1 << 0) /* bit 0 - 0x0001 */ /* relate to BTA_GATT_PERM_READ in bta/bta_gatt_api.h */
#define ESP_GATT_PERM_READ_ENCRYPTED (1 << 1) /* bit 1 - 0x0002 */ /* relate to BTA_GATT_PERM_READ_ENCRYPTED in bta/bta_gatt_api.h */
#define ESP_GATT_PERM_READ_ENC_MITM (1 << 2) /* bit 2 - 0x0004 */ /* relate to BTA_GATT_PERM_READ_ENC_MITM in bta/bta_gatt_api.h */
#define ESP_GATT_PERM_WRITE (1 << 4) /* bit 4 - 0x0010 */ /* relate to BTA_GATT_PERM_WRITE in bta/bta_gatt_api.h */
#define ESP_GATT_PERM_WRITE_ENCRYPTED (1 << 5) /* bit 5 - 0x0020 */ /* relate to BTA_GATT_PERM_WRITE_ENCRYPTED in bta/bta_gatt_api.h */
#define ESP_GATT_PERM_WRITE_ENC_MITM (1 << 6) /* bit 6 - 0x0040 */ /* relate to BTA_GATT_PERM_WRITE_ENC_MITM in bta/bta_gatt_api.h */
#define ESP_GATT_PERM_WRITE_SIGNED (1 << 7) /* bit 7 - 0x0080 */ /* relate to BTA_GATT_PERM_WRITE_SIGNED in bta/bta_gatt_api.h */
#define ESP_GATT_PERM_WRITE_SIGNED_MITM (1 << 8) /* bit 8 - 0x0100 */ /* relate to BTA_GATT_PERM_WRITE_SIGNED_MITM in bta/bta_gatt_api.h */
#define ESP_GATT_PERM_READ_AUTHORIZATION (1 << 9) /* bit 9 - 0x0200 */
#define ESP_GATT_PERM_WRITE_AUTHORIZATION (1 << 10) /* bit 10 - 0x0400 */
val_max_len:数值的最大长度。
val_cur_len:数值的当前长度。
value:对应的数值。
如果 UUID 为 0x2800,那么该行为服务声明,对应的值应该为自定义的 UUID,以 index 0 所在的行为例,对应的值为 A002。
如果 UUID 为 0x2803,那么该行为特征声明,对应的值为特征的属性(property),以 index 1 所在的行为例,对应的值为 02,代表该特征具有读属性。
属性的定义如下:
#define ESP_GATT_CHAR_PROP_BIT_BROADCAST (1 << 0) /* 0x01 */ /* relate to BTA_GATT_CHAR_PROP_BIT_BROADCAST in bta/bta_gatt_api.h */
#define ESP_GATT_CHAR_PROP_BIT_READ (1 << 1) /* 0x02 */ /* relate to BTA_GATT_CHAR_PROP_BIT_READ in bta/bta_gatt_api.h */
#define ESP_GATT_CHAR_PROP_BIT_WRITE_NR (1 << 2) /* 0x04 */ /* relate to BTA_GATT_CHAR_PROP_BIT_WRITE_NR in bta/bta_gatt_api.h */
#define ESP_GATT_CHAR_PROP_BIT_WRITE (1 << 3) /* 0x08 */ /* relate to BTA_GATT_CHAR_PROP_BIT_WRITE in bta/bta_gatt_api.h */
#define ESP_GATT_CHAR_PROP_BIT_NOTIFY (1 << 4) /* 0x10 */ /* relate to BTA_GATT_CHAR_PROP_BIT_NOTIFY in bta/bta_gatt_api.h */
#define ESP_GATT_CHAR_PROP_BIT_INDICATE (1 << 5) /* 0x20 */ /* relate to BTA_GATT_CHAR_PROP_BIT_INDICATE in bta/bta_gatt_api.h */
#define ESP_GATT_CHAR_PROP_BIT_AUTH (1 << 6) /* 0x40 */ /* relate to BTA_GATT_CHAR_PROP_BIT_AUTH in bta/bta_gatt_api.h */
#define ESP_GATT_CHAR_PROP_BIT_EXT_PROP (1 << 7) /* 0x80 */ /* relate to BTA_GATT_CHAR_PROP_BIT_EXT_PROP in bta/bta_gatt_api.h */
特征声明接下来的一行为特征值定义,以 index 2 所在的行为例,0xC300为自定义特征的 UUID ,对应的数值长度只有一个字节,对应数值为 30。
0x2902为客户端配置描述符(CCCD),可以看到,特征 0xC305(index 16) 具有 CCCD,其值占用两个字节,初始值为 0000,表示通知 (notification) 和指示 (indication) 被禁用。
上表中,自定义了两个 BLE 服务,分别为 A002 和 A003。
其中,服务 A002 有 8 个特征,服务 A003 有 2 个特征,简单总结见下表:
参考博客:
UUID
UUID的种类分为两种:
一种是 SIG 定义的公共服务 UUID,所有的公共服务共用一个 128bit 的基础 UUID,不同的服务采用一个 16bit UUID 进行定义。基础UUID:0x0000xxxx-0000-1000-8000-00805F9B34FB
另一种就是私有服务的 UUID,这是一个自定义的 128bit UUID。
注意:广播包里的 UUID 不影响服务特征值中 UUID 的值,仅仅是让广播把 UUID 的值广播给扫描设备,方便观察。
这里广播了两个 16bite UUID:0x00EE、0x00FF
static uint8_t adv_service_uuid128[32] = {/* LSB <--------------------------------------------------------------------------------> MSB *///first uuid, 16bit, [12],[13] is the value0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0xEE, 0x00, 0x00, 0x00,//second uuid, 32bit, [12], [13], [14], [15] is the value0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00,
};static esp_ble_adv_params_t ble_adv_params = {.adv_int_min = 0x20,.adv_int_max = 0x40,.adv_type = ADV_TYPE_IND,.own_addr_type = BLE_ADDR_TYPE_PUBLIC,.channel_map = ADV_CHNL_ALL,.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
};static esp_ble_adv_data_t adv_data = {.service_uuid_len = sizeof(adv_service_uuid128),.p_service_uuid = adv_service_uuid128,.flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
};esp_ble_gap_config_adv_data(&adv_data); // 配置广播内容
esp_ble_gap_start_advertising(&ble_adv_params); // 开启广播
广播的从机连接间隔数:
从机与主机之间的连接间隔,是由从机提出与主机进行协商,然后再由主机决定的参数。
先设置 min_interval 和 max_interval 的值,然后调用 esp_ble_gap_config_adv_data(),再调用 esp_ble_gap_start_advertising()
static esp_ble_adv_params_t ble_adv_params = {.adv_int_min = 0x20,.adv_int_max = 0x40,.adv_type = ADV_TYPE_IND,.own_addr_type = BLE_ADDR_TYPE_PUBLIC,.channel_map = ADV_CHNL_ALL,.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
};static esp_ble_adv_data_t adv_data = {.min_interval = 0x000A, //slave connection min interval, Time = min_interval * 1.25 msec=7.5ms.max_interval = 0x0014, //slave connection max interval, Time = max_interval * 1.25 msec=20ms.flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
};esp_ble_gap_config_adv_data(&adv_data); // 配置广播内容
esp_ble_gap_start_advertising(&ble_adv_params); // 开启广播
广播自定义数据
p_manufacturer_data:制造商自定义的数据,这个参数可以自由的设置,只要广播包的空间足够。假设自定义数据为 0x11,0x22,0x33,0x44,0x55。
static uint8_t user_data[5] = {0x11, 0x22, 0x33, 0x44, 0x55};static esp_ble_adv_params_t ble_adv_params = {.adv_int_min = 0x20,.adv_int_max = 0x40,.adv_type = ADV_TYPE_IND,.own_addr_type = BLE_ADDR_TYPE_PUBLIC,.channel_map = ADV_CHNL_ALL,.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
};static esp_ble_adv_data_t adv_data = {.manufacturer_len = sizeof(user_data),.p_manufacturer_data = user_data,.flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
};esp_ble_gap_config_adv_data(&adv_data); // 配置广播内容
esp_ble_gap_start_advertising(&ble_adv_params); // 开启广播
原文链接:https://blog.csdn.net/qq_36347513/article/details/118495466
BLE广播类型
参考博客:BLE 广播
typedef enum {ADV_TYPE_IND = 0x00,ADV_TYPE_DIRECT_IND_HIGH = 0x01,ADV_TYPE_SCAN_IND = 0x02,ADV_TYPE_NONCONN_IND = 0x03,ADV_TYPE_DIRECT_IND_LOW = 0x04,
} esp_ble_adv_type_t;
下面是对这几种广播类型的说明:
ADV_TYPE_IND(可连接的非定向广播):
这是一种用途最广,最常见的广播类型,包括广播数据和扫描响应数据,它表示当前设备可以接受任何设备的连接请求。
进行通用广播的设备能够被扫描设备扫描到,或者在接收到连接请求时作为从设备进入一个连接。
ADV_TYPE_DIRECT_IND_HIGH ADV_TYPE_DIRECT_IND_LOW(可连接的定向广播):
定向广播类型是为了尽可能快地连接,俗称回连包,这种报文包含两个地址:广播者的地址和发起者的地址。发起者收到发给自己的定向广播报文之后,可以立即发送连接请求作为回应。
当使用定向广播时,设备不能被主动扫描。此外,定向广播报文的有效载荷中也不能带有其他附加数据。该有效载荷只能包含两个必须的地址。
ADV_TYPE_DIRECT_IND_HIGH 为可连接高占空比定向广播,在该模式下,发送在相同广播信道索引上的两个连续 ADV_DIRECT_IND PDU 之间的时间间隔应小于或等于 3.75 毫秒。
ADV_TYPE_DIRECT_IND_LOW 为可连接低占空比定向广播,在该模式下,两个连续的ADV_DIRECT_IND PDU 之间的时间间隔在一个广播事件内应小于或等于 10 毫秒。广播事件应在广播间隔内结束。
ADV_TYPE_SCAN_IND(可扫描的非定向广播):
又称可发现广播,这种广播不能用于发起连接,但允许其他设备扫描该广播设备。这意味着该设备可以被发现,既可以发送广播数据,也可以响应扫描发送扫描响应数据,但不能建立连接。这是一种适用于广播数据的广播形式,动态数据可以包含于广播数据之中,而静态数据可以包含于扫描响应数据之中。
ADV_TYPE_NONCONN_IND(不可连接的非定向广播):
仅仅发送广播数据,而不能被扫描或者连接。这也是唯一可用于只有发射机而没有接收机设备的广播类型。不可连接设备不会进入连接态,因此,它只能根据主机的要求在广播态和就绪态之间切换。常用于 Beacon 项目。
配置蓝牙的广播设备地址类型:
参考博客:蓝牙广播设备地址类型
1、这个是蓝牙的公共地址,它可以使用 自带的,也可以使用自己设置,这个地址设置好了之后就是一直不变的。
2、BLE_ADDR_TYPE_RANDOM 表示随机静态地址(Random Static Address),是一种固定的、不变的地址类型。虽然它也是随机生成的,但在设备生命周期内不会更改(每次电源重启后设备可选择将其静态设备地址初始化并赋予新的静态地址值。设备完成初始化后在下一次电源重启前不得再修改其静态地址值。备注: 设备的静态地址修改后存储其原来地址的对等设备将无法与其自动重连。)
3、
将 BLE 设备的地址类型设置为 “ble_addr_type_rpa_public”。
在设备启动时,生成一个随机数作为低 24 位,并将上 24 位设置为公共设备 ID。
将生成的地址广播出去,以供其他设备扫描和连接。
4、不可解析私有地址配置
不可解析私有地址随机部分和随机静态地址的随机部分是一样的,个人理解这两个地址的不同就在于,随机静态地址在电源重启之前都不能改变其地址。而不可解析私有地址可以在每次广播之前都可以改变自己的地址。