ESP32入门例程5 蓝牙开发
目录
3. GATT profile通用属性配置文件——数据如何组织交互
4. GAP profile通用访问配置文件——设备如何被发现和连接
一、蓝牙基础知识
1. 蓝牙分类
蓝牙分为经典蓝牙和低功耗蓝牙
2. 低功耗蓝牙(BLE)

无论是哪个芯片厂商实现的BLE协议栈,其结构都非常的相似,低功耗蓝牙协议栈包含两部分共8层:主机(Host)和控制器(Controller)。 控制器部分包括:
- 物理层(Physical Layer)
- 链路层(Link Layer)
- 主机控制接口层(Host Controller Interface)
主机部分包括:
- L2CAP 逻辑链路控制及自适应协议层(Logical Link Control and Adaptation Protocol)
- 安全管理层(Security Manager)
- ATT 属性协议层(Attribute Protocol)
- GAP 通用访问配置文件层(Generic Access Profile)
- GATT 通用属性配置文件层(Generic Attribute Profile)
从应用层到物理层一共包含8层,如下图所示。对于开发者而言,不需要对每一层的具体实现都有深入的了解。只需要掌握与应用紧密相关的 GAP/GATT 层即可满足大部分开发的需求,通过 SoftDevice(即协议栈,这种方式使得协议栈和用户应用可以单独编译和链接)的 API 软件接口(以 sd_ 开头)调用来实现。
3. GATT profile通用属性配置文件——数据如何组织交互
Bluetooth Gatt 服务UUID 对照表:Bluetooth Gatt 服务UUID 对照表 – 库伦微
1)逻辑业务层(GATT)
| 层级 | 作用 | 示例UUID |
|---|---|---|
| Profile | 跨厂商的标准化功能集合(如HOGP、HRP) | 非直接对应UUID |
| Service | 具体功能模块(如电池服务、设备信息服务) | 0x180F (BAS) |
| Characteristic | 服务中的数据点(如电量百分比、序列号) | 0x2A19 |
| Descriptor | 描述特性元数据(如通知使能、数据格式) | 0x2902 (CCCD) |
2) 物理存储层(Attribute)
- Handle:2字节唯一标识(类似内存地址)
- UUID:2字节(SIG标准)或16字节(自定义)
- Value:动态/静态存储的实际数据
- Permissions:位域控制的访问权限
4. GAP profile通用访问配置文件——设备如何被发现和连接
1)核心功能
- 设备可见性控制:
- 广播模式(Broadcaster/Observer)
- 连接模式(Peripheral/Central)
- 连接管理:
- 发起连接(
LE Create Connection) - 安全配对(Just Works/Passkey Entry等)
- 发起连接(
- 物理信道选择:
- 37/38/39三个广播信道
- 0~36数据信道(自适应跳频)
2)开发者关注点
- 广播数据格式(31字节有效载荷):
- 连接参数优化:
connInterval:7.5ms~4s(影响功耗和吞吐量)slaveLatency:允许跳过的连接事件数
5. 蓝牙通信信道
频段:2.4G (2402M-2480M)
信道:一共40个信道(0-39),每隔2M划分一个信道,其中 37、38、39是蓝牙广播信道
二、ESP32的蓝牙开发实践
下图是蓝牙示例工程的代码截图,简述了蓝牙开发时应重点关注的内容。
项目的功能是:启动ESP32后,可以通过客户端搜索到ESP32这个蓝牙设置。当客户端与ESP32通过蓝牙连接以后,用户可以在客户端看到ESP32上传的温度和湿度数据,并且用户可以通过客户端上面的灯的开关来控制ESP32上连接的灯的亮灭。
完整代码:https://github.com/nolasun/ESP32/tree/main/esp32_example/ble
1. 初始化函数
/**
* 初始化并启动蓝牙BLE
* @param 无
* @return 是否成功
*/
esp_err_t ble_cfg_net_init(void)
{
//初始蓝牙控制器、使能蓝牙控制器
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_bt_controller_init(&bt_cfg));
ESP_ERROR_CHECK(esp_bt_controller_enable(ESP_BT_MODE_BLE));
//初始化bluedroid、使能bluedroid
esp_bluedroid_config_t bluedroid_cfg = BT_BLUEDROID_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_bluedroid_init_with_cfg(&bluedroid_cfg));
ESP_ERROR_CHECK(esp_bluedroid_enable());
//注册两个回调函数,GAP和GATT服务端回调函数
ESP_ERROR_CHECK(esp_ble_gatts_register_callback(gatts_profile_event_handler));
ESP_ERROR_CHECK(esp_ble_gap_register_callback(gap_event_handler));
ESP_ERROR_CHECK(esp_ble_gatts_app_register(ESP_APP_ID)); //一个APP对应一份Profile
//设置mtu
ESP_ERROR_CHECK(esp_ble_gatt_set_local_mtu(500));
return ESP_OK;
}
2. 回调函数
/**
* gatt事件回调函数
* @param event 事件ID
* @param gatts_if gatt接口,一个profile对应一个
* @param param 事件参数
* @return 无
*/
static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
switch (event) {
case ESP_GATTS_REG_EVT: //可以认为是一个Profile GATTS启动时会进入此事件
{
//设置广播名称
esp_err_t set_dev_name_ret = esp_ble_gap_set_device_name(BLE_DEVICE_NAME);
if (set_dev_name_ret){
ESP_LOGE(TAG, "set device name failed, error code = %x", set_dev_name_ret);
}
//设置广播数据
esp_err_t ret = esp_ble_gap_config_adv_data(&adv_data);
if (ret){
ESP_LOGE(TAG, "config adv data failed, error code = %x", ret);
}
adv_config_done |= ADV_CONFIG_FLAG;
//设置扫描回复参数
ret = esp_ble_gap_config_adv_data(&scan_rsp_data);
if (ret){
ESP_LOGE(TAG, "config scan response data failed, error code = %x", ret);
}
adv_config_done |= SCAN_RSP_CONFIG_FLAG;
//注册attr表
esp_err_t create_attr_ret = esp_ble_gatts_create_attr_tab(gatt_db, gatts_if, SV_IDX_NB, 0);
if (create_attr_ret){
ESP_LOGE(TAG, "create attr table failed, error code = %x", create_attr_ret);
}
if(param->reg.status == ESP_GATT_OK)
{
gl_gatts_if = gatts_if;
//gl_conn_id = param->connect.conn_id;
}
}
break;
case ESP_GATTS_READ_EVT: //收到客户端的读取事件
{
ESP_LOGI(TAG, "ESP_GATTS_READ_EVT");
esp_gatt_rsp_t rsp;
memset(&rsp, 0, sizeof(esp_gatt_rsp_t));
rsp.attr_value.handle = param->read.handle;
if(netcfg_handle_table[SV1_CH1_IDX_CHAR_VAL] == param->read.handle) //读取温度值
{
rsp.attr_value.len = sizeof(temp_value);
memcpy(rsp.attr_value.value,temp_value,sizeof(temp_value));
}
if(netcfg_handle_table[SV1_CH2_IDX_CHAR_VAL] == param->read.handle) //读取湿度值
{
rsp.attr_value.len = sizeof(humidity_value);
memcpy(rsp.attr_value.value,humidity_value,sizeof(humidity_value));
}
if(netcfg_handle_table[SV1_CH3_IDX_CHAR_VAL] == param->read.handle) //读取LED值
{
rsp.attr_value.len = sizeof(led_value);
memcpy(rsp.attr_value.value,led_value,sizeof(led_value));
}
esp_ble_gatts_send_response(gatts_if, param->read.conn_id, param->read.trans_id,ESP_GATT_OK, &rsp);
break;
}
case ESP_GATTS_WRITE_EVT: //收到客户端的写事件
if (!param->write.is_prep){
// the data length of gattc write must be less than GATTS_DEMO_CHAR_VAL_LEN_MAX.
ESP_LOGI(TAG, "GATT_WRITE_EVT, handle = %d, value len = %d, value :", param->write.handle, param->write.len);
esp_log_buffer_hex(TAG, param->write.value, param->write.len);
if(param->write.handle == netcfg_handle_table[SV1_CH1_IDX_CHAR_CFG]) //特征1客户端特征配置
{
sv1_ch1_client_cfg[0] = param->write.value[0];
sv1_ch1_client_cfg[1] = param->write.value[1];
}
else if(param->write.handle == netcfg_handle_table[SV1_CH2_IDX_CHAR_CFG]) //特征2客户端特征配置
{
sv1_ch2_client_cfg[0] = param->write.value[0];
sv1_ch2_client_cfg[1] = param->write.value[1];
}
else if(param->write.handle == netcfg_handle_table[SV1_CH3_IDX_CHAR_CFG]) //特征3客户端特征配置
{
sv1_ch3_client_cfg[0] = param->write.value[0];
sv1_ch3_client_cfg[1] = param->write.value[1];
}
else if(param->write.handle == netcfg_handle_table[SV1_CH3_IDX_CHAR_VAL]) //LED值
{
led_value[0] = param->write.value[0];
//改写LED的值
ESP_LOGI(TAG,"led value:%d",led_value[0]);
gpio_set_level(GPIO_NUM_27,led_value[0]?1:0);
}
if (param->write.need_rsp){
esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, NULL);
}
}
break;
case ESP_GATTS_EXEC_WRITE_EVT:
// the length of gattc prepare write data must be less than GATTS_DEMO_CHAR_VAL_LEN_MAX.
ESP_LOGI(TAG, "ESP_GATTS_EXEC_WRITE_EVT");
//example_exec_write_event_env(&prepare_write_env, param);
break;
case ESP_GATTS_MTU_EVT:
ESP_LOGI(TAG, "ESP_GATTS_MTU_EVT, MTU %d", param->mtu.mtu);
break;
case ESP_GATTS_CONF_EVT:
ESP_LOGI(TAG, "ESP_GATTS_CONF_EVT, status = %d, attr_handle %d", param->conf.status, param->conf.handle);
break;
case ESP_GATTS_START_EVT:
ESP_LOGI(TAG, "SERVICE_START_EVT, status %d, service_handle %d", param->start.status, param->start.service_handle);
break;
case ESP_GATTS_CONNECT_EVT: //收到连接事件
ESP_LOGI(TAG, "ESP_GATTS_CONNECT_EVT, conn_id = %d", param->connect.conn_id);
esp_log_buffer_hex(TAG, param->connect.remote_bda, 6);
esp_ble_conn_update_params_t conn_params = {0};
memcpy(conn_params.bda, param->connect.remote_bda, sizeof(esp_bd_addr_t));
/* For the iOS system, please refer to Apple official documents about the BLE connection parameters restrictions. */
conn_params.latency = 0; //从机延迟
conn_params.max_int = 0x20; // 最大连接间隔 = 0x20*1.25ms = 40ms
conn_params.min_int = 0x10; // 最小连接间隔 = 0x10*1.25ms = 20ms
conn_params.timeout = 400; // 监控超时 = 400*10ms = 4000ms
//start sent the update connection parameters to the peer device.
esp_ble_gap_update_conn_params(&conn_params);
gl_conn_id = param->connect.conn_id;
break;
case ESP_GATTS_DISCONNECT_EVT: //收到断开连接事件
ESP_LOGI(TAG, "ESP_GATTS_DISCONNECT_EVT, reason = 0x%x", param->disconnect.reason);
esp_ble_gap_start_advertising(&adv_params);
gl_conn_id = 0xFFFF;
break;
case ESP_GATTS_CREAT_ATTR_TAB_EVT:{ //注册ATTR表成功事件
if (param->add_attr_tab.status != ESP_GATT_OK){
ESP_LOGE(TAG, "create attribute table failed, error code=0x%x", param->add_attr_tab.status);
}
else if (param->add_attr_tab.num_handle != SV_IDX_NB){
ESP_LOGE(TAG, "create attribute table abnormally, num_handle (%d) \
doesn't equal to NETCFG_IDX_NB(%d)", param->add_attr_tab.num_handle, SV_IDX_NB);
}
else {
ESP_LOGI(TAG, "create attribute table successfully, the number handle = %d\n",param->add_attr_tab.num_handle);
memcpy(netcfg_handle_table, param->add_attr_tab.handles, sizeof(netcfg_handle_table));
esp_ble_gatts_start_service(netcfg_handle_table[SV1_IDX_SVC]);
}
break;
}
case ESP_GATTS_STOP_EVT:
case ESP_GATTS_OPEN_EVT:
case ESP_GATTS_CANCEL_OPEN_EVT:
case ESP_GATTS_CLOSE_EVT:
case ESP_GATTS_LISTEN_EVT:
case ESP_GATTS_CONGEST_EVT:
case ESP_GATTS_UNREG_EVT:
case ESP_GATTS_DELETE_EVT:
default:
break;
}
}
/**
* gap事件回调函数
* @param event 事件ID
* @param param 事件参数
* @return 无
*/
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
{
switch (event) {
case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: //设置广播数据成功事件
adv_config_done &= (~ADV_CONFIG_FLAG);
if (adv_config_done == 0){
esp_ble_gap_start_advertising(&adv_params);
}
break;
case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT: //设置扫描回复数据成功事件
adv_config_done &= (~SCAN_RSP_CONFIG_FLAG);
if (adv_config_done == 0){
esp_ble_gap_start_advertising(&adv_params);
}
break;
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: //启动广播成功事件
/* advertising start complete event to indicate advertising start successfully or failed */
if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(TAG, "advertising start failed");
}else{
ESP_LOGI(TAG, "advertising start successfully");
}
break;
case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: //停止广播成功事件
if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS) {
ESP_LOGE(TAG, "Advertising stop failed");
}
else {
ESP_LOGI(TAG, "Stop adv successfully\n");
}
break;
case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT: //更新连接参数成功事件
ESP_LOGI(TAG, "update connection params status = %d, min_int = %d, max_int = %d,conn_int = %d,latency = %d, timeout = %d",
param->update_conn_params.status,
param->update_conn_params.min_int,
param->update_conn_params.max_int,
param->update_conn_params.conn_int,
param->update_conn_params.latency,
param->update_conn_params.timeout);
break;
default:
break;
}
}
3. gatt属性表
//gatt描述表
static const esp_gatts_attr_db_t gatt_db[SV_IDX_NB] =
{
//服务声明
[SV1_IDX_SVC] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid, ESP_GATT_PERM_READ,
sizeof(uint16_t), sizeof(GATTS_SERVICE_UUID_TEST), (uint8_t *)&GATTS_SERVICE_UUID_TEST}},
//特征1 温度
//特征声明
[SV1_CH1_IDX_CHAR] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
sizeof(uint8_t), sizeof(uint8_t), (uint8_t *)&char_prop_read_notify}}, //温度值只允许读和通知
//特征值
[SV1_CH1_IDX_CHAR_VAL] =
{{ESP_GATT_RSP_BY_APP}, {ESP_UUID_LEN_16, (uint8_t *)&GATTS_CHAR_UUID_TEMP, ESP_GATT_PERM_READ,
sizeof(temp_value), sizeof(temp_value), (uint8_t *)temp_value}},
//特征描述->客户端特征配置
[SV1_CH1_IDX_CHAR_CFG] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_client_config_uuid, ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE,
sizeof(uint16_t), sizeof(sv1_ch1_client_cfg), (uint8_t *)sv1_ch1_client_cfg}},
//特征2 湿度
//特征声明
[SV1_CH2_IDX_CHAR] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
sizeof(uint8_t), sizeof(uint8_t), (uint8_t *)&char_prop_read_notify}}, //湿度值只允许读和通知
//特征值
[SV1_CH2_IDX_CHAR_VAL] =
{{ESP_GATT_RSP_BY_APP}, {ESP_UUID_LEN_16, (uint8_t *)&GATTS_CHAR_UUID_HUMIDITY, ESP_GATT_PERM_READ,
sizeof(humidity_value), sizeof(humidity_value), (uint8_t *)humidity_value}},
//特征描述->客户端特征配置
[SV1_CH2_IDX_CHAR_CFG] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_client_config_uuid, ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE,
sizeof(uint16_t), sizeof(sv1_ch2_client_cfg), (uint8_t *)&sv1_ch2_client_cfg}},
//特征3 LED
//特征声明
[SV1_CH3_IDX_CHAR] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
sizeof(uint8_t), sizeof(uint8_t), (uint8_t *)&char_prop_read_write_notify}}, //LED值允许读写
//特征值
[SV1_CH3_IDX_CHAR_VAL] =
{{ESP_GATT_RSP_BY_APP}, {ESP_UUID_LEN_16, (uint8_t *)&GATTS_CHAR_UUID_LED, ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE,
sizeof(led_value), sizeof(led_value), (uint8_t *)led_value}},
//特征描述->客户端特征配置
[SV1_CH3_IDX_CHAR_CFG] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_client_config_uuid, ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE,
sizeof(uint16_t), sizeof(sv1_ch3_client_cfg), (uint8_t *)&sv1_ch3_client_cfg}},
};
更多推荐


所有评论(0)