文章目录
1 工程简介
本次实验使用开发板如下
参考工程
- 工程 ble_spp_client :为客户端,作为扫描,SPP 即 Serial Port Profile
- 工程 ble_spp_server:为服务端,作为广播,SPP 即 Serial Port Profile
基本功能1:客户端串口输入数据会通过蓝牙将数据发送给服务端并由串口打印出来;服务端串口输入数据会通过蓝牙将数据发送给客户端并由串口打印出来;
2 工程分析
2.1 工程 ble_spp_client 分析
2.1.1 初始化分析
从函数 app_main 开始
- 初始化内存
- 初始化bt controller并使能
- 初始化bluedroid并使能
- ble_client_appRegister
在该函数下实现如下功能- GAP下注册扫描回调函数。
esp_ble_gap_register_callback(esp_gap_cb))
- GATT下注册回调函数。
esp_ble_gattc_register_callback(esp_gattc_cb))
- GATT下设置MTU(Maximum Transmission Unit)值。
- 创建client queue、spp_client_reg_task。
- GAP下注册扫描回调函数。
- 初始化串口 spp_uart_init
在该函数下实现如下功能- 初始化串口及串口中断、创建串口队列和串口任务。
2.1.2 两BLE扫描连接、配置、参数同步分析
—> 表示前面的函数直接调用后面的函数
- - - > 表示前面的函数执行后触发后面的函数
-
app_main()
—>ble_client_appRegister()
—>esp_ble_gattc_app_register(PROFILE_APP_ID)
(产生ESP_GATTC_REG_EVT
事件)- - - >esp_gattc_cb()
—>gattc_profile_event_handler()
。完成扫描参数的配置。static void gattc_profile_event_handler(...) { switch (event) { case ESP_GATTC_REG_EVT: esp_ble_gap_set_scan_params(&ble_scan_params); break;
-
esp_ble_gap_set_scan_params(&ble_scan_params)
(产生ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT
事件)- - ->esp_gap_cb()
。开始扫描static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { switch(event){ case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: { esp_ble_gap_start_scanning(duration); break; }
-
esp_ble_gap_start_scanning()
(产生ESP_GAP_BLE_SCAN_START_COMPLETE_EVT
事件) - - - >esp_gap_cb()
。这里没有执行任何函数,在实际工程中可根据需求添加相关函数static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { switch(event){ case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: //scan start complete event to indicate scan start successfully or failed if ((err = param->scan_start_cmpl.status) != ESP_BT_STATUS_SUCCESS) { ESP_LOGE(GATTC_TAG, "Scan start failed: %s", esp_err_to_name(err)); break; } ESP_LOGI(GATTC_TAG, "Scan start successed"); break;
-
esp_ble_gap_start_scanning()
(产生ESP_GAP_BLE_SCAN_RESULT_EVT
事件) - - - >esp_gap_cb()
。扫描开始后会搜索空中的BLE蓝牙广播数据,static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { switch(event){ case ESP_GAP_BLE_SCAN_RESULT_EVT: { esp_ble_gap_cb_param_t *scan_result = (esp_ble_gap_cb_param_t *)param; switch (scan_result->scan_rst.search_evt) { case ESP_GAP_SEARCH_INQ_RES_EVT: adv_name = esp_ble_resolve_adv_data(scan_result->scan_rst.ble_adv, ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len);//解析广播数据 if (adv_name != NULL) { // 判断名字是否为指定的名字 if ( strncmp((char *)adv_name, device_name, adv_name_len) == 0) { memcpy(&(scan_rst), scan_result, sizeof(esp_ble_gap_cb_param_t)); esp_ble_gap_stop_scanning(); } } } break; }
如果搜索不到指定的蓝牙,会一直搜索,串口数据打印如下(上诉代码删除了串口输出函数,详细请参考工程)
如果搜索到指定的蓝牙static const char device_name[] = "ESP_SPP_SERVER";
,会停止广播
-
esp_ble_gap_stop_scanning()
(产生ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT
事件) - - - >esp_gap_cb()
。停止扫描并根据搜索到的蓝牙广播进行连接。static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) { switch(event){ case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: ESP_LOGI(GATTC_TAG, "Scan stop successed"); if (is_connect == false) { ESP_LOGI(GATTC_TAG, "Connect to the remote device."); //Open a direct connection or add a background auto connection esp_ble_gattc_open(gl_profile_tab[PROFILE_APP_ID].gattc_if, scan_rst.scan_rst.bda, scan_rst.scan_rst.ble_addr_type, true); } break;
-
esp_ble_gattc_open()
(产生ESP_GATTC_CONNECT_EVT
事件)- - - >esp_gattc_cb()
—>gattc_profile_event_handler()
。完成连接static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param; switch (event) { case ESP_GATTC_CONNECT_EVT: spp_gattc_if = gattc_if; is_connect = true; spp_conn_id = p_data->connect.conn_id; memcpy(gl_profile_tab[PROFILE_APP_ID].remote_bda, p_data->connect.remote_bda, sizeof(esp_bd_addr_t)); // This function is called to get service from local cache.This function report service search result by a callback event, and followed by a service search complete event. esp_ble_gattc_search_service(spp_gattc_if, spp_conn_id, &spp_service_uuid); break;
-
esp_ble_gattc_search_service()
(产生ESP_GATTC_SEARCH_RES_EVT
事件)- - - >esp_gattc_cb()
—>gattc_profile_event_handler()
static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param; switch (event) { case ESP_GATTC_SEARCH_RES_EVT: spp_srv_start_handle = p_data->search_res.start_handle; spp_srv_end_handle = p_data->search_res.end_handle; break;
-
esp_ble_gattc_search_service()
(产生ESP_GATTC_SEARCH_CMPL_EVT
事件)- - - >esp_gattc_cb()
—>gattc_profile_event_handler()
。同步mtu的配置,需要提前调用函数esp_ble_gatt_set_local_mtu();
static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param; switch (event) { case ESP_GATTC_SEARCH_CMPL_EVT: //Configure the MTU size in the GATT channel. This can be done,only once per connection. Before using, use esp_ble_gatt_set_local_mtu() to configure the local MTU size. esp_ble_gattc_send_mtu_req(gattc_if, spp_conn_id); break;
-
esp_ble_gattc_send_mtu_req()
(产生ESP_GATTC_CFG_MTU_EVT
事件)- - - >esp_gattc_cb()
—>gattc_profile_event_handler()
。static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param; switch (event) { case ESP_GATTC_CFG_MTU_EVT: if(esp_ble_gattc_get_db(spp_gattc_if, spp_conn_id, spp_srv_start_handle, spp_srv_end_handle, db, &count) != ESP_GATT_OK){ ESP_LOGE(GATTC_TAG,"%s:get db falied\n",__func__); break; } cmd = SPP_IDX_SPP_DATA_NTY_VAL; xQueueSend(cmd_reg_queue, &cmd, 10/portTICK_PERIOD_MS); break;
这里将队列发送后,有函数
spp_client_reg_task();
接收,队列参数为SPP_IDX_SPP_DATA_NTY_VAL
void spp_client_reg_task(void* arg) { uint16_t cmd_id; for(;;) { vTaskDelay(100 / portTICK_PERIOD_MS); if(xQueueReceive(cmd_reg_queue, &cmd_id, portMAX_DELAY)) { if(db != NULL) { if(cmd_id == SPP_IDX_SPP_DATA_NTY_VAL){ ESP_LOGI(GATTC_TAG,"Index = %d,UUID = 0x%04x, handle = %d \n", cmd_id, (db+SPP_IDX_SPP_DATA_NTY_VAL)->uuid.uuid.uuid16, (db+SPP_IDX_SPP_DATA_NTY_VAL)->attribute_handle); esp_ble_gattc_register_for_notify(spp_gattc_if, gl_profile_tab[PROFILE_APP_ID].remote_bda, (db+SPP_IDX_SPP_DATA_NTY_VAL)->attribute_handle);
-
函数
esp_ble_gattc_register_for_notify(...)
将SPP_IDX_SPP_DATA_NTY_VAL
属性注册完成后,就可以接收蓝牙信息了static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param; switch (event) { case ESP_GATTC_NOTIFY_EVT: ESP_LOGI(GATTC_TAG,"ESP_GATTC_NOTIFY_EVT\n"); notify_event_handler(p_data); break;
在
notify_event_handler(p_data);
函数中可根据用户需求来处理数据。
2.1.3 蓝牙数据发送流程分析
-
在电脑串口助手客户端发送数据后,数据在uart_task函数被接收并通过蓝牙将数据发送给服务端
void uart_task(void *pvParameters) { for (;;) { //Waiting for UART event. if (xQueueReceive(spp_uart_queue, (void * )&event, (portTickType)portMAX_DELAY)) { switch (event.type) { //Event of UART receving data case UART_DATA: uart_read_bytes(...); //接收串口数据 esp_ble_gattc_write_char(...);//蓝牙发送数据
esp_ble_gattc_write_char()
(产生ESP_GATTC_WRITE_CHAR_EVT
事件) - - - >esp_gattc_cb()
—>gattc_profile_event_handler()
。static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param; switch (event) { case ESP_GATTC_WRITE_CHAR_EVT: ESP_LOGI(GATTC_TAG,"ESP_GATTC_WRITE_CHAR_EVT:status = %d,handle = %d", param->write.status, param->write.handle);
-
在电脑串口助手服务端发送数据后,客户端收到数据后会触发
ESP_GATTC_NOTIFY_EVT
事件。esp_gattc_cb()
—>gattc_profile_event_handler()
。static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) { esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param; switch (event) { case ESP_GATTC_NOTIFY_EVT: ESP_LOGI(GATTC_TAG,"ESP_GATTC_NOTIFY_EVT\n"); notify_event_handler(p_data); break;
在
notify_event_handler(p_data);
函数中可根据用户需求来处理数据。
2.2 工程 ble_spp_server 分析
与 《2.1 工程 ble_spp_client 分析》类似,这里省略。
2.3 属性(Attribute)分析
参考文章《esp32_bluetooth_architecture_cn》
我们把存有数据(即属性)的设备叫做服务器器 (Server),而将获取别人设备数据的设备叫做客户端 (Client)。下面是服务器器和客户端间的常用操作:
-
客户端给服务端发数据: 通过对服务器器的数据进行写操作 (Write),来完成数据发送工作。写操作分两种,一种是写入请求(Write Request),一种是写入命令 (Write Command),两者的主要区别是前者需要对方回复响应 (Write Response),而后者不需要对方回复响应。
-
服务端给客户端发数据: 主要通过服务端指示 (Indication) 或者通知 (Notification) 的形式,实现将服务端更新的数据发给客户端。写操作类似,指示和通知的主要区别是前者需要对方设备在收到数据指示后,进行回复(Confirmation)。
-
客户端也可以主动通过读操作读取服务端的数据:
2.3.1 服务特征属性表
在程序定义的属性表只有一个数组,但在该数组里分从属关系,如下表所示。
主要服务UUID、特征声明UUID使用官方定义的UUID,特征声明下的值UUID可使用官方定义的UUID,如电量值,也可以使用自定义UUID。
2.3.2 服务端(server)属性分析
-
在
app_main ---> esp_ble_gatts_app_register(ESP_SPP_APP_ID);
注册app之后,会产生ESP_GATTS_REG_EVT
事件,在该事件下创建属性表。static void gatts_profile_event_handler(...) { switch (event) { case ESP_GATTS_REG_EVT: esp_ble_gatts_create_attr_tab(spp_gatt_db, gatts_if, SPP_IDX_NB, SPP_SVC_INST_ID);
-
执行 esp_ble_gatts_create_attr_tab 函数后,会产生 ESP_GATTS_CREAT_ATTR_TAB_EVT 事件,在该事件下开始服务
static void gatts_profile_event_handler(...) { switch (event) { case ESP_GATTS_CREAT_ATTR_TAB_EVT: esp_ble_gatts_start_service(spp_handle_table[SPP_IDX_SVC]);
开始服务并连接成功后就可以使用指示 (Indication) 或者通知 (Notification) 的形式,实现将服务端更新的数据发给客户端。例如
2.3.3 客户端(client)属性分析
-
客户端连接成功后,会搜索服务
static void gattc_profile_event_handler(...) { switch (event) { case ESP_GATTC_CONNECT_EVT: esp_ble_gattc_search_service(spp_gattc_if, spp_conn_id, &spp_service_uuid);
-
执行搜索服务函数
esp_ble_gattc_search_service
后,会产生ESP_GATTC_SEARCH_RES_EVT
和ESP_GATTC_SEARCH_CMPL_EVT
事件。static void gattc_profile_event_handler(...) { switch (event) { case ESP_GATTC_SEARCH_RES_EVT: spp_srv_start_handle = p_data->search_res.start_handle; spp_srv_end_handle = p_data->search_res.end_handle; break; case ESP_GATTC_SEARCH_CMPL_EVT: esp_ble_gattc_send_mtu_req(gattc_if, spp_conn_id); break;
-
执行函数
esp_ble_gattc_send_mtu_req
后,会产生ESP_GATTC_CFG_MTU_EVT
事件。在该事件下获取服务端属性数据,客户端是如何知道服务端属性数据呢?是由步骤1esp_ble_gattc_search_service
函数获取的,从该函数第三个参数描述(filter_uuid: a UUID of the service application is interested in.If Null, discover for all services.)中可知。static void gattc_profile_event_handler(...) { switch (event) { case ESP_GATTC_CFG_MTU_EVT: esp_ble_gattc_get_db(spp_gattc_if, spp_conn_id, spp_srv_start_handle, spp_srv_end_handle, db, &count); cmd = SPP_IDX_SPP_DATA_NTY_VAL; xQueueSend(cmd_reg_queue, &cmd, 10/portTICK_PERIOD_MS);
在获取服务端属性数据后,对服务特定属性功能的通知进行注册,之后就可以实现与服务端通信了。
void spp_client_reg_task(void* arg) { uint16_t cmd_id; for(;;) { vTaskDelay(100 / portTICK_PERIOD_MS); if(xQueueReceive(cmd_reg_queue, &cmd_id, portMAX_DELAY)) { if(db != NULL) { if(cmd_id == SPP_IDX_SPP_DATA_NTY_VAL){ esp_ble_gattc_register_for_notify(spp_gattc_if, gl_profile_tab[PROFILE_APP_ID].remote_bda, (db+SPP_IDX_SPP_DATA_NTY_VAL)->attribute_handle); }