一、介绍
该代码实现了一个低功耗蓝牙 (BLE) 通用属性 (GATT) 客户端,该客户端扫描附近的外围服务器并连接到预定义的服务。然后,客户端搜索可用特征并订阅已知特征,以便接收通知或指示。该示例可以注册应用程序配置文件并初始化一系列事件,这些事件可用于配置通用访问配置文件 (GAP) 参数以及处理扫描、连接到外围设备以及读写特性等事件
二、include
gattc_demo.c 中包含的头文件是:
#include <stdint.h>
#include <string.h>
#include <stdbool.h>
#include <stdio.h>
#include "nvs.h"
#include "nvs_flash.h"
#include "controller.h"
这些是 FreeRTOS 和底层系统组件运行所必需的,包括日志记录功能和将数据存储在非易失性闪存中的库。
#include "bt.h"
#include "esp_gap_ble_api.h"
#include "esp_gattc_api.h"
#include "esp_gatt_defs.h"
#include "esp_bt_main.h"
#include "esp_gatt_common_api.h"
- bt.h:从主机侧配置 BT 控制器和 VHCI。
- esp_bt_main.h:初始化并启用 Bluedroid 堆栈。
- esp_gap_ble_api.h:实现 GAP 配置,如广告、连接参数等。
- esp_gattc_api.h:实现 GATT Client 配置,如连接外设、搜索服务等。
三、入口函数
main 函数首先初始化非易失性 storage library。该库允许在闪存中保存键值对,并被一些组件(如 Wi-Fi 库)用于保存 SSID 和密码:
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK( ret );
四、BT 控制器和堆栈初始化
- 首先创建一个 BT 控制器配置结构来初始化 BT 控制器,该结构使用宏生成的默认设置命名。BT 控制器在控制器端实现主机控制器接口 (HCI)、链路层 (LL) 和物理层 (PHY)。BT 控制器对用户应用程序不可见,并处理 BLE 堆栈的较低层。控制器配置包括设置 BT 控制器堆栈大小、优先级和 HCI 波特率。创建设置后,BT 控制器将初始化并使用以下功能启用:
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
ret = esp_bt_controller_init(&bt_cfg);
- 接下来,在 BLE 模式下启用控制器。
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
支持四种蓝牙模式:
ESP_BT_MODE_IDLE:蓝牙未运行
ESP_BT_MODE_BLE:BLE 模式
ESP_BT_MODE_CLASSIC_BT:BT Classic 模式
ESP_BT_MODE_BTDM:双模式(BLE + BT Classic)
- 初始化 BT 控制器后,Bluedroid 堆栈(包括 BT Classic 和 BLE 的通用定义和 API)将使用以下方法初始化和启用:
ret = esp_bluedroid_init();
ret = esp_bluedroid_enable();
- main 函数通过注册 GAP 和 GATT 事件处理程序以及应用程序配置文件来结束,并设置支持的最大 MTU 大小。
//register the callback function to the gap module
ret = esp_ble_gap_register_callback(esp_gap_cb);
//register the callback function to the gattc module
ret = esp_ble_gattc_register_callback(esp_gattc_cb);
ret = esp_ble_gattc_app_register(PROFILE_A_APP_ID);
esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(500);
if (local_mtu_ret){
ESP_LOGE(GATTC_TAG, "set local MTU failed, error code = %x", local_mtu_ret);
}
- GAP 和 GATT 事件处理程序是用于捕获 BLE 堆栈生成的事件并执行函数来配置应用程序参数的函数。此外,事件处理程序还用于处理来自中央的读取和写入事件。GAP 事件处理程序负责扫描和连接到服务器,而 GATT 处理程序管理客户端连接到服务器后发生的事件,例如搜索服务以及写入和读取数据。GAP 和 GATT 事件处理程序使用以下方法注册:
esp_ble_gap_register_callback();
esp_ble_gattc_register_callback();
五、应用程序配置文件
- Application Profiles 是一种对专为一个或多个 Server 应用程序设计的功能进行分组的方法。例如,您可以将一个应用程序配置文件连接到心率传感器,将另一个应用程序配置文件连接到温度传感器。每个应用程序配置文件都会创建一个 GATT 接口以连接到其他设备。
struct gattc_profile_inst {
esp_gattc_cb_t gattc_cb;
uint16_t gattc_if;
uint16_t app_id;
uint16_t conn_id;
uint16_t service_start_handle;
uint16_t service_end_handle;
uint16_t char_handle;
esp_bd_addr_t remote_bda;
};
在此示例中,有一个 Application Profile,其 ID 定义为:
#define PROFILE_NUM 1
#define PROFILE_A_APP_ID 0
Application Profile 存储在结构体数组中,该数组初始化为:gl_profile_tab
/* One gatt-based profile one app_id and one gattc_if, this array will store the gattc_if returned by ESP_GATTS_REG_EVT /static struct gattc_profile_inst gl_profile_tab[PROFILE_NUM] = {
[PROFILE_A_APP_ID] = {.gattc_cb = gattc_profile_event_handler,
* .gattc_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */
},
};
Application Profile 表数组的初始化包括定义 Profile 的回调函数。是的。此外,GATT 接口初始化为默认值 。稍后,当注册 Application Profile 时,BLE 堆栈会返回一个 GATT 接口实例,用于该 Application Profile。
配置文件注册会触发一个事件,该事件由事件处理程序处理。处理程序获取事件返回的 GATT 接口,并将其存储在 profile 表中:
static void esp_gattc_cb(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param)
{
ESP_LOGI(GATTC_TAG, "EVT %d, gattc if %d", event, gattc_if);
/* If event is register event, store the gattc_if for each profile */if (event == ESP_GATTC_REG_EVT) {
if (param->reg.status == ESP_GATT_OK) {
gl_profile_tab[param->reg.app_id].gattc_if = gattc_if;
} else {
ESP_LOGI(GATTC_TAG, "reg app failed, app_id %04x, status %d",
param->reg.app_id,
param->reg.status);
return;
}
}
最后,回调函数为表中的每个配置文件调用相应的事件处理程序。
/* If the gattc_if equal to profile A, call profile A cb handler, * so here call each profile's callback /do {
int idx;
for (idx = 0; idx < PROFILE_NUM; idx++) {
* if (gattc_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */gattc_if == gl_profile_tab[idx].gattc_if) {
if (gl_profile_tab[idx].gattc_cb) {
gl_profile_tab[idx].gattc_cb(event, gattc_if, param);
}
}
}
} while (0);
}
六、设置 Scan 参数
GATT 客户端通常会扫描附近的服务器,并尝试连接到它们(如果感兴趣)。但是,为了执行扫描,首先需要设置配置参数。这是在注册 Application Profiles 后完成的,因为注册完成后会触发事件。第一次触发此事件时,GATT 事件处理程序会捕获该事件并将其分配给配置文件 A,然后将该事件转发到配置文件 A 的 GATT 事件处理程序。在此事件处理程序中,该事件用于调用函数,该函数将结构实例作为参数。此结构定义为:ESP_GATTC_REG_EVT esp_ble_gap_set_scan_params() ble_scan_params
/// Ble scan parameters
typedef struct {
esp_ble_scan_type_t scan_type; /*!< Scan type */
** esp_ble_addr_type_t own_addr_type; /***!< Owner address type */
** esp_ble_scan_filter_t scan_filter_policy; /***!< Scan filter policy */
** uint16_t scan_interval; /***!< Scan interval. This is defined as the time interval from when the Controller started its last LE scan until it begins the subsequent LE scan.*/
//Range: 0x0004 to 0x4000
//Default: 0x0010 (10 ms)
//Time = N * 0.625 msec
//Time Range: 2.5 msec to 10.24 seconds
uint16_t scan_window; /*!< Scan window. The duration of the LE scan. LE_Scan_Window shall be less than or equal to LE_Scan_Interval*/
//Range: 0x0004 to 0x4000 //Default: 0x0010 (10 ms)
//Time = N * 0.625 msec
//Time Range: 2.5 msec to 10240 msec
} esp_ble_scan_params_t;
它初始化为:
static esp_ble_scan_params_t ble_scan_params = {
.scan_type = BLE_SCAN_TYPE_ACTIVE,
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL,
.scan_interval = 0x50,
.scan_window = 0x30
};
对 BLE 扫描参数进行配置,以便扫描类型处于活动状态(包括读取扫描响应),它是公共类型,允许读取任何广告设备,并且扫描间隔为 100 毫秒(1.25 毫秒 * 0x50)和 60 毫秒(1.25 毫秒 * 0x30)。
扫描值使用以下函数设置:esp_ble_gap_set_scan_params()
case ESP_GATTC_REG_EVT:
ESP_LOGI(GATTC_TAG, "REG_EVT");
esp_err_t scan_ret = esp_ble_gap_set_scan_params(&ble_scan_params);
if (scan_ret){
ESP_LOGE(GATTC_TAG, "set scan params error, error code = %x", scan_ret);
}
break;
七、开始扫描
设置扫描参数后,将触发一个事件,该事件由 GAP 事件处理程序处理。此事件用于开始扫描附近的 GATT 服务器:ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT esp_gap_cb()
case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: {
//the unit of the duration is second
uint32_t duration = 30;
esp_ble_gap_start_scanning(duration);
break;
}
使用函数启动扫描,该函数采用表示连续扫描持续时间(以秒为单位)的参数。扫描期结束后,将触发事件。esp_ble_gap_start_scanning() ESP_GAP_SEARCH_INQ_CMPL_EVT
八、获取扫描结果
扫描结果在与事件一起到达后立即显示,其中包括以下参数:ESP_GAP_BLE_SCAN_RESULT_EVT
我们对事件感兴趣,每次找到新设备时都会调用该事件。我们还对 感兴趣,它在扫描持续时间完成时触发,可用于重新启动扫描过程:ESP_GAP_SEARCH_INQ_RES_EVT ESP_GAP_SEARCH_INQ_CMPL_EVT

