CSR8675学习笔记:新建一个GATT server

为了让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学习笔记:新建一个GATT server_第1张图片
可以看到,应用程序处于最上层。往下一层是属性协议,再往下是逻辑链接控制访问协议,最后到达控制器。

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

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

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

CSR8675学习笔记:新建一个GATT server_第2张图片
上图中,电脑为请求发起方,即为client,传感器为请求应答方,即为server。对我们的产品而言,手机即为client,音箱即为server。

相同的传感器硬件,启用不同的server,即可支持不同的功能。一般的server profile结构如下:
CSR8675学习笔记:新建一个GATT server_第3张图片
从图上可以看出,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系统架构,如下:
CSR8675学习笔记:新建一个GATT server_第4张图片

我们需要创建的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. 初始化服务
初始化服务的消息流图如下:
CSR8675学习笔记:新建一个GATT server_第5张图片

创建服务初始化函数:

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(®istration_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”等非法消息。使用属性协议层直接应答:
    CSR8675学习笔记:新建一个GATT server_第6张图片

  • 处理“HANDLE_EXAMPLE_TYPE_EQ_SELECT”等合法消息。向应用层提交。由应用层处理完毕后,再通知属性协议层应答:
    CSR8675学习笔记:新建一个GATT server_第7张图片

“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工程中添加库文件,如下:
CSR8675学习笔记:新建一个GATT server_第8张图片
###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,
                              ¶ms,
                              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各层即打通。如需添加新的特征,在属性协议层和应用层添加对应的处理代码即可。

你可能感兴趣的:(蓝牙方案,CSR8670蓝牙芯片软件开发,蓝牙,gatt,csr8675,ble)