ESP32学习笔记(34)——BLE一主多从连接

发布者:Xiaohan521最新更新时间:2025-02-27 来源: jianshu关键字:ESP32  BLE 手机看文章 扫描二维码
随时随地手机看文章

一、简介

由于蓝牙主机和从机组网,如果不使用 Mesh 的话,只能组微微网。蓝牙5.0的微微网最大可以连接20个从机。

1.1 连接句柄

在主机与从机发生连接的时候会进行连接句柄的分配。连接句柄的作用是在蓝牙数据进行分组的时候进行设备区分的。连接句柄相当于一个“令牌”,从设备一旦和主设备发生连接,主设备就给从设备分配一个“令牌”。主设备通过这个“令牌”来识别与区分从设备。因此对于连接句柄的分配将是实现一主多从连接,并且进行通信的关键。

1.2 Bluedroid主机架构

在 ESP-IDF 中,使用经过大量修改后的 BLUEDROID 作为蓝牙主机 (Classic BT + BLE)。BLUEDROID 拥有较为完善的功能,⽀持常用的规范和架构设计,同时也较为复杂。经过大量修改后,BLUEDROID 保留了大多数 BTA 层以下的代码,几乎完全删去了 BTIF 层的代码,使用了较为精简的 BTC 层作为内置规范及 Misc 控制层。修改后的 BLUEDROID 及其与控制器之间的关系如下图:

二、API说明

以下 GATT 接口位于 bt/host/bluedroid/api/include/api/esp_gattc_api.h

2.1 esp_ble_gattc_open

2.2 esp_ble_gattc_search_service

2.3 esp_ble_gattc_get_char_by_uuid

2.4 esp_ble_gattc_get_descr_by_char_handle

2.5 esp_ble_gattc_get_attr_count

2.6 esp_ble_gattc_write_char

2.7 esp_ble_gattc_write_char_descr

2.8 esp_ble_gattc_register_for_notify

三、BT控制器和协议栈初始化

使用 esp-idfexamplesbluetoothbluedroidblegattc_multi_connect 中的例程


.........//esp_bt_controller_config_t是蓝牙控制器配置结构体,这里使用了一个默认的参数

    esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();

    //初始化蓝牙控制器,此函数只能被调用一次,且必须在其他蓝牙功能被调用之前调用

    ret = esp_bt_controller_init(&bt_cfg);

    if (ret) {

        ESP_LOGE(GATTC_TAG, '%s initialize controller failed: %sn', __func__, esp_err_to_name(ret));

        return;

    }


    //使能蓝牙控制器,mode是蓝牙模式,如果想要动态改变蓝牙模式不能直接调用该函数,

    //应该先用disable关闭蓝牙再使用该API来改变蓝牙模式

    ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);

    if (ret) {

        ESP_LOGE(GATTC_TAG, '%s enable controller failed: %sn', __func__, esp_err_to_name(ret));

        return;

    }

    //初始化蓝牙并分配系统资源,它应该被第一个调用

    /*

    蓝牙栈bluedroid stack包括了BT和BLE使用的基本的define和API

    初始化蓝牙栈以后并不能直接使用蓝牙功能,

    还需要用FSM管理蓝牙连接情况

    */

    ret = esp_bluedroid_init();

    if (ret) {

        ESP_LOGE(GATTC_TAG, '%s init bluetooth failed: %sn', __func__, esp_err_to_name(ret));

        return;

    }

    //使能蓝牙栈

    ret = esp_bluedroid_enable();

    if (ret) {

        ESP_LOGE(GATTC_TAG, '%s enable bluetooth failed: %sn', __func__, esp_err_to_name(ret));

        return;

    }


    //建立蓝牙的FSM(有限状态机

    //这里使用回调函数来控制每个状态下的响应,需要将其在GATT和GAP层的回调函数注册

    /*esp_gattc_cb和esp_gap_cb处理蓝牙栈可能发生的所有情况,达到FSM的效果*/

    ret = esp_ble_gap_register_callback(esp_gap_cb);

    if (ret){

        ESP_LOGE(GATTC_TAG, 'gap register error, error code = %x', ret);

        return;

    }

    ret = esp_ble_gattc_register_callback(esp_gattc_cb);

    if(ret){

        ESP_LOGE(GATTC_TAG, 'gattc register error, error code = %x', ret);

        return;

    }


    //下面创建了BLE GATT服务A、B、C,相当于3个独立的应用程序

    ret = esp_ble_gattc_app_register(PROFILE_A_APP_ID);

    if (ret){

        ESP_LOGE(GATTC_TAG, 'gattc app register error, error code = %x', ret);

        return;

    }

    ret = esp_ble_gattc_app_register(PROFILE_B_APP_ID);

    if (ret){

        ESP_LOGE(GATTC_TAG, 'gattc app register error, error code = %x', ret);

        return;

    }

    ret = esp_ble_gattc_app_register(PROFILE_C_APP_ID);

    if (ret){

        ESP_LOGE(GATTC_TAG, 'gattc app register error, error code = %x', ret);

        return;

    }

    /*

    设置了MTU的值(经过MTU交换,从而设置一个PDU中最大能够交换的数据量)。

    例如:主设备发出一个1000字节的MTU请求,但是从设备回应的MTU是500字节,那么今后双方要以较小的值500字节作为以后的MTU。

    即主从双方每次在做数据传输时不超过这个最大数据单元。

    */

    ret = esp_ble_gatt_set_local_mtu(200);

    if (ret){

        ESP_LOGE(GATTC_TAG, 'set local  MTU failed, error code = %x', ret);

    }.......

