【ESP32蓝牙通信】gatt_client 和 gatt_server 调试

项目中需要用到ESP32的蓝牙通信,查资料知道。当esp32作为客户端 client时,可以连接多个设备,当作为服务器端server时,只能被一个设备连接(这是我的理解,若有误请之指出)。以下根据,esp-idf的例程的调试过程总结如下:

蓝牙协议基本概念

创建自定义表的时候,要符合蓝牙基本原理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JZfFhYrR-1668508416466)(H:\typora_picture\Ble_bluetooth\image-20221110104111685.png)]

->service1
	-> characterA
		->特性声明
		->特性数值
		->特性描述符
	-> characterB
		->特性声明
		->特性数值
		->特性描述符
	-> …………

->  …………………………
    
数据以属性(attribute)的方式存在,每条属性由四个元素组成:
->属性句柄
->属性类型(UUID表示的含义,温度、湿度、电量等)
->属性值
->属性的权限(读、写、通知)

【概述】

  • ESP32 作为 gatt_server ,手机app作为gatt_client,ESP32采集温湿度传感器的值,然后通过蓝牙协议将数据传到手机端(或者其它支持蓝牙的MCU端)

【数据收发】

  • ESP32被写数据:手机app给ESP32写数据,触发 ESP_GATTS_WRITE_EVT事件,将数据写入相应的 character_value 中去
  • ESP32被读数据:手机APP读取ESP32的数据,触发 ESP_GATTS_READ_EVT事件,读取相应的 character_value 的值

【源码中问题】

  • 比如,我想读取 特性A的 characterA_value 的实时的值,因此在代码中就需要开启一个任务,实时改变 characterA_value 的值。库函数中的这个函数可以改变 value 的值,esp_ble_gatts_set_attr_value(),因此需要在其他任务中调用这个函数。 注意: 调用这个函数时,必须保证蓝牙已经建立连接,否则调试会报错。
  • 源码中ESP_GATTS_READ_EVT事件下面是空的,为何手机还会读到数据?比如手机APP读 characterA_value 的值,characterA_value 的初值为 0x1234,则读到的值为value的初始值, 手机读取数据这个事发生以后,才会触发ESP_GATTS_READ_EVT事件。并不是读到的数是靠 事件底下的处理过程给的。

ESP32 蓝牙客户端和服务端

esp-idfbluetooth 例程 demo 说明:

程序路径: …\esp-idf-master\examples\bluetooth\bluedroid\ble

gatt_servergatt_client: gatt_server 广播 adv 数据, client 连接 server,client_demo 在连接成功 server_demo 后,会使能 server_demo 中的 notify 通知提示,连接成功之后,二者可以互相交换数据。

参考资料: ESP32-C3 学习测试 蓝牙 篇(四、GATT Server 示例解析)

gatt_server_service_table:使用预定义的 attribute table 创建了一个 GATT database ,他扮演和 gatt_server 相同的角色,可以被 gatt_client 连接,连接成功后通知消息,并且在连接成功后,可以交换数据。

参考资料: ESP32-C3 学习测试 蓝牙 篇(七、GATT 数据通信 — 发送自定义数据)

ESP32 作为服务器调试

可以实现,别的设备连接ESP32,并获取ESP32采集到的数据。ESP32 为服务器,其他设备为客户端

属性表解析:

只添加了一个服务:

【service】
UUID:0x00FF     	UUID的值:primary service 0x28     	权限:只读
   【特征A声明】
	UUID:0x2803    	UUID的值:读、写、通知     	  权限:只读
		【特征A的值】
		UUID:0xFF01     	UUID的值:‘'yonghaohao'’     	权限:读写      最大字节:500
		【特征A的描述符】
		UUID:0x2902     	UUID的值:0x00 0x00     	       权限:读写     最大字节: 4
   【特征B声明】
	UUID:0x2803    	UUID的值:读      	  权限:只读
		【特征B的值】
		UUID:0xFF02     	UUID的值:‘'yonghaohao'’     	权限:读写      最大字节:500
   【特征C声明】
	UUID:0x2803    	UUID的值:读      	  权限:只读
		【特征C的值】
		UUID:0xFF03     	UUID的值:‘'yonghaohao'’     	权限:读写      最大字节:500

