CC2640R2F学习笔记(14)——GATT客户端读写特征值

168 阅读4分钟

一、背景

1.1 GATT协议

GATT(Generic Attributes Profile)的缩写,中文是通用属性协议,是已连接的低功耗蓝牙设备之间进行通信的协议。

一旦两个设备建立起了连接,GATT 就开始起作用了,这也意味着,你必需完成前面的GAP协议。

GATT使用了 ATT(Attribute Protocol)协议,ATT 协议把 Service,Characteristic 对应的数据保存在一个查找表中,查找表使用 16bit ID 作为每一项的索引。

GATT定义的多层数据结构简要概括起来就是 服务(Service) 可以包含多个 特征(Characteristic),每个特征包含 属性(Properties)值(Value),还可以包含多个 描述(Descriptor)

1.2 属性协议(ATT)

属性协议层 负责数据检索,允许一个设备暴露一些数据块给其他设备,其他设备称之为“属性”。

在ATT环境中,展示属性的设备称之为服务器,与它配对的设备称之为客户端。链路层的主机从机和这里的服务器、客服端是两种概念,主设备既可以是服务器,也可以是客户端。从设备毅然。

1.3 GATT通信中角色

从GATT的角度来看,处于连接状态时的两个设备,它们各自充当两种角色中的一种:

服务端(Server)

包含被GATT客户端读取或写入的特征数据的设备。

客户端(Client)

从GATT服务器中读取数据或向GATT服务器写入数据的设备。

外围设备(从机)作为 GATT 服务端(Server),它维持了 ATT 的查找表以及 service 和 characteristic 的定义;

客户端和服务器的GATT角色独立于外围设备和中央设备的GAP角色。外围设备可以是GATT客户端或GATT服务器,中心可以是GATT客户端或GATT服务器

二、配置读写特征值参数

2.1 发现服务和特征相关结构体

// GATT发现服务和特征时用
// discovery information
typedef struct
{
  discState_t discState;            // discovery state
  uint16_t svcStartHdl;             // service start handle
  uint16_t svcEndHdl;               // service end handle
  uint16_t charHdl;                 // characteristic handle
} discInfo_t;

// 连接句柄表
// entry to map index to connection handle and store address string for menu module
typedef struct
{
  uint16_t connHandle;              // connection handle of an active connection
  uint8_t strAddr[B_STR_ADDR_LEN];  // memory location for menu module to store address string
} connHandleMapEntry_t;

// pointer to allocate the connection handle map
static connHandleMapEntry_t *connHandleMap;

2.2 读写特征值相关变量

// Entity ID globally used to check for source and/or destination of messages
static ICall_EntityID selfEntity;

// Value to write
static uint8_t charVal = 0;

2.3 初始化GATT客户端

以SDK2.4 multi_role工程为例,在 multi_role_init() 初始化多角色应用程序函数中,

/*==================================== 客户端 ====================================*/
// 初始化GATT客户端
VOID GATT_InitClient();

// 注册GATT 本地事件和ATT响应等待传输
GATT_RegisterForMsgs(selfEntity);

// 注册当前任务为GATT的notify和indicate的接收端
// 如果不注册,无法接收从机通过GATT_Notification发来的数据
GATT_RegisterForInd(selfEntity);

三、读取特征值

3.1 流程

建立连接,产生建立连接完成事件 GAP_LINK_ESTABLISHED_EVENT

multi_role_startDiscovery() 开始发现

multi_role_processGATTMsg() 处理GATT消息和事件,响应GATT发现

multi_role_processGATTDiscEvent() 处理GATT发现事件,陆续更改发现状态

发现服务/特征

读取特征值

3.2 读取特征值函数

GATT发现服务和特征查看CC2640R2F学习笔记(13)——GATT客户端发现服务和特征

在SDK2.4 multi_role工程中: 根据mr_doGattRw函数重新定义一个读取特征值函数 mr_doGattRead()

/**
 @brief 执行GATT读函数
 @param index 索引
 @return TRUE - 成功;FALSE - 失败
*/
bool mr_doGattRead(uint8_t index)
{
    bStatus_t status = FAILURE;

    // Create read request...place in CSTACK
    attReadReq_t req;

    req.handle = discInfo[index].charHdl;                       // 填充读请求包

    // Send read request. no need to free if unsuccessful
    // since the request is only placed in CSTACK; not allocated
    status = GATT_ReadCharValue(connHandleMap[index].connHandle, &req, selfEntity);

    return status;
}

