沁恒微蓝牙主机读写从机示例说明

来源:矜辰所致 通信无线 12 次阅读
摘要:  以沁恒微 CH58x 为例说明一下主机对不同属性从机特征值的读写    ...... 矜辰所致 前言 按照前面博文讲解的流程,讲完了主机从机通信框架(GATT 应用框架),讲完了从机自定义服务,讲完了主机获取从设备服务特征值句柄, 获取到了句柄当然就是要数据读写了。 所以本文的内容就是说明一下主机如何进行数据读写的。 相关博文: CH58x 蓝牙主机获取从设备服务特征值句柄 沁恒微蓝牙 GAT

 

以沁恒微 CH58x 为例说明一下主机对不同属性从机特征值的读写    ...... 矜辰所致

前言

按照前面博文讲解的流程,讲完了主机从机通信框架(GATT 应用框架),讲完了从机自定义服务,讲完了主机获取从设备服务特征值句柄, 获取到了句柄当然就是要数据读写了。

所以本文的内容就是说明一下主机如何进行数据读写的。

相关博文: CH58x 蓝牙主机获取从设备服务特征值句柄 沁恒微蓝牙 GATT 应用框架说明 CH585 蓝牙 主机示例 Central 解析 CH58x/CH59x 系列蓝牙芯片从机示例解析 沁恒微蓝牙从机添加服务和特征示例

我是矜辰所致,全网同名,尽量用心写好每一系列文章,不浮夸,不将就,认真对待学知识的我们,矜辰所致,金石为开!

📑 本文目录 一、🎯 基础说明 ├─ 1.1 特征值的属性 ├─ 1.2 示例流程说明 └─ 1.3 库函数说明 │ └─ 1.3.1 长数据和短数据区分 二、🔍 新增不同属性读写测试 ├─ 2.1 GATT_PROP_WRITE_NO_RSP └─ 2.2 GATT_PROP_INDICATE 三、📚 补充说明 四、📝 结语

一、 基础说明

我们之前说过,主机从机的数据交互,实际上就是对从机特征值的读写。而我们从机的特征值,具备不同的属性,主机针对不同属性的特征值,操作也会有不同。

1.1 特征值的属性

我们在从机示例,在文件 gattprofile.c 中特征值定义的时候通过 simpleProfileChar1Props 设置特征数的属性,我们通过跳转可以看到库中所有关于特征值属性的宏定义 ,我们这里直接用注释解释一下:

// GATT Characteristic Properties Bit Fields
//广播特征值(极少用)从机可广播该特征值的数值(无需主机连接)
#define GATT_PROP_BCAST                 0x01 //!< Permits broadcasts of the Characteristic Value 
//可读特征值(最常用) 主机可主动读取该特征值的当前数据
#define GATT_PROP_READ                  0x02 //!< Permits reads of the Characteristic Value
//无响应写 主机向从机写数据,从机接收后不返回响应 速度快,功耗低
#define GATT_PROP_WRITE_NO_RSP          0x04 //!< Permits writes of the Characteristic Value without response
//带响应写 主机向从机写数据,从机接收后必须返回响应 可靠
#define GATT_PROP_WRITE                 0x08 //!< Permits writes of the Characteristic Value with response
//通知(主动推送,无确认) 从机主动向主机推送特征值数据,主机接收后无需返回确认 ,需要主机写CCCD,0x0001
#define GATT_PROP_NOTIFY                0x10 //!< Permits notifications of a Characteristic Value without acknowledgement
//指示(主动推送,有确认) 从机主动向主机推送特征值数据,主机接收后必须返回确认 ,需要主机写CCCD,0x0002
#define GATT_PROP_INDICATE              0x20 //!< Permits indications of a Characteristic Value with acknowledgement
//认证写(需加密) 主机必须先和从机建立加密连接(绑定 / 配对),才能写该特征值
#define GATT_PROP_AUTHEN                0x40 //!< Permits signed writes to the Characteristic Value
// 扩展属性 该特征值有 “扩展属性”(如支持可靠写、广播等扩展功能) 需读取 “特征值扩展属性描述符(0x2900)” 才能知道具体扩展功能。
#define GATT_PROP_EXTENDED              0x80 //!< Additional characteristic properties are defined in the Characteristic Extended Properties Descriptor

1.2 示例流程说明

经过前面的几篇文章,大家应该对主机读写数据这个流程已经很熟悉了,本文再再再次简要说明一下。

主机读写数据请求 :

主机写数据是在获取到了句柄之后新建了两个任务事件:

  • • if(events & START_READ_OR_WRITE_EVT) 用来「写特征值」 使用GATT_WriteCharValue 函数 用来「读特征值」 使用GATT_ReadCharValue 函数
  • • if(events & START_WRITE_CCCD_EVT) 用来「写CCCD」 也使用GATT_WriteCharValue 函数

主机接收的数据处理 :

