CH58x 蓝牙主机读写从机示例说明

复制代码
CH58x 主机对不同属性从机特征值的读写    ...... 矜辰所致

前言

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

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

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

.

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

目录

  • 前言
  • [一、 基础说明](#一、 基础说明)
    • [1.1 特征值的属性](#1.1 特征值的属性)
    • [1.2 示例流程说明](#1.2 示例流程说明)
    • [1.3 库函数说明](#1.3 库函数说明)
      • [1.3.1 长数据和短数据区分](#1.3.1 长数据和短数据区分)
  • [二、 新增不同属性读写测试](#二、 新增不同属性读写测试)
    • [2.1 GATT_PROP_WRITE_NO_RSP](#2.1 GATT_PROP_WRITE_NO_RSP)
    • [2.2 GATT_PROP_INDICATE](#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 字节,长数据函数就是为了突破 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_WRITEGATT_PROP_NOTIFY

我们本文再测试 一下 GATT_PROP_WRITE_NO_RSPGATT_PROP_INDICATE

2.1 GATT_PROP_WRITE_NO_RSP

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

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

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

c 复制代码
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 写入,代码如下:

c 复制代码
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 里面处理,我们需要添加一个分支,而且我们需要注意一下要自己确认响应,看代码:

c 复制代码
else if(pMsg->method == ATT_HANDLE_VALUE_NOTI)
    {
        PRINT("Receive noti: %x\n", *pMsg->msg.handleValueNoti.pValue);
    }
else if(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 。

结语

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

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

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

相关推荐
矜辰所致1 天前
CH58x 蓝牙主机获取从设备服务特征值句柄
蓝牙主机·ble 蓝牙·ch58x·蓝牙获取服务句柄·蓝牙获取特征值句柄
矜辰所致3 个月前
CH58x/CH59x 系列芯片从机示例解析
ble·沁恒微蓝牙·ch58x·蓝牙从机·ch59x
矜辰所致4 个月前
CH58x 蓝牙芯片 SysTick、RTC、TMRx
沁恒微·时钟·rtc·systick·ch58x