首先,解析设备名称并将其与 中定义的设备名称进行比较。如果它等于我们感兴趣的 GATT 服务器的设备名称,则停止扫描。remote_device_name
九、连接到 GATT 服务器
每次我们从事件收到结果时,代码首先打印远程设备的地址:
ESP_GAP_SEARCH_INQ_RES_EVT
case ESP_GAP_SEARCH_INQ_RES_EVT:
esp_log_buffer_hex(GATTC_TAG, scan_result->scan_rst.bda, 6);
然后,客户端打印公布的数据长度和扫描响应长度:
ESP_LOGI(GATTC_TAG, "searched Adv Data Len %d, Scan Response Len %d", scan_result->scan_rst.adv_data_len, scan_result->scan_rst.scan_rsp_len);
为了获取设备名称,我们使用函数,该函数获取存储在 中的广告数据、广告数据的类型和长度,以便从广告数据包帧中提取值。然后打印设备名称。esp_ble_resolve_adv_data() scan_result->scan_rst.ble_adv
adv_name = esp_ble_resolve_adv_data(scan_result->scan_rst.ble_adv, ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len);
ESP_LOGI(GATTC_TAG, "searched Device Name Len %d", adv_name_len);
esp_log_buffer_char(GATTC_TAG, adv_name, adv_name_len);
最后,如果远程设备名称与我们上面定义的相同,则本地设备将停止扫描并尝试使用该函数打开与远程设备的连接。此函数将应用程序配置文件 GATT 接口、远程服务器地址和布尔值作为参数。
十、配置 MTU 大小
ATT_MTU定义为在客户端和服务器之间发送的任何数据包的最大大小。当客户端连接到服务器时,它通过交换 MTU 请求和响应协议数据单元 (PDU) 来通知服务器要使用的 MTU 大小。这是在打开连接后完成的。打开连接后,将触发一个事件:ESP_GATTC_CONNECT_EVT
十一、发现服务
MTU 配置事件还用于开始发现客户端刚刚连接到的服务器中可用的服务。为了发现服务,使用了该函数。该函数的参数是 GATT 接口、Application Profile 连接 ID 和客户端感兴趣的服务应用程序的 UUID。我们正在寻找的服务定义为:esp_ble_gattc_search_service()
十二、获取特性
此示例实现从预定义服务获取特征数据。我们想要从中获取特征的服务具有 0x00FF 的 UUID,我们感兴趣的特征的 UUID 为 0xFF01:
十三、注册通知
客户端可以注册以在每次特征值更改时从服务器接收通知。在此示例中,我们希望注册使用 UUID 0xff01标识的特征的通知。获取所有特征后,我们检查接收到的特征的属性,然后使用该函数注册通知。函数参数是 GATT 接口、远程服务器的地址以及我们要注册通知的句柄。
6740

被折叠的 条评论
为什么被折叠?



