为了让CSR867x的开发更容易,现与思度科技联合推出CSR867x学习板【淘宝链接:思度科技CSR开发板】。

技术交流QQ群号:743434463
开发板会员QQ群号:725398389(凭订单号入群,赠PPT、项目源码、视频教程)
——————————正文分割线———————————–

#1. 引言
公司的新项目需要CSR8675支持iOS APP调节音量、设置EQ等功能。由于这些功能不属于任何已公开的蓝牙GATT服务,因此定制一个全新的GATT server成了必不可少的工作。

本文章介绍了GATT server的基本概念,并给出了用CSR8675创建GATT server的简单示例。

#2. 了解GATT server
网上有很多基本概念的介绍,本文不展开,推荐阅读如下链接:

  • 蓝牙【GATT】协议介绍
  • 蓝牙BLE GATT完全分析和运用
  • Bluetooth GATT 介绍

有几个重点概念需要提一下,如下:

可以看到,应用程序处于最上层。往下一层是属性协议,再往下是逻辑链接控制访问协议,最后到达控制器。

CSR8675的server开发只涉及到应用层和属性协议,L2CAP和controller由固件完成,对二次开发人员是封闭的。

GATT可配置为如下两种角色:

  • Client: 命令、请求发起方
  • server: 命令、请求接收方


上图中,电脑为请求发起方,即为client,传感器为请求应答方,即为server。对我们的产品而言,手机即为client,音箱即为server。

相同的传感器硬件,启用不同的server,即可支持不同的功能。一般的server profile结构如下:

从图上可以看出,server profile结构有一些主要特征:

  • 一个server profile可包含多个server
  • server可以引用多个别的server
  • 每个server包含多个特征
  • 每个特征包含属性、值和描述信息

具体到产品,当用户需要在iOS APP上修改音箱的EQ设置时,手机和CSR8675之间有如下交互:

  • 手机通过LE L2CAP连接获取CSR8675所支持的服务列表
  • 从服务列表中找到手机和CSR8675事先约定好的服务
  • 找到服务中包含的EQ设置的特征值
  • 修改EQ设置
  • 修改成功后,CSR8675返回修改成功通知,否则返回修改失败

CSR8675能提供的服务类型分三种:

  • 蓝牙联盟组织已采用的服务。如DIS(设备信息服务)、BAS(电池服务)等。官方链接:GATT规格。
  • 蓝牙成员专属的服务。这些规格由蓝牙成员自行制定,提供自定义的服务,有16位的UUID,需要向蓝牙联盟组织付费购买。官方链接:成员16位UUID。
  • 自定义的服务。这些规格可以由任何人自行制定和实施,提供自定义的服务,有128位UUID,不需要付费购买。这种服务的优点是免费,缺点是可能碰巧与别人制定的服务有着相同的UUID,影响到双方产品的使用。

新建一个自定义的的服务,即是本文的目标。
#3. 新建一个server
在创建一个新的server之前,先来看一下CSR8675的GATT系统架构,如下:

我们需要创建的server,即处于audio sink application和GATT manager library中间。因此,server代码分别位于库文件路径(ADKXX\src\lib)和应用路径(ADKXX\app)。

  • 库文件路径的代码实现GATT属性协议层的功能。例如响应EQ设置特征值的读写访问动作,并向应用层发送消息。

  • 应用路径的代码实现GATT应用层的功能。例如当应用层接收到新的EQ设置消息,其会将消息中的数据转换成DSP可辨识的数据,改变DSP的模块设置。在EQ设置更改成功后,应用层向属性协议层发送更改成功的消息。

  • 库文件层代码使用VM rebuild工具编译,应用层使用xIDE开发环境编译。

##3.1. 库文件层
###3.1.1. 创建服务描述
在ADKXX\src\lib\路径下创建一个新的文件夹,名为"gatt_example_server"。

创建gatt_example_server_uuids.h,添加如下代码:

#ifndef __GATT_EXAMPLE_SERVER_UUIDS_H__
#define __GATT_EXAMPLE_SERVER_UUIDS_H__ #define UUID_EXAMPLE_SERVICE                           0xFE33
#define UUID_EXAMPLE_TYPE_EQ_SELECT                         0x0001#endif /* __GATT_EXAMPLE_SERVER_UUIDS_H__ */

