目录
- 1 ESP32 蓝牙架构学习
-
- 1.1 蓝牙
-
- 1.1.1 HCI 接口选择
- 1.1.2 蓝牙运行环境
- 1.1.3 框架
-
- 1.1.3.1 控制器
- 1.1.3.2 BLUEDROID
- 1.2 经典蓝牙
-
- 1.3 低功耗蓝牙
-
- 1.3.1 GAP(Generic Access Profile)
-
- 1.3.1.1 BLE角色转换图
- 1.3.1.2 BLE广播流程
- 1.3.1.3 BLE扫描流程
- 1.3.2 GATT(Generic Attribute Profile)
-
- 1.3.2.1 ATT(Attribute Protocol)
- 1.3.2.2 GTAA 规范
- 1.3.2.3 基于 ESP32 IDF 建立 GATT 服务(GATT 服务器)
- 1.3.2.4 基于 ESP32 IDF 发现对方设备的服务信息(GATT 客户端)
1 ESP32 蓝牙架构学习
1.1 蓝牙
1.1.1 HCI 接口选择
Host与Bluetooth Controller连接的HCI(主机控制接口, Host Controller Interface), 在ESP32中只能同时使用以下IO接口中的一个:
在ESP-IDF中,可以在 menuconfig 中配置蓝牙的 HCI IO接口方式。
1.1.2 蓝牙运行环境
ESP-IDF 的默认运行环境为双核 FreeRTOS, ESP32 的蓝牙可按照功能分为多个任务(task)运行,不同任务的优先级也有不同,其中优先级最⾼的为运⾏控制器的任务。控制器任务对实时性要求高,在FreeRTOS系统中的优先级仅次于IPC任务(IPC任务用于CPU的进程间通信)。BLUEDROID(ESP-IDF默认的蓝牙主机)共包含4个任务:
- BTC
- BTU
- HCI UPWARD
- HCI DOWNWARD
1.1.3 框架
1.1.3.1 控制器
ESP32 同时支持 Classic BT 和 BLE,蓝牙版本4.2 。集成了H4 协议、 HCI、 Link Manager、 Link Controller、 Device Manager、 HW Interface 等功能。这些功能以库的形式提供给开发者。
1.1.3.2 BLUEDROID
BLUEDROID蓝牙主机内部分为两层:
- BTU层
- 功能:负责蓝⽛主机底层协议栈的处理,包括 L2CAP、 GATT/ATT、 SMP、 GAP 以及部分规范等。
- BTC层(除去HCI)
- 功能:负责向应用层提供接口支持、处理基于 GATT 的规范、处理杂项等 。
所有的API都在ESP_API层,开发者应当使用“esp”为前缀的蓝牙API。
1.2 经典蓝牙
ESP-IDF 中的蓝⽛主机协议栈源于 BLUEDROID,后经过改良以配合嵌入式系统的应⽤。
在底层中,蓝牙主机协议栈通过虚拟 HCI 接⼝,与蓝牙双模控制器进行通信;
在上层中,蓝牙主机协议栈将为用户应用程序提供用于协议栈管理和规范的 API。
主机协议栈支持的经典蓝牙协议和规范:
- 规范: GAP、A2DP (SNK)、AVRCP (CT)
- 协议: L2CAP、SDP、AVDTP、AVCTP
协议模型:
- L2CAP:L2CAP,Logical Link Control and Adaptation Protocol,即逻辑链路控制和适配协议。
- SDP:Service Discovery Protocol,服务发现协议
- 功能:允许应用程序发现其他对等蓝牙设备提供的服务,并确定可用服务的特征。
- GAP:Generic Access Profile,通⽤用访问规范
- 功能:提供有关设备可发现性、可连接性和安全性的模式和过程描述。
- A2DP(Advanced Audio Distribution Profile,高级音频分发规范) 和 AVRCP(Audio/Video Remote Control Profile,⾳音频/视频远程控制规范)
- 实现高质量单声道或立体声⾳音频内容传输的协议和过程。
- SPP (Serial Port Profile,串口协议)
1.2.1 代码实现
有关经典蓝牙的例程路径为:examples\bluetooth\bluedroid\classic_bt。
参考 bt_spp_acceptor demo,作为 acceptor 通过 spp 协议进行串口环回通信。
调用api的流程为:
- 1 esp_bt_controller_mem_release
功能:按模式释放controller内存
注意:esp_bt_controller_mem_release() 只能在esp_bt_controller_init() 之前或者 esp_bt_controller_deinit() 之后调用。
初始化时调用了esp_bt_controller_mem_release()之后,使用esp_bt_controller_enable()来使用BLE才是安全的。
- 2 esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
功能:获得控制器配置选项。通过配置掩码启用了某些功能的一些选项或参数。
- 3 esp_err_t esp_bt_controller_init(esp_bt_controller_config_t *cfg)
功能: 初始化 BT controller 以分配 task 和其他 资源。
注意:这个函数需要在调用任何 BT 函数之前调用一次。
- 4 esp_err_t esp_bt_controller_enable(esp_bt_mode_t mode);
功能:使能 BT controller
注意:调用之后不能再次调用。如果需要动态改变 controller 的模式,则需要调用 esp_bt_controller_disable() 之后重新调用此函数。
- 5 esp_err_t esp_bluedroid_init(void);
功能:初始化和分配蓝牙资源
注意:必须优先于每一个bluetooth stuff。
- 6 esp_err_t esp_bluedroid_enable(void);
功能:启用蓝牙。
注意:必须在esp_bluedroid_init()之后。
- 7 esp_err_t esp_bt_gap_register_callback(esp_bt_gap_cb_t callback);
功能:注册gap回调函数
注意:此函数应在 esp_bluedroid_enable() 成功完成后调用
回调函数中对 BT GAP 回调事件进行判断并处理,如果收到认证完成事件(ESP_BT_GAP_AUTH_CMPL_EVT),则输出认证成功的消息。如果收到传统配对的PIN请求的事件(ESP_BT_GAP_PIN_REQ_EVT),先判断PIN的位数,如果是16位则设置PIN为 0000 0000 0000 0000 ,如果是4位则设置PIN为 1234 。
- 8 esp_err_t esp_spp_register_callback(esp_spp_cb_t callback);
功能:注册spp模块初始化回调函数
- 9 esp_err_t esp_spp_init(esp_spp_mode_t mode);
功能:初始化 spp 模块
- 10 esp_err_t esp_bt_gap_set_pin(esp_bt_pin_type_t pin_type, uint8_t pin_code_len, esp_bt_pin_code_t pin_code);
功能:为传统配对(Legacy Pairing)设置默认参数。
主文件代码:
编译的过程如果报错:fatal error:esp_bt.h: no such file or directory
解决:需要在menuconfig工具中配置SPP环境,配置好的环境设置保存在工程路径下的 sdkconfig
文件中。
- 设置
component config
-> Bluetooth
为打开
- 设置
component config
-> Bluetooth
-> Bluetooth contriller
-> Bluetooth contorller mode (BR/EDR/BLE/DUALMODE)
选择为 BR/EDR Only
- 设置
component config
-> Bluetooth
-> Bluedroid Options
为 选中 Classic Bluetooth
下的SPP
协议。
修改宏定义为显示数据:
line 32: #define SPP_SHOW_MODE SPP_SHOW_DATA /*Choose show mode: show data or speed*/
测试结果:
蓝牙串口通信测试完成。
1.3 低功耗蓝牙
1.3.1 GAP(Generic Access Profile)
Generic Access Profile,通⽤用访问规范。
功能:定义了了 BLE 设备的发现流程,设备管理和设备连接的建立。
BLE GAP采用API调用和事件(Event)返回的设计模式,通过事件返回来获取 API 在协议栈的处理结果。当对端设备主动发起请求时,也是通过事件返回获取对端设备的状态。
BLE设备定义四类 GAP 角色:
- 广播者(Broadcaster):处于这种角色的设备通过发送广播 (Advertising) 让接收者发现自己。这种角色只能发广播,不能被连接。
- 观察者(Observer):处于这种角色的设备通过接收广播事件并发送扫描(scan)请求。这种角色只能发送扫描请求,不能被连接。
- 外围设备(Peripheral): 当广播者接受了观察者发来的请求后就就会进入这种角色。当设备进入这种角色后,将会作为从设备(slave)在链路中进行通信。
- 中央设备(Central): 当观察者主动进行初始化,并建立一个物理链路时就会进入这种角色。这种角色在链路中被称为主设备(master)
1.3.1.1 BLE角色转换图
1.3.1.2 BLE广播流程
方式:
- 使用public地址进行广播
- 使用可解析地址进行广播
- 使用静态随机地址进行广播
类型:
- 可连接可扫描非定向广播 (Connectable scannable undirected event type)
- 高占空比定向广播 (High duty cycle directed event type)
- 可扫描非定向广播 (Scannable undirected event type)
- 不可连接非定向广播 (Non-connectable undirected event type)
- 可连接低占空比定向广播 (Connectable low duty cycle directed event type)
1.3.1.3 BLE扫描流程
在 ESP32 中,扫描设备主要是通过调用 esp_ble_gap_set_scan_params 来设置扫描时的参数,然后调用 esp_ble_gap_start_scanning 开始扫描。扫描到的设备将会通过ESP_GAP_BLE_SCAN_RESULT_EVT 事件返回,最后当 duration 超时,会通过ESP_GAP_SEARCH_INQ_CMPL_EVT 事件返回。
1.3.2 GATT(Generic Attribute Profile)
1.3.2.1 ATT(Attribute Protocol)
BLE 中的数据以属性(Attribute)方式存在,每条属性由四个元素组成:
- 属性句句柄 (Attribute Handle) :类似于地址。
- 属性类型 (Attribute UUID) : 每个数据有⾃自⼰己需要代表的意思,用标识码 (UUID)区分。
- 属性值 (Attribute Value):属性值是每个属性真正要承载的信息。
- 属性许可 (Attribute Permissions) :每个属性对各自的属性值有相应的访问限制。
服务器(server): 存有数据(即属性)的设备。
客户端(client): 获取别人设备数据的设备。
常用操作:
- 客户端给服务端发数据:通过 写入请求 (Write Request)需要对方回复响应(Write Response)。写入命令 (Write
Command)不需要。
- 服务端给客户端发数据:通过 服务端指示 (Indication) 、通知 (Notification)
- 客户端也可以主动通过读操作读取服务端的数据
服务器和客户端之间的交互操作都是通过上述的消息 ATT PDU 实现的。每个设备可以指定⾃自己设备支持的最大 ATT 消息长度,我们称之为 MTU。 ESP32 IDF 里面规定 MTU 可以设置的范围是 23~517 字节,对属性值的总长度没有做限制。
1.3.2.2 GTAA 规范
ATT 属性协议规定了在 BLE 中的最小数据存储单位,而 GATT 规范则定义了:
- 如何⽤用特性值和描述符表示一个数据
- 如何把相似的数据聚合成服务 (Service)
- 以及如何发现对端设备拥有哪些服务和数据
- GATT 规范引进了了特性值的概念,增加数据额外的信息。
为了包含额外的这些信息,每个属性中均需要安排一大段数据空间,存储这些额外信息。然而,一个数据很有可能用不不到绝大部分的额外信息,因此这种设计并这不符合 BLE “协议尽可能精简”的要求。在此背景下, GATT 规范引进了描述符的概念,每种描述符可以表达一种意思,用户可使用描述符,描述数据的额外信息。必需说明的是,每个数据和描述符并非一一对应,即一个复杂的数据可以拥有多个描述符,而一个简单的数据可以没有任何描述符。
BLE 协议中会把一些常⽤用的功能定义成一个个的服务 (Service)*,每个服务包含若干个特性,每个特性包含若干个描述符。
1.3.2.3 基于 ESP32 IDF 建立 GATT 服务(GATT 服务器)
通过属性表 (Attribute Table)* 添加服务和特性的功能。用户只需将要添加的服务和特性逐一填入一个表格,然后调用
esp_ble_gatts_create_attr_tab 函数,即可添加对应的服务和特性。
1.3.2.4 基于 ESP32 IDF 发现对方设备的服务信息(GATT 客户端)
- 首先发现对方所有的 Service 信息,包括 Service 的 UUID 和 Handle 范围。
- 然后在 GATT 的 Handle 范围内 ,继续查找所有的特性。
- 然后在 GATT 的 Handle 范围内 ,继续查找特性后的所有的描述符。