创建项目
使用 idf.py create-project 创建新项目
idf.py create-project gatts
随后使用更改项目配置,使其支持蓝牙
idf.py menuconfig
编辑gatts.c
包含要使用的头文件
#include <stdio.h>
#include "esp_err.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_bt.h"
#include "esp_bt_main.h"
#include "esp_gap_ble_api.h"
#include "esp_gatts_api.h"
#include "led.h"
定义日志前缀和蓝牙设备名称
#define GATTS_TAG "GATTS_SERVER"
#define DEVICE_NAME "BLE_DEVICE"
定义profile相关
#define PROFILE_NUM 1
#define PROFILE_LED_APP_ID 0
定义服务 UUID 和 特征 UUID
这里将服务 UUID 定义为:Automation IO Service 0x1815
Automation IO Service
[[Assigned_Numbers.pdf#page=66&selection=39,0,39,21|Assigned_Numbers, 页面 66]]
#define LED_SERVICE_UUID 0x1815
#define LED_CHAR_UUID 0x01
定义全局变量,保存 LED 状态
static uint8_t led_state = 0x00;
定义 gatts 事件分发函数
static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatt_if, esp_ble_gatts_cb_param_t *params);
定义 gap 事件处理函数
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *params);
定义处理 LED 服务的 gatts 事件处理函数
static void gatts_profile_led_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatt_if, esp_ble_gatts_cb_param_t *params);
定义结构体,用于实例化 gatts_profile 的各项属性
struct gatts_profile_instance = {
esp_gatts_cb_t gatts_cb; // 存储该 profile 的 gatts 回调函数
uint16_t gatts_if; // 存储该 profile 的 gatts 接口号
uint16_t app_id; // 存储该 profile 的 app_id
uint16_t conn_id; // 存储该 profile 的 连接ID
uint16_t service_handle; // 储存该 profile 的 连接句柄
esp_gatt_srvc_id_t service_id; // 储存该 profile 的 服务ID
uint16_t char_handle; // 储存该 profile 的 特征句柄
esp_bt_uuid_t char_uuid; // 储存该 profile 的 特征 UUID
esp_attr_value_t char_val; // 储存该 profile 的 特征值变量
esp_gatt_char_prop_t property; // 储存该 profile 的 特征属性
esp_gatt_perm_t perm; // 储存该 profile 的 权限
uint16_t descr_handle; // 储存该 profile 的 描述符句柄
esp_bt_uuid_t descr_uuid; // 储存该 profile 的 描述符 UUID
}
实例化 gatts profile
创建一个结构体数组,来保存各服务的实例化。
因为通常情况下,蓝牙设备包含多个 profile ,可以将 profile 理解为 app。用 结构体数组实例化 profile 的各项属性,有助于代码模块化,便于维护以及更易读。
static struct gatts_profile_instance gl_profile_tab[] = {
[PROFILE_LED_APP_ID] = {
.gatts_cb = gatts_profile_led_event_handler,
.gatts_if = ESP_GATT_IF_NONE
}
};
创建原始广播数据
static uint8_t raw_adv_data_app_led[] = {
/* device flag */
0x02, 0x01, 0x06,
/* complete local name */
0x0C, 0x09, 'P', 'F', 'O', 'F', 'I', 'L', 'E', '-', 'L', 'E', 'D',
/* tx power -61db */
0x02, 0x0A, 0xEB,
/* 16-bit Service UUID */
0x03, 0x03, 0x01, 0x00
}
定义广播参数
static esp_ble_adv_params_t 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
};
创建处理 LED 的 gatts 事件处理函数
static void gatts_profile_led_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatt_if, esp_ble_gatts_cb_param_t *params)
{
switch (event) {
// GATTS APP 注册之后,会触发该事件
case ESP_GATTS_REG_EVT:
ESP_LOGI(GATTS_TAG, \
"Prifile LED registerd. This profile is use to control led on the board, status: %d, app_id: %" PRIu16 "", \
params->reg.status, params->reg.app_id);
/* 为 profile 属性赋值 */
gl_profile_tab[PROFILE_LED_APP_ID].service_id.is_primary = true; // 设置该服务为主服务
gl_profile_tab[PROFILE_LED_APP_ID].service_id.id.inst_id = 0x00; // 设置实例ID
gl_profile_tab[PROFILE_LED_APP_ID].service_id.id.uuid.len = ESP_UUID_LEN_16; // 设置UUID长度
gl_profile_tab[PROFILE_LED_APP_ID].service_id.id.uuid.uuid.uuid16 = LED_SERVICE_UUID; // 设置服务的UUID
// 创建 gatts 服务,最后的 4 表示服务最多包含四个属性,包含服务、特征、描述符
esp_ble_gatts_create_service(gatt_if, \
&gl_profile_tab[PROFILE_LED_APP_ID].service_id, \
4);
break;
// 连接事件
case ESP_GATTS_CONNECT_EVT:
ESP_LOGI(GATTS_TAG, "Profile LED device connect, conn_id: %" PRIu16 "", params->connect.conn_id);
break;
// 断开连接事件,为了保证设备在断开连接之后能够重新连接,这里需要重新启动广播
case ESP_GATTS_DISCONNECT_EVT:
esp_ble_gap_start_advertising(&adv_params);
break;
// GATTS 服务创建之后,会执行
case ESP_GATTS_CREATE_EVT:
/* 为 profile 属性赋值 */
gl_profile_tab[PROFILE_LED_APP_ID].service_handle = params->create.service_handle; // 从事件参数中获取服务句柄
gl_profile_tab[PROFILE_LED_APP_ID].char_uuid.len = ESP_UUID_LEN_16; // 特征 UUID 长度
gl_profile_tab[PROFILE_LED_APP_ID].char_uuid.uuid.uuid16 = LED_CHAR_UUID; // 特征 UUID
gl_profile_tab[PROFILE_LED_APP_ID].perm = ESP_GATT_PERM_WRITE; // 权限,这里定义为 只写
gl_profile_tab[PROFILE_LED_APP_ID].property = ESP_GATT_CHAR_PROP_BIT_WRITE; // 属性,这里定义为 可写
gl_profile_tab[PROFILE_LED_APP_ID].char_val.attr_max_len = sizeof(uint8_t); // 特征值最大长度
gl_profile_tab[PROFILE_LED_APP_ID].char_val.attr_len = sizeof(uint8_t); // 特征值当前长度
gl_profile_tab[PROFILE_LED_APP_ID].char_val.attr_value = &led_state; // 特征值变量,读取此变量用来更改 LED 状态
// 将 特征 添加到 gatts 服务中
esp_ble_gatts_add_char(gl_profile_tab[PROFILE_LED_APP_ID].service_handle, \
&gl_profile_tab[PROFILE_LED_APP_ID].char_uuid, \
gl_profile_tab[PROFILE_LED_APP_ID].perm, \
gl_profile_tab[PROFILE_LED_APP_ID].property, \
&gl_profile_tab[PROFILE_LED_APP_ID].char_val, \
NULL);
break;
// 特征添加到 gatts 服务后会执行
case ESP_GATTS_ADD_CHAR_EVT:
// 启动 gatts 服务
esp_ble_gatts_start_service(gl_profile_tab[PROFILE_LED_APP_ID].service_handle);
// 获取特征句柄
// 因为触发此事件之后,才会分配一个特征句柄,之后我们需要用这个句柄判断数据写入来源
gl_profile_tab[PROFILE_LED_APP_ID].char_handle = params->add_char.attr_handle;
break;
// gatts 服务启动后执行
case ESP_GATTS_START_EVT:
break;
// gatts 服务收到写入操作后执行
case ESP_GATTS_WRITE_EVT:
uint8_t value = *params->write.value;
printf("value = %d\n", value);
// 判断数据写入来源,避免与其他特征的写入操作混淆
if (params->write.handle == gl_profile_tab[PROFILE_LED_APP_ID].char_handle) {
if (value == LED_ON) {
ESP_LOGI(GATTS_TAG, "LED is ON");
led_on();
} else if (value == LED_OFF) {
ESP_LOGI(GATTS_TAG, "LED is OFF");
led_off();
}
}
// 接收写入操作完成后,需要返回给客户端一个回应,表示收到了写入的数据,不然客户端会认为写入失败
esp_ble_gatts_send_response(gl_profile_tab[PROFILE_LED_APP_ID].gatts_if, \
params->write.conn_id, \
params->write.trans_id, \
ESP_GATT_OK,
NULL);
break;
default:
break;
}
}
创建 gatts 事件分发函数
此函数意味将 gatts 事件分发给 不同的 profile。
假设现有另外一 profile:PROFILE_TMC2209_APP_ID,事件分发就显得尤为重要,添加事件分发层大大减少了程序量和程序复杂度
static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *params)
{
switch (event) {
/* If event is register event, store the gatts_if for each profile */
case ESP_GATTS_REG_EVT:
if (params->reg.status == ESP_GATT_OK) {
gl_profile_tab[params->reg.app_id].gatts_if = gatts_if;
/* 这里用 gatts_if 来判断需要配置哪个 profile 的广播事件 */
switch (params->reg.app_id) {
case PROFILE_LED_APP_ID:
esp_ble_gap_config_adv_data_raw(raw_adv_data_app_led, sizeof(raw_adv_data_app_led));
break;
case PROFILE_TMC2209_APP_ID:
esp_ble_gap_config_adv_data_raw(raw_adv_data_app_tmc2209, sizeof(raw_adv_data_app_tmc2209));
break;
default:
break;
}
} else {
ESP_LOGE(GATTS_TAG, "Register app failed, app_id: %04x, status: %d", params->reg.app_id, params->reg.status);
return;
}
break;
default:
break;
}
/* 这里的 ACTIVE_APP 为 两个 profile 中的其中一个,用来判断执行哪个 profile 的回调函数 */
/* Execute the actived app */
gl_profile_tab[ACTIVE_APP].gatts_cb(event, gatts_if, params);
}
创建 gap 事件处理函数
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *params)
{
switch (event) {
/* 广播数据配置完成后执行 */
case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:
esp_ble_gap_set_device_name(DEVICE_NAME);
esp_ble_gap_start_advertising(&adv_params);
break;
/* 广播启动后执行 */
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
ESP_LOGI(GATTS_TAG, "GAP handler: Start advertising complete.");
break;
default:
break;
}
}
app_main
void app_main(void)
{
ESP_LOGI(GATTS_TAG, "------------------------------------------------\n");
configure_led();
esp_err_t ret;
/* Initialize nvs flash */
ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
nvs_flash_erase();
if (!nvs_flash_init())
return;
} else {
ESP_LOGI(GATTS_TAG, "Initialized nvs flash");
}
led_blink();
/* Initialize bluetooth controller with default config */
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
/* Initialize bluetooth controller */
if (esp_bt_controller_init(&bt_cfg)) {
ESP_LOGE(GATTS_TAG, "Initialize bluetooth controller failed.");
return;
} else {
ESP_LOGI(GATTS_TAG, "Initialized bluetooth controller.");
}
led_blink();
/* Enable bluetooth controller */
if (esp_bt_controller_enable(ESP_BT_MODE_BLE)) {
ESP_LOGE(GATTS_TAG, "Enable bluetooth controller failed.");
return;
} else {
ESP_LOGI(GATTS_TAG, "Enabled bluetooth controller.");
}
led_blink();
/* Initialize bluedroid */
if (esp_bluedroid_init()) {
ESP_LOGE(GATTS_TAG, "Initialize bluedroid failed.");
return;
} else {
ESP_LOGI(GATTS_TAG, "Initialized bluedroid.");
}
led_blink();
/* Enable bluedroid */
if (esp_bluedroid_enable()) {
ESP_LOGE(GATTS_TAG, "Enable bluedroid failed.");
return;
} else {
ESP_LOGI(GATTS_TAG, "Enabled bluedroid.");
}
led_blink();
/* Register GATTS callback handler */
if (esp_ble_gatts_register_callback(gatts_event_handler)) {
ESP_LOGE(GATTS_TAG, "Register gatts event handler error.");
return;
} else {
ESP_LOGI(GATTS_TAG, "Registerd gatts event handler");
}
led_blink();
/* Register GAP callback handler */
if (esp_ble_gap_register_callback(gap_event_handler)) {
ESP_LOGE(GATTS_TAG, "Register gap event handler error.");
return;
} else {
ESP_LOGI(GATTS_TAG, "Registerd gap event handler.");
}
led_blink();
/* Register APP by APP id */
esp_ble_gatts_app_register(ACTIVE_APP);
led_blink();
}
编辑led.h
#include "driver/gpio.h"
#include "freertos/task.h"
#define LED_PIN 2
#define LED_ON 1
#define LED_OFF 0
void configure_led(void)
{
gpio_reset_pin(LED_PIN);
gpio_set_direction(LED_PIN, GPIO_MODE_OUTPUT);
}
void led_on(void)
{
gpio_set_level(LED_PIN, LED_ON);
}
void led_off(void)
{
gpio_set_level(LED_PIN, LED_OFF);
}
void led_blink(void)
{
led_on();
vTaskDelay(pdMS_TO_TICKS(50));
led_off();
vTaskDelay(pdMS_TO_TICKS(50));
}