总结属性表:
在这里插入图片描述

测试结果:
在这里插入图片描述

当手机APP向esp32写数据时,触发 ESP_GATTS_WRITE_EVT 事件,然而在源码中,这个事件下的处理部分是空的,但是测试的时候,手机可以收到数据,那么手机是如何接收到数据的呢?参考博客的分析: ESP32-C3 学习测试 蓝牙 篇(七、GATT 数据通信 — 发送自定义数据),确实存在以下问题。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6mUxprKB-1668508416465)(H:\typora_picture\Ble_bluetooth\image-20221109180658096.png)]

总结:参考以上博客总结来看为以下几点:

ESP32 作为客户端调试

在GAP通信角色中,中心设备去扫描和连接外围设备。所以外围设备是server端,中心设备是 client 端。

过程:客户端扫描设备,搜索相关服务器的服务和特征,当搜索到感兴趣的服务器时,与服务器建立连接,并执行服务搜索。最后,客户端在找到的服务中查找特定的特征,若找到, 则获取特征值并注册该特征的通知。这是通过注册一个应用程序配置文件,并按照一些列事件俩配置所需的GAP个GATT参数来完成的。

client 连接 server ,会查找某个服务的 UUID,如在server中定义 serviceA_UUID = 0xFF01,则在 client 端需要根据 0xFF01 查找 server 端的是否有这个服务,查找时会触发事件 ESP_GATTC_DIS_SRVC_CMPL_EVT

参考资料:【ESP32蓝牙的Gatt Client的例子演练】

ESP32 作为客户端,他扫描连接外围设备, 并连接到一个预定义的服务,然后 客户端搜索可用特征并订阅已知特征,以便接受通知(notify)或指示(indicate)。该例程可以注册一个应用程序配置文件并且初始化一系列事件,这些时间可用于配置文件GP参数并处理扫描、连接外围设备和读写特征等事件。

NVS初始化:该库允许将 key-value 保存在闪存中,比如保存 wifi 的 SSID 和 password

GAP和GATT事件处理程序是用来捕获BLE堆栈生成事件并执行函数来配置应用程序参数的函数。GAP事件处理程序负责扫描和连接到服务器,GATT处理程序负责管理客户机连接到服务器后发送的时间,比如搜索服务和读写数据。

应用程序配置文件:是为一个或者多个服务器应用程序设计的功能进行分组的一种方式。如,可以将应用程序配置文件连接到心率传感器,并将另一个配置文件连接到温度传感器。每个应用程序配置文件创建一个GATT接口来连接到其他设备,代码中是 gattc_profile_inst

分别用两个设备,测试 gatt_client 和 gatt_server,注意,client 连接 server的代码中,device_name 和 service 以及 characteri 的 uuid 要对应:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wh7oLoQB-1668508416466)(H:\typora_picture\Ble_bluetooth\image-20221115171058297.png)]

为什么 client 可以直接写数据呢?在代码中找原因:

参考 Gatt_Client_Example_Walkthrough手册,找到了原因,手册中说, esp_ble_gattc_send_mtu_req()函数触发之后,会自动调用搜索服务的功能,然后就触发了 ESP_GATTC_DIS_SRVC_CMPL_EVT事件,然后就一步步触发相应的事件,最后调用 esp_ble_gattc_write_char()函数,写数据。

Gatt_Client_Example_Walkthrough 手册的路径:
gatt_client\tutorial