3.3 执行读取特征值

以SDK2.4 multi_role工程为例,在 multi_role_processGATTDiscEvent() GATT发现事件处理函数中,发现服务和特征完成后,加入读取特征值函数 mr_doGattRead()

static void multi_role_processGATTDiscEvent(gattMsgEvent_t *pMsg)
{
    ······
    ······
    /*--------------------------- 发现特征 ---------------------------*/
    else if(discInfo[connIndex].discState == BLE_DISC_STATE_CHAR)
    {
        if((pMsg->method == ATT_READ_BY_TYPE_RSP) &&                // 发现服务,存储句柄
            (pMsg->msg.readByTypeRsp.numPairs > 0))
        {
            discInfo[connIndex].charHdl = BUILD_UINT16(pMsg->msg.readByTypeRsp.pDataList[3],
                                                        pMsg->msg.readByTypeRsp.pDataList[4]);

                /* Display_print0(dispHandle, MR_ROW_STATUS1, 0, "Simple Svc Found"); */
        }

        mr_doGattRead(connIndex);                                     // 读取特征值
    }
}

四、写入特征值

4.1 流程

建立连接,产生建立连接完成事件 GAP_LINK_ESTABLISHED_EVENT multi_role_startDiscovery() 开始发现 multi_role_processGATTMsg() 处理GATT消息和事件,响应GATT发现 multi_role_processGATTDiscEvent() 处理GATT发现事件,陆续更改发现状态 发现服务/特征 写入特征值

3.2 写入特征值函数

在SDK2.4 multi_role工程中: 根据mr_doGattRw函数重新定义一个读取特征值函数 mr_doGattWrite()

/**
 @brief 执行GATT写函数
 @param index 索引
 @return TRUE - 成功;FALSE - 失败
*/
bool mr_doGattWrite(uint8_t index)
{
    bStatus_t status = FAILURE;

    if(discInfo[index].charHdl != 0)                                    // 如果已经发现特征
    {
        attWriteReq_t req;

        req.pValue = GATT_bm_alloc(connHandleMap[index].connHandle,     // 为写请求分配空间
                                    ATT_WRITE_REQ, 1, NULL);

        if(req.pValue != NULL)
        {
            req.handle = discInfo[index].charHdl;                       // 填充写请求包
            req.len = 2;
            req.pValue[0] = charVal;  // 写入的特征值(1字节),如果多字节则添加多个req.pValue
            req.sig = 0;
            req.cmd = 0;
                                                                        // 发送GATT写请求给控制器
            status = GATT_WriteCharValue(connHandleMap[index].connHandle, &req, selfEntity);

            if(status != SUCCESS)                                       // 发送失败
            {
                GATT_bm_free((gattMsg_t *)&req, ATT_WRITE_REQ);         // 释放空间
                return FALSE;
            }
        }
    }
    return TRUE;
}

4.3 执行写入特征值

以SDK2.4 multi_role工程为例,在 multi_role_processGATTDiscEvent() GATT发现事件处理函数中,发现服务和特征完成后,加入写入特征值函数 mr_doGattWrite()

static void multi_role_processGATTDiscEvent(gattMsgEvent_t *pMsg)
{
    ······
    ······
    /*--------------------------- 发现特征 ---------------------------*/
    else if(discInfo[connIndex].discState == BLE_DISC_STATE_CHAR)
    {
        if((pMsg->method == ATT_READ_BY_TYPE_RSP) &&                // 发现服务,存储句柄
            (pMsg->msg.readByTypeRsp.numPairs > 0))
        {
            discInfo[connIndex].charHdl = BUILD_UINT16(pMsg->msg.readByTypeRsp.pDataList[3],
                                                        pMsg->msg.readByTypeRsp.pDataList[4]);

                /* Display_print0(dispHandle, MR_ROW_STATUS1, 0, "Simple Svc Found"); */
        }

        mr_doGattWrite(connIndex);                                     // 写入特征值
    }
}

• 由 Leung 写于 2019 年 4 月 9 日

• 参考:simplelink_cc2640r2_sdk_2_40_00_32 [提取码:3pg6]