文章目录

  • 1 工程简介
  • 2 工程分析
    • 2.1 工程 ble_spp_client 分析
      • 2.1.1 初始化分析
      • 2.1.2 两BLE扫描连接、配置、参数同步分析
      • 2.1.3 蓝牙数据发送流程分析
    • 2.2 工程 ble_spp_server 分析
    • 2.3 属性(Attribute)分析
      • 2.3.1 服务特征属性表
      • 2.3.2 服务端(server)属性分析
      • 2.3.3 客户端(client)属性分析
    • 2.4 具体函数详细分析

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
    在该函数下实现如下功能

    1. GAP下注册扫描回调函数。esp_ble_gap_register_callback(esp_gap_cb))
    2. GATT下注册回调函数。esp_ble_gattc_register_callback(esp_gattc_cb))
    3. GATT下设置MTU(Maximum Transmission Unit)值。
    4. 创建client queue、spp_client_reg_task。
  • 初始化串口 spp_uart_init
    在该函数下实现如下功能

    1. 初始化串口及串口中断、创建串口队列和串口任务。

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 failedif ((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 connectionesp_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 datacase 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)属性分析

  1. 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);
    
  2. 执行 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)属性分析

  1. 客户端连接成功后,会搜索服务

    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);
    
  2. 执行搜索服务函数 esp_ble_gattc_search_service 后,会产生 ESP_GATTC_SEARCH_RES_EVTESP_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;
    
  3. 执行函数 esp_ble_gattc_send_mtu_req 后,会产生 ESP_GATTC_CFG_MTU_EVT 事件。在该事件下获取服务端属性数据,客户端是如何知道服务端属性数据呢?是由步骤1 esp_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);}
    

2.4 具体函数详细分析

ESP32入门基础之ble spp client 和 ble spp server 的学习理解相关推荐

  1. ESP32入门基础之UDP和TCP实验

    文章目录 1 用户数据协议报UDP简介 1.1 UDP作为client进行数据收发实验 1.1.1 向app-wifi-udp-client工程添加udp client相关文件 1.1.2 网络调试助 ...

  2. ESP32入门基础之ESP32作为 WIFI Station去连接wifi热点

    文章目录 1 工程简介 1.1 在menuconfig配置WiFi账号.密码 1.2 在程序中配置WiFi账号.密码 1 工程简介 参考工程为乐鑫官方例程: 实验目标: ESP32作为WiFi sta ...

  3. ESP32入门基础之SNTP时间显示

    文章目录 1 实验简介 2 实验分析 1 实验简介 在esp-idf合集下就有该该工程,目录如下Espressif\frameworks\esp-idf-v4.4.2\examples\protoco ...

  4. 用LayaAir引擎开发HTML5的3D与VR游戏(入门基础)【面向JS开发者】-赖圆圆-专题视频课程...

    用LayaAir引擎开发HTML5的3D与VR游戏(入门基础)[面向JS开发者]-4626人已学习 课程介绍         全面介绍LayaAir引擎的3D游戏开发基础.学习在3DMax与Unity ...

  5. JS学习笔记——入门基础知识总结

    JS入门基础知识总结1 前言 基础背景知识 一.产生历史: 二.特点: 三.应用方向: 四.Javascript组成: JavaScript书写使用方式 一.行内式(了解即可,项目中不使用,日常练习尽 ...

  6. ESP32 入门笔记05: BLE 蓝牙客户端和服务器 (ESP32 for Arduino IDE)

    目录 1. BLE 服务器和客户端 2. GATT 协议 3. UUID 4. 项目概述 4.1 材料清单 4.2 ESP32 BLE 服务器 4.3 ESP32 BLE 客户端 参考资料 1. BL ...

  7. 大数据入门基础系列之初步认识大数据生态系统圈(博主推荐)

    不多说,直接上干货! 之前在微信公众平台里写过 大数据入门基础系列之初步认识hadoop生态系统圈 http://mp.weixin.qq.com/s/KE09U5AbFnEdwht44FGrOA 大 ...

  8. Java入门基础及面试100题--初入门

    Java入门基础及面试100题 注:适合应届毕业生或java初入门者 1.面向对象的特征有哪些方面? 答:面向对象的特征主要有以下几个方面: - 抽象:抽象是将一类对象的共同特征总结出来构造类的过程, ...

  9. 3.Jenkins入门基础使用与Maven+SonarQube集成配置与实践

    目录一览: Maven 集成配置与实践 ​SonarQube 集成配置与实践 WeiyiGeek Blog - 为了能到远方,脚下的每一步都不能少. Tips : 本文章来源 Blog 站点或者 We ...

最新文章

  1. 【电子基础】模拟电路问答
  2. Mysql InnoDB索引分析
  3. 信息安全系统设计基础第二周学习总结
  4. 【struts2】预定义拦截器
  5. leetcode —— 48. 旋转图像
  6. 唯真才能永久--读《十年》
  7. python 提取列表元素_Python如何获取列表中元素的索引,python,获得,某个,index
  8. 如何获得一个干净的 gnome 开发环境?
  9. linux调度器(三)——周期性调度器与CFS
  10. cdr导出pdf是html的,CorelDraw 文件导出PDF CDR页码插件 忽略视图及颜色样式补丁
  11. Contrastive Loss (对比损失)
  12. BaiduMap SDK-Location自定义定位图标
  13. windows批处理修改IE主页
  14. CloudNative:云原生(分布式云)的简介(发展演变/为什么需要/优势价值/安全/对比传统企业应用)、四大核心技术、CNCF云原生交互景观、云原生技术的使用经验及方法之详细攻略
  15. ​万邦医药在创业板过会:上半年收入约1亿元,陶春蕾母子为实控人​
  16. 百度与吉利联合制造智能电动汽车;霍尼韦尔2020年度10大创新科技揭晓 | 美通企业日报...
  17. Java程序设计基础【9】
  18. 面向异构众核超级计算机的大规模稀疏计算性能优化研究
  19. 它们改变了整个世界 盘点50个最伟大的游戏创意
  20. java补考不过怎样,合格考补考好过吗 合格考补考不过怎么办

热门文章

  1. 途牛最新财报再亏5.6亿,步子太大总会扯到蛋
  2. 微信开发者工具:errMsg: “request:fail invalid url “xxx“
  3. QQ for Mac OS X : QQ表情管理插件
  4. 按键修饰符、系统修饰符、鼠标修饰符
  5. 杂谈(4)---比风水厉害100倍的惊人定律
  6. 2020浙江计算机类录取分数线,2020年浙江高考普通类第一段录取分数线公布
  7. 4个方法快速完成回收站文件恢复!
  8. 开源字节 CRM 系统
  9. 15软工课后作业01 P18 第四题
  10. 深度:人群、场景、信任感—中国中老年消费品市场三大机会解读