创建gatt_example_server_db.dbi,添加如下代码:

#ifndef __GATT_EXAMPLE_SERVER_DB_DBI__
#define __GATT_EXAMPLE_SERVER_DB_DBI__#include "gatt_example_server_uuids.h"/* Primary service declaration of example service */
primary_service {uuid : UUID_EXAMPLE_SERVICE,name : "EXAMPLE_SERVICE",/* example level characteristic */characteristic {uuid        : UUID_EXAMPLE_TYPE_EQ_SELECT,name        : "EXAMPLE_TYPE_EQ_SELECT",properties  : [read, write, notify],flags      : [FLAG_IRQ],value      : 0x3}
},
#endif /* __GATT_EXAMPLE_SERVER_DB_DBI__ */

上述代码中包含几个重要信息:

  • 首要服务的UUID号“UUID_EXAMPLE_SERVICE”。对手机APP而言,如果它搜索到一个服务的UUID号与此UUID号相同,它会认为此服务即是我们的example服务。

  • 特征的UUID号。在手机APP获取到首要服务的UUID号后,它会尝试获取这个服务的每个特征值的UUID号,并与预先保存的EQ设置特征值的UUID号相匹配。

  • 特征的属性。read代表此特征值支持APP的读值操作,write代表此特征值支持APP的写值操作,notify代表此特征值支持主动向APP发送通知消息。

###3.1.2. 初始化服务
初始化服务的消息流图如下:

创建服务初始化函数:

bool GattExampleServerInit(Task appTask,GEXMS_T *const example_server,const gatt_example_server_init_params_t *init_params,uint16 start_handle,uint16 end_handle)
{gatt_manager_server_registration_params_t registration_params;if(INPUT_PARAM_NULL(appTask, example_server)){GATT_EXAMPLE_SERVER_PANIC(("GEXM: Invalid Initialisation parameters"));}/* Reset all the service library memory */memset(example_server, 0, sizeof(GEXMS_T));/* Set up library handler for external messages */example_server->lib_task.handler = exampleServerMsgHandler;/* Store the Task function parameter.All library messages need to be sent here */example_server->app_task = appTask;/* Check optional initialisation parameters *//* When GATT_MANAGER_USE_CONST_DB is enabled then it is the callers responsibility* to register the appropriate GATT example server configuration when the * const database is registered.*/if (init_params){/* Store notifications enable flag */example_server->notifications_enabled = init_params->enable_notifications;}else{example_server->notifications_enabled = FALSE;}/* Setup data required for Example Service to be registered with the GATT Manager */registration_params.task = &example_server->lib_task;registration_params.start_handle = start_handle;registration_params.end_handle = end_handle;/* Register with the GATT Manager and verify the result */return (GattManagerRegisterServer(&registration_params) == gatt_manager_status_success);
}

为了初始化函数,需在应用层调用此初始化函数,以注册消息处理钩子函数、使能通知事件、注册通知处理钩子函数,最后调用“GattManagerRegisterServer”将服务注册到GATT管理器中。初始化成功后,应用层收到“GATT_MANAGER_REGISTER_WITH_GATT_CFM”消息,此时初始化服务完成。

###3.1.3. 处理GATT管理器的消息
GATT管理器将L2CAP层向属性协议层提交的消息发送给初始化时注册的消息处理钩子函数。消息处理钩子函数代码如下:

void exampleServerMsgHandler(Task task, MessageId id, Message msg)
{GEXMS_T *const example_server = (GEXMS_T*)task;switch (id){case GATT_MANAGER_SERVER_ACCESS_IND:{/* ATT Access IND is received */GATT_EXAMPLE_DEBUG_INFO(("GEXM: Received 'GATT_MANAGER_SERVER_ACCESS_IND' \n"));handle_example_access(example_server, (const GATT_MANAGER_SERVER_ACCESS_IND_T *)msg);}break;default:{/* GATT unrecognised messages */GATT_EXAMPLE_DEBUG_PANIC(("GEXM: example_ext_msg_handler()  Unknown message \n"));}break;}
}

消息处理钩子函数“handle_example_access”的代码如下:

