目录

一、蓝牙基础知识

1.  蓝牙分类

2.  低功耗蓝牙(BLE)

3.  GATT profile通用属性配置文件——数据如何组织交互

1)逻辑业务层(GATT)‌

2) ‌物理存储层(Attribute)‌

4.  GAP profile通用访问配置文件——设备如何被发现和连接

1)核心功能

2)开发者关注点

5.  蓝牙通信信道

二、ESP32的蓝牙开发实践


一、蓝牙基础知识

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}},  

};

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