若不方便下载,请私信我获取;

  • 2
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
ESP-IDF是一款用于ESP32和ESP8266的官方开发框架,其中包含了丰富的蓝牙接口。下面是一个简单的蓝牙数据发送接收例程。 发送数据: ```c #include <stdio.h> #include "esp_bt.h" #include "esp_bt_main.h" #include "esp_gap_ble_api.h" #define GATTS_TAG "GATTS_DEMO" //定义GATT服务UUID #define GATTS_SERVICE_UUID_TEST_A 0x00FF //定义GATT特征UUID #define GATTS_CHAR_UUID_TEST_A 0xFF01 static uint8_t adv_config_done = 0; //GATT服务定义 static uint8_t test_service_uuid[16] = { /* LSB <--------------------------------------------------------------------------------> MSB */ //first uuid, 16bit, [12],[13] is the value 0x1b,0x9a, //second uuid, 16bit, [12],[13] is the value 0x12,0x34, //third uuid, 32bit, [12],[13],[14],[15] is the value, 0x0000XXXX is a placeholder 0x11,0x22,0x33,0x44, 0x00,0x00,0x00,0x00, }; //GATT特征定义 static uint8_t test_char_uuid[16] = { /* LSB <--------------------------------------------------------------------------------> MSB */ //first uuid, 16bit, [12],[13] is the value 0x1b,0x9a, //second uuid, 16bit, [12],[13] is the value 0x12,0x34, //third uuid, 32bit, [12],[13],[14],[15] is the value, 0x0000XXXX is a placeholder 0x11,0x22,0x33,0x44, 0x00,0x00,0x00,0x00, }; //GATT特征值 static uint8_t test_char_value[20] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09}; //GATT事件处理函数 static esp_gatts_cb_event_t gatts_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: //注册GATT事件 esp_ble_gap_set_device_name("ESP_GATTS_DEMO"); esp_ble_gatts_create_attr_tab((esp_gatts_attr_db_t *)param->create.db, gatts_if, param->create.svc_inst_id, param->create.num_handle); break; case ESP_GATTS_CREAT_ATTR_TAB_EVT: if (param->add_attr_tab.status != ESP_GATT_OK) { printf("create attribute table failed, error code=0x%x\n", param->add_attr_tab.status); } else if (param->add_attr_tab.num_handle != ESP_GATT_AUTO_RSP) { //获取特征值句柄 printf("create attribute table success, the number handle = %d\n", param->add_attr_tab.num_handle); } else { printf("create attribute table success, the number handle = %d\n", param->add_attr_tab.num_handle); } //注册服务 esp_ble_gatts_start_service(param->add_attr_tab.handles[0]); break; case ESP_GATTS_CONNECT_EVT: printf("a remote device connected\n"); break; case ESP_GATTS_DISCONNECT_EVT: printf("a remote device disconnected\n"); break; case ESP_GATTS_WRITE_EVT: if (param->write.handle == test_handle_table[IDX_CHAR_VAL_A]) { memcpy(test_char_value, param->write.value, param->write.len); printf("write test_char_value=%x,%x,%x,%x,%x,%x,%x,%x,%x,%x\n", test_char_value[0],test_char_value[1],test_char_value[2],test_char_value[3], test_char_value[4],test_char_value[5],test_char_value[6],test_char_value[7], test_char_value[8],test_char_value[9]); } //确认写入数据 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: esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, NULL); break; default: break; } return ESP_GATT_OK; } void gatt_server_init(void) { //GATT参数初始化 esp_err_t ret; esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); ret = esp_bt_controller_init(&bt_cfg); if (ret) { printf("%s initialize controller failed\n", __func__); return; } ret = esp_bt_controller_enable(ESP_BT_MODE_BTDM); if (ret) { printf("%s enable controller failed\n", __func__); return; } ret = esp_bluedroid_init(); if (ret) { printf("%s init bluetooth failed\n", __func__); return; } ret = esp_bluedroid_enable(); if (ret) { printf("%s enable bluetooth failed\n", __func__); return; } //GATT事件注册 esp_ble_gatts_register_callback(gatts_event_handler); esp_ble_gatts_app_register(GATTS_APP_ID); //创建服务表 esp_ble_gatts_create_service(GATTS_IF_NUM, test_service_uuid, GATTS_NUM_HANDLE_TEST_A); } void app_main() { gatt_server_init(); //循环发送数据 while (1) { esp_ble_gatts_set_attr_value(test_handle_table[IDX_CHAR_VAL_A], sizeof(test_char_value), test_char_value); vTaskDelay(1000 / portTICK_PERIOD_MS); } } ``` 接收数据: ```c #include <stdio.h> #include "esp_bt.h" #include "esp_bt_main.h" #include "esp_gap_ble_api.h" #define GATTC_TAG "GATTC_DEMO" static uint8_t remote_bda[ESP_BD_ADDR_LEN] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; static esp_gattc_char_elem_t *char_elem_result = NULL; static esp_gattc_descr_elem_t *descr_elem_result = NULL; static uint16_t char_handle = 0; static uint16_t descr_handle = 0; //GATT事件处理函数 static void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { switch (event) { case ESP_GATTC_REG_EVT: //注册GATT事件 esp_ble_gap_set_scan_params(&ble_scan_params); break; case ESP_GATTC_CONNECT_EVT: printf("a GATT client connected\n"); esp_ble_gattc_search_service(gattc_if, param->connect.conn_id, NULL); break; case ESP_GATTC_SEARCH_RES_EVT: //搜索到服务 printf("search service uuid: "); esp_log_buffer_hex(GATTC_TAG, param->search_res.srvc_id.uuid.uuid, param->search_res.srvc_id.uuid.len); printf("search service start handle %d end handle %d\n", param->search_res.start_handle, param->search_res.end_handle); //搜索特征 esp_ble_gattc_get_char_by_uuid(gattc_if, param->search_res.conn_id, &param->search_res.srvc_id, &char_uuid, char_elem_result, &count, 0); break; case ESP_GATTC_SEARCH_CMPL_EVT: //搜索完成 printf("search complete\n"); break; case ESP_GATTC_GET_CHAR_EVT: //获取特征 printf("get char %s, handle %d\n", char_elem_result->uuid.uuid.uuid16, char_elem_result->char_handle); char_handle = char_elem_result->char_handle; //获取特征描述符 esp_ble_gattc_get_descr_by_char_handle(gattc_if, param->search_res.conn_id, char_handle, &descr_uuid, descr_elem_result, &count, 0); break; case ESP_GATTC_GET_DESCR_EVT: //获取特征描述符 printf("get descr %s, handle %d\n", descr_elem_result->uuid.uuid.uuid16, descr_elem_result->descr_handle); descr_handle = descr_elem_result->descr_handle; //开启特征通知 esp_ble_gattc_register_for_notify(gattc_if, remote_bda, char_handle); break; case ESP_GATTC_REG_FOR_NOTIFY_EVT: //注册通知事件 printf("register for notify\n"); break; case ESP_GATTC_NOTIFY_EVT: //接收到通知 printf("receive notify value:"); esp_log_buffer_hex(GATTC_TAG, param->notify.value, param->notify.value_len); break; default: break; } } void gatt_client_init(void) { //GATT参数初始化 esp_err_t ret; esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); ret = esp_bt_controller_init(&bt_cfg); if (ret) { printf("%s initialize controller failed\n", __func__); return; } ret = esp_bt_controller_enable(ESP_BT_MODE_BTDM); if (ret) { printf("%s enable controller failed\n", __func__); return; } ret = esp_bluedroid_init(); if (ret) { printf("%s init bluetooth failed\n", __func__); return; } ret = esp_bluedroid_enable(); if (ret) { printf("%s enable bluetooth failed\n", __func__); return; } //GATT事件注册 esp_ble_gattc_register_callback(gattc_event_handler); esp_ble_gattc_app_register(0); //开始扫描 esp_ble_gap_start_scanning(10); } void app_main() { gatt_client_init(); } ``` 以上代码仅供参考,实际使用中需要根据具体情况进行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

积跬步、至千里

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值