void handle_example_access(GEXMS_T *const example_server,const GATT_MANAGER_SERVER_ACCESS_IND_T *const access_ind)
{GATT_EXAMPLE_DEBUG_INFO((" GEXM: handle_example_access(), Handle = %x \n",access_ind->handle));switch (access_ind->handle){case HANDLE_EXAMPLE_SERVICE:{example_service_access(example_server, access_ind);}break;case HANDLE_EXAMPLE_TYPE_EQ_SELECT:{/* This is the handle EQ */example_type_eq_select_access(example_server, access_ind);}break;default:{/* Respond to invalid handles */send_example_access_error_rsp(example_server, access_ind, gatt_status_invalid_handle);}break;}
}

从上述代码中可以看出,“exampleServerMsgHandler”有两种应答方式:

  • 处理“default”等非法消息。使用属性协议层直接应答:

  • 处理“HANDLE_EXAMPLE_TYPE_EQ_SELECT”等合法消息。向应用层提交。由应用层处理完毕后,再通知属性协议层应答:

“example_type_eq_select_access”函数代码如下:

static void example_type_eq_select_access(GEXMS_T *const example_server,const GATT_MANAGER_SERVER_ACCESS_IND_T *const access_ind)
{GATT_EXAMPLE_DEBUG_INFO((" GEXM: handle_example_type_name_access_ind(), Access Ind flags = %x \n",access_ind->flags));if(example_server != NULL){if (access_ind->flags & ATT_ACCESS_READ){MAKE_EXAMPLE_MESSAGE(GATT_EXM_READ_EQ_SELECT);/* Inform app about the change in type name */message->example_server = example_server;         /* Pass the instance which can be returned in the response */message->cid = access_ind->cid;         /*Fill in CID*/GATT_EXAMPLE_DEBUG_INFO((" access_ind->cid = 0x%x \n",access_ind->cid));GATT_EXAMPLE_DEBUG_INFO((" access_ind->value1 = 0x%x \n",access_ind->value[0]));GATT_EXAMPLE_DEBUG_INFO((" access_ind->offset = 0x%x \n",access_ind->offset));GATT_EXAMPLE_DEBUG_INFO((" access_ind->size_value = 0x%x \n",access_ind->size_value));GATT_EXAMPLE_DEBUG_INFO((" access_ind->handle = 0x%x \n",access_ind->handle));/* access_ind->cid = 80 */MessageSend(example_server->app_task, GATT_EXM_READ_EQ_SELECT, message);}else if ( access_ind->flags & ATT_ACCESS_WRITE_COMPLETE){if(access_ind->size_value == 1){MAKE_EXAMPLE_MESSAGE(GATT_EXM_WRITE_EQ_SELECT);/* Inform app about the change in alert level */message->example_server = example_server;         /* Pass the instance which can be returned in the response */message->cid = access_ind->cid;         /*Fill in CID*/message->eq_select = access_ind->value[0];GATT_EXAMPLE_DEBUG_INFO((" WRITE access_ind->cid = 0x%x \n",access_ind->cid));GATT_EXAMPLE_DEBUG_INFO((" WRITE access_ind->value1 = 0x%x \n",access_ind->value[0]));GATT_EXAMPLE_DEBUG_INFO((" WRITE access_ind->offset = 0x%x \n",access_ind->offset));GATT_EXAMPLE_DEBUG_INFO((" WRITE access_ind->size_value = 0x%x \n",access_ind->size_value));GATT_EXAMPLE_DEBUG_INFO((" WRITE access_ind->handle = 0x%x \n",access_ind->handle));MessageSend(example_server->app_task, GATT_EXM_WRITE_EQ_SELECT, message);send_example_access_rsp((Task)&example_server->lib_task, access_ind->cid, HANDLE_EXAMPLE_TYPE_EQ_SELECT, gatt_status_success, 0, NULL);}else{send_example_access_error_rsp(example_server, access_ind, gatt_status_invalid_length);}}else{/* Reject access requests that aren't read/write, which shouldn't happen. */send_example_access_error_rsp(example_server, access_ind, gatt_status_request_not_supported);}}
}

MessageSend(example_server->app_task, GATT_EXM_READ_EQ_SELECT, message);此消息即是发送给应用层的消息。