数据是通过 TMOS 消息传递,最终在 centralProcessGATTMsg 函数中通过不同分分支处理:

  • • pMsg->method == ATT_READ_RSP 处理「读特征值」的结果
  • • pMsg->method == ATT_WRITE_RSP 处理「写特征值」的结果
  • • pMsg->method == ATT_HANDLE_VALUE_NOTI 接收「从机主动推送的 Notify(通知) 数据」
  • • 示例中没有处理「从机主动推送的 Indicate (通知) 数据」分支 也就是 pMsg->method == ATT_HANDLE_VALUE_NOTI 分支,我们本文会添加测试一下

我们看一下官方示例测试的效果,我在读写请求的 TMOS 事件里面加了个打印方便测试 :

1.3 库函数说明

库函数中提供了多种不同的读写的函数,我们来看一下。

读操作(Read):

函数 场景 说明
GATT_ReadCharValue 数据短(< MTU-1) 知道特征值句柄即可,会返回ATT_READ_RSP响应
GATT_ReadLongCharValue 数据很长 读取长特征值,分多次读取(Read Blob),返回多个ATT_READ_BLOB_RSP响应
GATT_ReadMultiCharValues 一次性读取多个特征值(均为短数据) 一次请求,批量读取,返回ATT_READ_MULTI_RSP响应
GATT_ReadCharDesc 读描述符(短数据) 读取特征的通知 / 指示配置状态
GATT_ReadLongCharDesc 读描述符(长数据) 读取超长的描述符内容(极少用)

写操作(Write)

函数 场景 说明
GATT_WriteNoRsp 不需要回应的写 无响应,最快,但不知道服务器是否收到
GATT_WriteCharValue 需要确认服务器收到 有响应,适合关键数据
GATT_WriteLongCharValue 数据超过 MTU-3 分批次写长特征值(Prepare+Execute Write),返回多个响应
GATT_ReliableWrites 批量配置多个参数(如设备多模式参数) 先批量准备多个写操作,再统一执行(原子操作),确保所有写要么都成要么都败
GATT_SignedWriteNoRsp 带签名的无确认写(仅未加密连接可用)) 需安全但无需确认的控制操作
GATT_WriteCharDesc 写描述符(短数据) 配置特征的通知 / 指示功能
GATT_WriteLongCharDesc 写描述符(长数据) 超长描述符写入(极少用)

1.3.1 长数据和短数据区分

上面函数分为长数据和短数据的读写,对于长数据和短数据是多少,它们并不是固定的,长 / 短的分界由当前协商好的 ATT_MTU 决定 :

类型 字节数 判定标准
短数据 ≤ ATT_MTU - 3(写)/ ≤ ATT_MTU - 1(读) 单次 ATT 请求能装下
长数据 > ATT_MTU - 3(写)/ > ATT_MTU - 1(读) 必须分片传输

比如:

场景 短数据定义 长数据定义 备注
默认 ATT_MTU=23 ≤ 20 字节(写)/ ≤ 22 字节(读) > 20 字节(写)/ > 22 字节(读) 兼容 BLE 4.0
协商 ATT_MTU=158 ≤ 155 字节(写)/ ≤ 157 字节(读) > 155 字节(写)/ > 157 字节(读) 常用优化值
CH585 最大 ATT_MTU=517 ≤ 512 字节(写)/ ≤ 512 字节(读) > 512 字节 ATT 硬上限优先512是个分界线

默认 MTU = 23 时: 读:> 22 字节 = 长数据 写:> 20 字节 = 长数据 MTU = 517 时: 读:> 512 字节 = 长数据(受 ATT 硬上限限制,不是 516) 写:> 512 字节 = 长数据(受 ATT 硬上限限制,不是 514) 通用规则: 无论 MTU 多大,> 512 字节必须用 Long 函数

这里几个数据要搞清楚:

1、单次 ATT 包传输的属性值最大 512 字节; 2、GATT 长读写函数(Read Long/Write Long)通过分块偏移,可访问理论上限 65536 字节的特征值(工程中常用 ≤ 512 字节,长数据的一般应用极少使用,博主自己也没测试,这里的说明仅供参考,有错误请指正); 3、BLE 4.2+ /5.0 ATT MTU 最大上限是 517 字节; 4、BLE 4.0/4.1 ATT_MTU 最大23 字节; 5、一般工程中为了兼容性,默认 ATT_MTU 都为 23字节; 6、CH585 一次传输 最大支持247 字节 MTU(有效数据 244 字节);

二、 新增不同属性读写测试

官方示例是读写包含了3个特征是属性: GATT_PROP_READGATT_PROP_WRITE  和  GATT_PROP_NOTIFY 。

我们本文再测试 一下                

 GATT_PROP_WRITE_NO_RSP 和 GATT_PROP_INDICATE 。

2.1 GATT_PROP_WRITE_NO_RSP

我们把在从机示例改一个特征值 属性为 GATT_PROP_WRITE_NO_RSP ,然后接收数据回调函数改一下方便查看:

