ESP32-BLE-NIMBLE蓝牙透传DEMO

最近在对ESP32-S3的蓝牙调试使用,现记录一些关于蓝牙的理解与使用例子方便后续回顾

因为ESP32-S3只支持BLE,先只对BLE进行了解

协议栈结构

对于蓝牙整体的协议栈能找到很多的图来表示
例如
ce61b586-ab40-413c-abdc-c973abe54b42
整体的蓝牙协议为分成了很多层
要完善掌握整个蓝牙协议是要花很多的功夫的,难点肯定也很多,对于当前直接使用蓝牙的应用来说,并不需要去了解,物理层的信号是如何传输
以及底层Controller层的实现,只需要了解Host层里面的部分概念,知道每部分是干嘛用的就可以了,方便在调用的代码的时候可以对应起来就可以。
可以代码跑起来,再根据实际需要再深入了解相关的部分,这样效率相对会更高一些
像实现简单的蓝牙透传的示例,前期只需进行简单的通信连接的话,只需要了解GATT,GAP的概念就可以了

GAP(Generic Access Profile)

主要负责处理,前期蓝牙连接,断开连接等操作的事件

GATT(Generic Attribute Profile)

应用的数据交换应该就是发生在这里
GATT包含Service而又包含Characteristic,且Service跟Characteristic都有自己的UUID,且不相互关联
Characteristic就是我们负责数据通信的最基本的单元,
通过去Characteristic去赋权限,并添加相应的响应操作就实现了最基本的数据读写
2f371bef-66c0-41da-919e-ea33a638ad2a

默认状态下,通知每一个蓝牙连都会带一个GAP的服务(包括读取蓝牙名字的Characteristic),以及一个GATT的默认服务(这个服务没有只针对透传的应用的话也是可以正常使用,会少于一些扩展性,这个扩展性如何实现暂不了解)

DEMO示例

连接拓扑

ESP32-S3是服务器,手机是客户端

工程中配置好相对BLE的配置,不然include头文件跟调用的时候会发生错误

4cc76952-c70c-476e-b872-bb89553cfe96

添加被主调用函数,完成相应的初始化及配置相关联

void app_ble_add_task(void)
{
    nimble_port_init(); //初始化必须,加载NIMBLE这个协议栈

    ble_hs_cfg.sync_cb = ble_app_on_sync; //同步回调,开启蓝牙初始相关操作,主要是设置名字跟发起广播,并添加GAP服务的状态响应函数,比如可以使系统知道当前的蓝牙连接状态

    ble_svc_gap_init(); // 默认GAP服务,主要是蓝牙名称的读
    ble_svc_gatt_init(); //默认GATT,这个函数不注册也可以正常使用

    ble_gatts_count_cfg(user_ble_svc_gatt_defs); //自定义的透传蓝牙服务,这个是主要添加的相关代码
    ble_gatts_add_svcs(user_ble_svc_gatt_defs);

    nimble_port_freertos_init(ble_spp_server_host_task); // 创建蓝牙响应任务
	
	xTaskCreate(ble_notify_task, "ble_notify_task", 4096, NULL, 5, NULL);//透传轮咨任务,不加这个任务也不影响读写的响应
}

//esp32 示例代码,freertos的一个一直运行的响应任务
void ble_spp_server_host_task(void *param)
{
    MODLOG_DFLT(INFO, "BLE Host Task Started");
    /* This function will return only when nimble_port_stop() is executed */
    nimble_port_run();

    nimble_port_freertos_deinit();
}

初始同步函数的定义,主要是设置名称与广播类型设置,GAP状态回调绑定,并开启广播

#define BLE_NAME    "HELLO_BLE"
static void ble_app_on_sync(void)
{
    ESP_LOGI(TAG, "BLE host synced, start advertising");

    struct ble_gap_adv_params adv_params = {0};
    adv_params.conn_mode = BLE_GAP_CONN_MODE_UND;
    adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN;

    struct ble_hs_adv_fields fields = {0};
    fields.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP;
    fields.name = (uint8_t *)BLE_NAME;
    fields.name_len = strlen(BLE_NAME);
    fields.name_is_complete = 1;
    ble_gap_adv_set_fields(&fields);
    
    ble_gap_adv_start(BLE_OWN_ADDR_PUBLIC, NULL, BLE_HS_FOREVER, &adv_params, ble_server_gap_event, NULL);
}