###3.1.4. 生成库文件
准备好上述两个文件后,运行ADK的vm rebuild工具,即可生成VM需要的.c、.h和lib文件。有关此工具的使用方法请参考ADK文档《GATT Database generator user guide》。

##3.2. 应用层
###3.2.1. 添加库文件
在vm rebuild工具编译成功后,生成库文件“libgatt_example_server.a”。在app工程中添加库文件,如下:

###3.2.2. 创建应用层服务函数
属性协议层的“GATT_EXM_READ_EQ_SELECT”消息发送给应用层服务函数,如下:

void sinkGattExampleServerMsgHandler(Task task, MessageId id, Message message)
{switch(id){/* eq uuid:0x0004*/case GATT_EXM_READ_EQ_SELECT:{GATT_DEBUG(("handleEQReadReq request\n"));handleEQReadReq((GATT_EXM_READ_TYPE_REQ_T*)message);}break;default:GATT_DEBUG(("GATT Unknown message from EXM lib\n"));break;}
}

###3.2.3. 发送通知
上述“GATT_EXM_READ_EQ_SELECT”和“GATT_EXM_WRITE_EQ_SELECT”都是由属性协议层向上提交给应用层,对应GATT的read,write功能。notify功能是从应用层向下发送给属性协议层。

示例代码实现了定时发送通知的功能,需要改动几处app的代码:

  • 在sink_gatt_server.c的“gattServerConnectionAdd”函数中发送通知消息:
        MessageCancelFirst( sinkGetBleTask(), BLE_INTERNAL_MESSAGE_DYNAUDIO_TIMER);MessageSend( sinkGetBleTask(), BLE_INTERNAL_MESSAGE_DYNAUDIO_TIMER, 0 );
  • 在sink_ble.c的“bleInternalMsgHandler”函数中添加定时消息处理函数:
        case BLE_INTERNAL_MESSAGE_EXAMPLE_TIMER:{BLE_INFO(("BLE_INTERNAL_MESSAGE_EXAMPLE_TIMER\n"));sinkBleExampleTypeReadSendAndRepeat();}break;
  • 在sink_ble.c的“bleInternalMsgHandler”函数中添加定时消息处理函数:
void sinkBleExampleTypeReadSendAndRepeat(void)
{uint16 index = 0;GATT_EXM_SERVER_INFO(("Sending GATT_SERVER[%d] type name\n", index));GattExampleServerTypeReadNotification( GATT_SERVER.example_server, 1, &(GATT[index].cid),  GATT_SERVER.type_name);MessageSendLater( sinkGetBleTask(), BLE_INTERNAL_MESSAGE_EXAMPLE_TIMER, 0, GATT_SERVER_EXAMPLE_UPDATE_TIME );
}

###3.2.4. 启动服务
在“sink_gatt_init.c”中添加服务初始化函数:

if(!sinkGattExampleServerInitialiseTask(ptr))
{return FALSE;
}

服务初始化函数如下:

bool sinkGattExampleServerInitialiseTask(uint16 **ptr)
{gatt_example_server_init_params_t params = {EXAMPLE_SERVER_ENABLE_NOTIFICATIONS};if(GattExampleServerInit(sinkGetBleTask(), (GEXMS_T*)*ptr,&params,HANDLE_EXAMPLE_SERVICE,HANDLE_EXAMPLE_SERVICE_END)){GATT_DEBUG(("GATT Example Server initialised\n"));gattServerSetServicePtr(ptr, gatt_server_service_exm);*ptr += sizeof(GEXMS_T);return TRUE;}else{GATT_DEBUG(("GATT Example Server init failed\n"));return FALSE;}
}

#4. 总结
按照上述步骤添加代码后,GATT server各层即打通。如需添加新的特征,在属性协议层和应用层添加对应的处理代码即可。