我们在主机示例中,也修改一下给这个特征值写数据,这里因为我们能够算出来 CHAR3 的句柄,我们就不再去额外写 获取 CHAR 3 的句柄的程序了,我们直接在示例获取完 CCCD 后面增加一个自己的测试任务,给CHAR3 写数据:

然后在事件中写特征值,使用GATT_WriteNoRsp 函数,不需要响应参数就不需要任务ID,这里直接放代码:

static uint8_t mytestCharVal = 0x11;
...
if(events & START_MY_RWTEST_EVT)
    {
        // Do a write
        attWriteReq_t req;

        req.cmd = FALSE;
        req.sig = FALSE;
        req.handle = centralCharHdl + 5; //这里是因为根据char1 算的 char3 ,测试知道自己要写那个特征值,仅供测试
        req.len = 1;
        req.pValue = GATT_bm_alloc(centralConnHandle, ATT_WRITE_REQ, req.len, NULL, 0);
        if(req.pValue != NULL)
        {
            *req.pValue = mytestCharVal++;
            PRINT("准备写的句柄:%d\n", req.handle);
            if(GATT_WriteNoRsp(centralConnHandle, &req) == SUCCESS)
            {
                tmos_start_task(centralTaskId, START_MY_RWTEST_EVT, 1600);
            }
            else
            {
                GATT_bm_free((gattMsg_t *)&req, ATT_WRITE_REQ);
            }
        }

        return (events ^ START_MY_RWTEST_EVT);
    }

测试效果如下:

2.2 GATT_PROP_INDICATE

再来看一下带 indicate 属性,这里因为测试,并没有去设置标志位防止一些请求冲突,我直接把例程中写 CCCD 的地方,改成写带有 indicate 属性特征值的 CCCD,使用的从机示例就是之前博文《沁恒微蓝牙从机添加服务和特征示例》 我们自己添加服务和特征值的示例 。

这里主要需要注意的地方:一个是写对 CCCD 的句柄,第二个是 notify写 0x0001 ,indicate写 0x0002 。

然后我们使用 GATT_WriteCharDesc 写入,代码如下:

if(events & START_WRITE_CCCD_EVT)
    {
        if(centralProcedureInProgress == FALSE)
        {
            // Do a write
            attWriteReq_t req;

            req.cmd = FALSE;
            req.sig = FALSE;
            req.handle = centralCCCDHdl + 7; //这里也是硬算的,仅供测试
            req.len = 2;
            req.pValue = GATT_bm_alloc(centralConnHandle, ATT_WRITE_REQ, req.len, NULL, 0);
            if(req.pValue != NULL)
            {
                req.pValue[0] = 2; // notify写1 ,indicate写2
                req.pValue[1] = 0;
                PRINT("写CCCD的句柄:%d\n", centralCCCDHdl + 7);
                if(GATT_WriteCharDesc(centralConnHandle, &req, centralTaskId) == SUCCESS)
                {
                    centralProcedureInProgress = TRUE;
                }
                else
                {
                    GATT_bm_free((gattMsg_t *)&req, ATT_WRITE_REQ);
                }
            }
        }
        return (events ^ START_WRITE_CCCD_EVT);
    }

写完以后和 notify 一样需要到 centralProcessGATTMsg 里面处理,我们需要添加一个分支,而且我们需要注意一下要自己确认响应,看代码:

else if(pMsg->method == ATT_HANDLE_VALUE_NOTI)
    {
        PRINT("Receive noti: %x\n", *pMsg->msg.handleValueNoti.pValue);
    }
elseif(pMsg->method == ATT_HANDLE_VALUE_IND)
    {
        PRINT("my test Receive ind: ");
        for(uint8_t i=0;i < pMsg->msg.handleValueInd.len;i++){
            PRINT("%02x",pMsg->msg.handleValueInd.pValue[i]);
        }
        PRINT("\n");
        //还要调用函数发送确认响应
        bStatus_t Ind_test_state = ATT_HandleValueCfm(pMsg->connHandle);
        PRINT("Ind_test_state:= %d \r\n ",Ind_test_state);
    }

最后我们看看测试效果:

三、 补充说明

示例中写 CCCD 使用的是                     GATT_WriteCharValue         而不是     GATT_WriteCharDesc         ,实际上在 CH585 的库里面,这两个函数是一样的,不管是写 CCCD ,还是写特征值,两个函数效果一样。想想也应该一样,本质上是一样的,都是传入的需要写入的句柄,数值,和传递消息的任务 ID 。

结语

本文说明测试了一下主机读写不同属性从机特征值的操作,相对来说本文还是比较简单的。

官方示例和本文测试的读写,基本足够满足正常应用的需求了,对于文中介绍的库函数中的长数据读写函数示例,有机会使用到了再来说明吧。

好了,本文就到这里。谢谢大家!

图片

 

评论区

登录后即可参与讨论

立即登录