//GAP回调设置,主要是告诉系统连接状态及状态更新后添加一些自定义的响应操作
static int ble_server_gap_event(struct ble_gap_event *event, void *arg)
{
    struct ble_gap_conn_desc desc;
    int rc;

    switch (event->type) {
    case BLE_GAP_EVENT_CONNECT:
		is_ble_connected = 1;
		current_conn_handle = event->connect.conn_handle;
        ble_gap_adv_stop();
        return 0;
    case BLE_GAP_EVENT_DISCONNECT:
		is_ble_connected = 0;
        ble_app_on_sync();//重新广播就可以,用重新广播的操作函数,会更合适一些
        return 0;
    case BLE_GAP_EVENT_CONN_UPDATE:
        return 0;
    case BLE_GAP_EVENT_ADV_COMPLETE:
        return 0;
    case BLE_GAP_EVENT_MTU:
        ESP_LOGI("BLE", "MTU update: conn_handle=%d mtu=%d",
                    event->mtu.conn_handle,
                    event->mtu.value);
        return 0;
    default:
        return 0;
    }
}

效果如下

4bae3f57-e1e5-4daa-a7b0-b890a24f55fb

自定义服务,及characteristics用于当前透传的蓝牙应用实现

需要赋值权限,读,写,NOTIFY

#define BLE_SVC_SPP_UUID16                  0xABF0
#define BLE_SVC_SPP_CHR_UUID16              0xABF1

static const struct ble_gatt_svc_def user_ble_svc_gatt_defs[] = {
    {
        .type = BLE_GATT_SVC_TYPE_PRIMARY,
        .uuid = BLE_UUID16_DECLARE(BLE_SVC_SPP_UUID16),
        .characteristics = (struct ble_gatt_chr_def[])
        {
            {
                .uuid = BLE_UUID16_DECLARE(BLE_SVC_SPP_CHR_UUID16),
                .access_cb = ble_svc_gatt_handler,
                .val_handle = &current_notify_handle,
                .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_NOTIFY,
            },
            {
                0,
            }
        },
    },
    {
        0,
    },
};

效果如下

3b0c8e0d-55aa-48cb-b7d1-f3f2d06e2914

Characteristic读写回调响应

static int  ble_svc_gatt_handler(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg)
{
    switch (ctxt->op) {
    case BLE_GATT_ACCESS_OP_READ_CHR:
        MODLOG_DFLT(INFO, "Callback for read");
        int read_id = 0x1234;
        uint8_t r_tmp[2];
        r_tmp[0] = read_id;
        r_tmp[1] = read_id >> 8;
        os_mbuf_append(ctxt->om, r_tmp, 2);
        break;

    case BLE_GATT_ACCESS_OP_WRITE_CHR:
        MODLOG_DFLT(INFO, "Data received in write event,conn_handle = %x,attr_handle = %x", conn_handle, attr_handle);
        int len = OS_MBUF_PKTLEN(ctxt->om);
        uint8_t w_tmp[100];
        os_mbuf_copydata(ctxt->om, 0, len, w_tmp);
        printf("receive data len:%d\n", len);
        for(int i = 0; i < len; i++)
        {
            printf("0x%02x\n", w_tmp[i]);
        }
        break;

    default:
        MODLOG_DFLT(INFO, "\nDefault Callback");
        break;
    }
    return 0;
}

对于问询式的通信方式用读写方式可以实现基本通信,但用于透传这样的应用,这样就有些不太够用,需要客户端发起问询也可以返回数据

介于这个问题,需要添加一个NOTIFY的任务,实时的轮询等有数据就通过NOTIFY的形式将数据发送出去,不需要客户端发起读数据的请求
notify发送数据的时候,需要知道一个conn_handle跟一个notify_handle这两个值,这两个值相当于是关联操作的characteristic。
notity_handle是定义characteristic时定义的, conn_handle是连接的时候,或者读写响应的时候,可以记录出来,
具体的参数区别,目前对这些参数的含义还不是很了解,照这样用可以实现当前NOTIFY的数据传输,手机端需要开启NOTIFY的开关,就可以收到定时接收的数据

void ble_notify_task(void *arg)
{
    uint32_t counter = 0;
    while (1) {
        if (current_conn_handle != 0 && is_ble_connected) { // 已连接
            char msg[100];
            sprintf(msg, "Count: %lu\n", counter++);
            struct os_mbuf *om = ble_hs_mbuf_from_flat(msg, strlen(msg));
            ble_gatts_notify_custom(current_conn_handle, current_notify_handle, om);
            printf("Notify: %s\n", msg);
        }
        vTaskDelay(pdMS_TO_TICKS(1000)); // 每秒发一次
    }
}

效果如下

81343e8d8a9132b35a4bd7d18494af9f

总结

实现简单的透传DEMO,可以不用太了解蓝牙的协议栈的整体结构,有个初略的认识就可以,对相关概念有一点了解,先将代码跑起来就可以
像UUID,以及不同种类UUID的一些官方的资料,暂不了解,
官方对常用的一些应用服务,做了专门的UUID分配也需要去,做得识别好一些,也需要去了解这部分的内容,像当前的DEMO,手机上的应用并不能识别当前自定义服务ID
可以多查看下官方的DEMO代码,对修改代码有所帮助

posted @ 2025-10-24 08:52  cau_par  阅读(76)  评论(0)    收藏  举报