CSR8675学习笔记:新建一个GATT server相关推荐

  1. java createchannel_【原创】java NIO FileChannel 学习笔记 新建一个FileChannel

    首先使用FileChannel 的open方法获取一个FileChannel对象.下面这段代码是FileChannel中open方法的代码. public static FileChannel ope ...

  2. activiti7关联mysql_学习笔记:一个MySQL实例有多个Activiti数据库问题

    学习笔记:一个MySQL实例有多个Activiti数据库问题 使用SpringBoot + activiti6 搭建审批流项目,数据库使用的是MySQL.且我的数据库下存在多个activiti相关的数 ...

  3. 陈表达VBA学习笔记-新建工作表鼠标右键菜单按钮

    陈表达VBA学习笔记-新建工作表鼠标右键菜单按钮:新建一个我的菜单按钮 设置对应的宏过程名称为 [我的菜单宏] 点击按钮弹窗信息,信息可自定义设置 详细代码如下: Sub 新建右键菜单()Dim 菜单 ...

  4. Magnolia学习笔记(一个基于JSR170的内容管理系统) ( by quqi99 )

                         Magnolia学习笔记(一个基于JSR170的内容管理系统) ( by quqi99 ) 作者:张华 发表于:2007-05-24  ( http://bl ...

  5. weblogic新建一个managed server并启动

    weblogic新建一个managed server并启动 一.创建managedserver 1.启动Adminserver进入控制台 域结构------>环境------>服务器 2. ...

  6. 嵌入式开发学习笔记9-做一个好玩的LED闪烁

    嵌入式开发学习笔记9-做一个好玩的LED闪烁 前言 实际操作 程序功能 实现思路 程序源码 实现效果展示 前言 LED小灯闪烁实质是控制单片机上的I/O口,通过向I/O口循环输入高低电平,从而控制LE ...

  7. Flutter学习笔记 —— 完成一个简单的新闻展示页

    Flutter学习笔记 -- 完成一个简单的新闻展示页 前言 思路分析 案例代码 结束语 上图 前言 刚学Flutter不久,今天我们来看看如何使用 Container & ListView实 ...

  8. golang游戏开发学习笔记-开发一个简单的2D游戏(基础篇)

    此文写在golang游戏开发学习笔记-创建一个能自由探索的3D世界之后,感兴趣可以先去那篇文章了解一些基础知识,在这篇文章里我们要创建一个简单的2D游戏场景以及配套的人物,并实现人物运动和碰撞检测功能 ...

  9. 【学习笔记】一个关于utf8编码转换的问题

    [学习笔记]一个关于utf8编码转换的问题 在验证某些东西时,需要使用到中文对应的utf8编码,就到网上搜索了utf8编码转换的一些在线工具,发现了些问题. 百度第一页所有的utf8编码在线转换转换出 ...

最新文章

  1. CDesktopView类
  2. java学习笔记—国际化(41)
  3. phalcon: 资源文件管 理 引入css,js
  4. OpenStack Telemetry系统架构及实践
  5. 2019年春季学期第九周作业
  6. html绝对定位最小化超标,html – 静态定位元素影响后续兄弟元素的绝对位置
  7. Excel Cookbook by Eric
  8. html实现在线新闻浏览器,使用JQuery Mobile实现手机新闻浏览器
  9. Page.IsValid 属性
  10. C语言大数阶乘的求法
  11. 美国在线教育的启示:教育领域正在革命
  12. linux驱动开发学习笔记十六:gpio相关OF函数和子系统API函数
  13. opencv——convertTo
  14. qt 字体旋转90_如何识别图片和视频上文字的字体
  15. 大反转!马斯克哭求被裁员工回来
  16. emi滤波matlab,EMI电源滤波器选型方法 浅析EMI电源滤波器及其原理介绍
  17. 计算机无线键盘没反应,电脑连接无线键盘没反应怎么办
  18. vue3 路由缓存页面
  19. 基于COMSOL Multiphysics的静电场仿真分析
  20. 基于QTC++的线激光标定+测距模型

热门文章

  1. QT叠加HDMI采集视频OSD
  2. 戴尔灵越7000笔记本开机吱吱响,解决办法
  3. 怎么才能在Mac电脑提醒事项添加提醒事项
  4. finalcut剪切快捷键_final cut pro常用小技巧
  5. HTML5期末大作业:旅游网页设计——山东旅游9页(代码质量好) 学生DW网页设计作业源码 web课程设计网页规划与设计
  6. 洛谷Java入门级代码
  7. Stable Diffusion
  8. 谈谈您不知道的湖南创发科技 讲诉我在创发的经历
  9. 全球首个开源图像识别系统上线了!
  10. 蓝桥杯 java 历届试题 对局匹配