四、应用程序配置文件

应用程序配置文件是一种对功能进行分组的方法。它们的设计使每个应用程序配置文件连接到一个对等设备,这样同一个 ESP32 可以通过为每个设备分配一个应用程序配置文件连接到多个设备。每个应用程序配置文件都会创建一个 GATT 接口以连接到其他设备。应用程序配置文件由 ID 号定义,在此示例中有三个配置文件:


#define PROFILE_NUM 3#define PROFILE_A_APP_ID 0  #define PROFILE_B_APP_ID 1#define PROFILE_C_APP_ID 2

该esp_ble_gattc_app_register()函数用于将每个应用程序配置文件注册到 BLE 堆栈。注册操作会生成一个 GATT 接口,该接口作为注册事件中的参数返回。此外,每个应用程序配置文件还由一个结构定义,该结构可用于在堆栈传播新数据时保持应用程序的状态并更新其参数。


代码中的应用程序配置文件是gattc_profile_inst结构的实例。有关详细信息,请参见应用程序配置文件在GATT客户实例演练。


五、扫描

5.1 设置扫描参数

参见第设置扫描参数在GATT客户实例演练。


5.2 开始扫描

参阅Section开始扫描在GATT客户实例演练。


5.3 获取扫描结果

参阅Section获取扫描结果在GATT客户实例演练。


5.4 名称比较

首先,从广告数据中提取设备名称并存储在adv_name变量中:


adv_name = esp_ble_resolve_adv_data(scan_result->scan_rst.ble_adv, ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len);

然后,将找到的设备名称与客户端想要连接的服务器名称进行比较。服务器名称在remote_device_name数组中定义:


static const char remote_device_name[3][20] = {'ESP_GATTS_DEMO_1', 'ESP_GATTS_DEMO_2', “ESP_GATTS_DEMO_3'};

名称比较发生如下:


 if (strlen(remote_device_name[0]) == adv_name_len && strncmp((char *)adv_name, remote_device_name[0], adv_name_len) == 0) {

                 if (find_device_1 == false) {

                     find_device_1 = true;

                     ESP_LOGI(GATTC_TAG, 'Searched device %s', remote_device_name[0]);

                     memcpy(gl_profile_tab[PROFILE_A_APP_ID].remote_bda, scan_result->scan_rst.bda, 6);

                 }

                 break;

             }

             else if (strlen(remote_device_name[1]) == adv_name_len && strncmp((char *)adv_name, remote_device_name[1], adv_name_len) == 0) {

                 if (find_device_2 == false) {

                     find_device_2 = true;

                     ESP_LOGI(GATTC_TAG, 'Searched device %s', remote_device_name[1]);

                     memcpy(gl_profile_tab[PROFILE_B_APP_ID].remote_bda, scan_result->scan_rst.bda, 6);

                 }

             }

             else if (strlen(remote_device_name[2]) == adv_name_len && strncmp((char *)adv_name, remote_device_name[2], adv_name_len) == 0) {

                 if (find_device_3 == false) {

                     find_device_3 = true;

                     ESP_LOGI(GATTC_TAG, 'Searched device %s', remote_device_name[2]);

                     memcpy(gl_profile_tab[PROFILE_C_APP_ID].remote_bda, scan_result->scan_rst.bda, 6);

                 }

                 break;

             }                                    

如果找到的任何设备名称对应于远程设备名称,find_device_X则设置该标志并将远程设备的地址存储在gl_profile_tab表中。设置所有标志后,客户端将停止扫描并连接到远程设备。


六、连接到远程设备

6.1 连接到第一个远程设备

找到所有设备后,客户端将停止扫描:


if (find_device_1 && find_device_2 && find_device_3 && stop_scan == false {

    stop_scan = true;

    esp_ble_gap_stop_scanning();

    }

扫描停止触发一个ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT事件,该事件用于打开与第一个远程设备的连接。一旦客户端搜索服务、获取特征并在第一个设备上注册通知,第二个和第三个设备就会建立连接。此工作流旨在测试每个远程设备之间的通信是否正常工作,然后再尝试连接到下一个设备,或者如果出现错误,请跳到下一个设备。


case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:

    if (param->scan_stop_cmpl.status != ESP_BT_STATUS_SUCCESS){

        ESP_LOGE(GATTC_TAG, 'Scan stop failed');

        break;

    }

    ESP_LOGI(GATTC_TAG, 'Stop scan successfully');

    if (!stop_scan){

        ESP_LOGE(GATTC_TAG, 'Did not find all devices');

    }

    if (find_device_1){

        esp_ble_gattc_open(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, gl_profile_tab[PROFILE_A_APP_ID].remote_bda, true);

    }

    break;

使用esp_ble_gattc_open()GATT 接口、远程设备地址和布尔值的函数打开连接,直接连接设置为 true,后台自动连接设置为 false。为了断开物理连接,使用了 GAP API 函数esp_ble_gap_disconnect()。


连接到第一个设备时,ESP_GATTC_CONNECT_EVT会生成一个事件,该事件将转发到所有配置文件。它还触发ESP_GATTC_OPEN_EVT仅转发到 Profile A 事件处理程序或gattc_profile_a_event_handler()函数的事件。该事件检查连接是否成功打开,如果没有,则忽略该设备,客户端尝试打开与第二个设备的连接:


case ESP_GATTC_OPEN_EVT:

      if (p_data->open.status != ESP_GATT_OK){

          //open failed, ignore the first device, connect the second device

          ESP_LOGE(GATTC_TAG, 'connect device failed, status %d', p_data->open.status);

          if (find_device_2){

              esp_ble_gattc_open(gl_profile_tab[PROFILE_B_APP_ID].gattc_if, gl_profile_tab[PROFILE_B_APP_ID].remote_bda, true);

          }

          break;

      }

如果连接成功,客户端保存连接 ID,打印远程设备信息并将 MTU 大小配置为 200 字节。


 gl_profile_tab[PROFILE_A_APP_ID].conn_id = p_data->open.conn_id;

 ESP_LOGI(GATTC_TAG, 'ESP_GATTC_OPEN_EVT conn_id %d, if %d, status %d, mtu %d', p_data->open.conn_id, gattc_if, p_data->open.status, p_data->open.mtu);

 ESP_LOGI(GATTC_TAG, 'REMOTE BDA:');

 esp_log_buffer_hex(GATTC_TAG, p_data->open.remote_bda, sizeof(esp_bd_addr_t));

[1] [2]
关键字:ESP32  BLE 引用地址:ESP32学习笔记(34)——BLE一主多从连接

上一篇:ESP32学习笔记(35)——蓝牙MAC地址
下一篇:ESP32学习笔记(33)——BLE GATT客户端发现服务和读写特征值

小广播
最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

厂商技术中心

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

 
机器人开发圈

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2026 EEWORLD.com.cn, Inc. All rights reserved