沁恒微蓝牙从机添加服务和特征示例
蓝牙从机添加自定义服务特征示例 包括 Indicate 和 128bit UUID ...... 矜辰所致 ...增加特征值长度说明 2026/3/19前言在之前的文章《沁恒微蓝牙 GATT 应用框架说明》中我们已经详细了解了 GATT 中服务和特征值有关的应用框架官方也给出了添加自定义服务和特征的代码在实际应用中我们或许也需要根据需求增加服务和特征。所以本文内容就是说明以及实际演示一下在应用中如何添加不同的服务和特征值。相关博文沁恒微蓝牙 GATT 应用框架说明CH58x/CH59x 系列芯片从机示例解析一文搞清 BLE 蓝牙 UUID.我是矜辰所致全网同名尽量用心写好每一系列文章不浮夸不将就认真对待学知识的我们矜辰所致金石为开目录前言一、本文准备实现的示例二、官方示例说明2.1 服务声明2.2 特征值1Char1- 可读可写2.2.1 服务和特征申明的区别2.2.2 GATT_PERMIT_READ 和 GATT_PROP_READ2.2.3 关于名字 Unknown Characteristic2.2.4 可选的描述符2.3 特征值 2~32.4 特征值 4 - 通知Notify2.5 特征值 5 - 需要认证的读2.6 特征值长度说明三、示例测试3.1 增加 Indicate 属性特征值3.2 增加服务3.3 增加128bit UUID 服务四、示例下载结语一、本文准备实现的示例实际上示例 Peripheral 中的 gattprofile.c 已经完全展示了 5 种不同属性的服务实际应用需要增加完全可以复制一下把对应的名字修改一下就可以实现了。本文我们计划实现几个示例在官方示例自定义服务里增加特征值额外再增加我们自己的服务和特征值额外再增加 128bit UUID 的服务和特征值。我们只要搞清楚了官方示例提供的自定义服务特征值那么自己应用中的服务特征也不会有什么问题。二、官方示例说明我们需要参考的是gattprofile.c文件代码我们把此文件中的结构体数组simpleProfileAttrTbl[]搞清楚基本上就完成了大部分工作了。在文章 沁恒微蓝牙 GATT 应用框架说明 的 2.1.1 服务和特征值配置 小节关于服务和特征值的说明。我们把这个结构体数组拆分开来讲解说明能更快的让我们理解。2.1 服务声明负责服务声明的代码如下// Simple Profile Service{{ATT_BT_UUID_SIZE,primaryServiceUUID},/* type primaryServiceUUID0x2800*/GATT_PERMIT_WRITE,/* permissions*/0,/* handle */(uint8_t*)simpleProfileService/* pValue保存的值为本服务的UUID 0XFFE0 */},对于服务申明第一个成员{ATT_BT_UUID_SIZE, primaryServiceUUID}是固定的表示 “这是一个主服务” 。primaryServiceUUID在我以前的文章 一文搞清 BLE 蓝牙 UUID 中也说明过这个 UUID 类型为 0x2800 是用来声明这是一个服务的是个常量固定存放在 Flash 中的 所以的服务声明前面2个成员永远固定这么写就行了不能改变第二个成员权限需要是可读主机才能正确识别服务设置为GATT_PERMIT_READ实际应用这里按照标准的写成可读即可。这里额外说明一下华为手机连接过从机设备一次以后就会保存设备信息我测试的时候使用华为手机正常连接过设备后将第二个成员权限改成其他任意数值包括0重新烧录后连接都依然能够正常获取这是华为手机自己的策略问题。大家做测试的时候记得注意一下。第三个成员句柄写成 0连接上以后由协议栈自动分配最后一个成员是储存的值这里因为服务本身不是用来数据通信的这里的值保存的是服务自己的 UUID 就是示例中定义的 0xFFE0 。2.2 特征值1Char1- 可读可写表示特征值1 的代码如下// Characteristic 1 Declaration//// 1. 特征值1 - 声明项告诉手机特征值1支持读写{{ATT_BT_UUID_SIZE,characterUUID},//固定的类型0x2803Characteristic特征值声明0x2803GATT_PERMIT_READ,// 声明项本身的权限可读0,simpleProfileChar1Props},// 本特征值的属性为GATT_PROP_READ | GATT_PROP_WRITE 特征值1的功能读写// Characteristic Value 1//2. 特征值1 - 数值项存放特征值1的实际数据{{ATT_BT_UUID_SIZE,simpleProfilechar1UUID},//自定义的特征值的 0xFFE1GATT_PERMIT_READ|GATT_PERMIT_WRITE,// 数值项的访问权限可读可写0,simpleProfileChar1},// 特征值1的数据自定义的数组我们可读写修改// Characteristic 1 User Description// 3. 特征值1 - 描述项给特征值1起名字{{ATT_BT_UUID_SIZE,charUserDescUUID},// 类型用户描述0x2901GATT_PERMIT_READ,// 描述项本身的权限可读0,simpleProfileChar1UserDesp},// 描述项的值那这里我们可以明显看到这里特征的代码使用了 3 个属性成员表示而上面服务只需要一个成员表示这里我们先来说明一下为什么。2.2.1 服务和特征申明的区别在 BLE GATT 规范里每个特征值的标准结构是特征值声明Characteristic Declaration说明这是一个特征值包含它的属性读 / 写 / 通知等。特征值的值Characteristic Value真正的数据内容。可选描述符Descriptor如用户描述0x2901、客户端配置0x2902等。而对于服务来说服务的核心作用是告诉外部 “我有一个 UUID 为 0xFFE0 的主服务” 今次而已。使用一个属性项的 UUID0x2800 定义了 “这是服务声明”数据值0xFFE0 定义了 “是哪个服务”就完成了服务的核心声明。不需要额外的 “配置” 或 “描述”所以一个属性项就够了。所以一个完整的特征值需要多个属性项配合才能描述清楚而服务只需要一个属性项就能完成核心声明。下面上一张 BLE GATT 协议的数据组织结构示意图对于我们的示例代码而言gattAttribute_t结构体就是 GATT 成员属性表数组simpleProfileAttrTbl中每个{ }都是独立的 “属性项”有自己的 UUID 和数据值服务的 UUID0x2800 数据值服务 UUID就能完成声明仅需 1 项特征值需要 0x2803声明属性 自定义 UUID存数据 0x2901描述可选至少 2 项再给一张示意图2.2.2 GATT_PERMIT_READ 和 GATT_PROP_READ在特征值定义的时候有两个地方都会用到关于读写的宏定义如下图:对于有些新人可能会有一点迷糊两个地方都有读写而且这两个地方有什么区别那么它们是不是一定要一致 我们先来搞懂这两个位置的不同我们用一个表格来说明一下simpleProfileChar1Props特征属性比如 GATT_PROP_READ)GATT_PERMIT_xxx访问权限比如 GATT_PERMIT_READ位置特征值声明项0x2803的数据值pValue特性值的权限字段permissions作用对象告诉客户端比如手机这个特性支持什么功能告诉协议栈这个属性项实际能不能被读 / 写控制实际数据能否访问协议层发现阶段广播/读取声明数据访问阶段读/写值给谁看手机/主机看决定是否显示读/写按钮协议栈看决定是否允许操作简单总结一下就是GATT_PROP_*告诉外界你能做什么代码中在特征值的声明项的第四个成员pValue位置被使用GATT_PERMIT_*实际控制能不能做代码中在特征值的数值项的第二个成员permissions位置被使用。正常来说这两处的定义会保持一致但是不一致也可以存在我们举两个不同的例子。EX1// 场景数据只读但声明说有写权限故意不一致有特殊用途// 声明告诉手机这个特性可读写{{ATT_BT_UUID_SIZE,characterUUID},GATT_PERMIT_READ,0,simpleProfileChar1Props// GATT_PROP_READ | GATT_PROP_WRITE},// 实际值但协议栈禁止写入{{ATT_BT_UUID_SIZE,simpleProfilechar1UUID},GATT_PERMIT_READ,// ← 只有读没有写0,simpleProfileChar1},效果手机看到声明 → 显示可读写 → 用户看到写按钮手机尝试写入 → 协议栈拒绝 → 返回 Write Not Permitted 错误EX2 // 场景声明只读实际可写隐藏功能// 声明告诉手机只读{{ATT_BT_UUID_SIZE,characterUUID},GATT_PERMIT_READ,0,simpleProfileChar1Props// GATT_PROP_READ 只有读},// 实际值但协议栈允许写入{{ATT_BT_UUID_SIZE,simpleProfilechar1UUID},GATT_PERMIT_READ|GATT_PERMIT_WRITE,// ← 实际可写0,simpleProfileChar1},效果普通APP看不到写功能知道Handle的开发者可以直接写隐藏接口讲到这里我们再来看特征值1 的代码是不是感觉很明了了结合上文内容然后对着上面给出的代码注释仔细看一下是不是就清楚了。2.2.3 关于名字 Unknown Characteristic在上面给出的特征值1 的代码中在描述项的注释中我写了描述符是给本特征值取名字的而且代码中确实也给了值但是显示的是 Unknown Characteristic 。如果观察比较仔细在这个官方示例工程中 Profile 文件夹下面与gattprofile.c同样是添加服务特征的代码文件devinfoservice.c中所定义的特征值都没有加描述项但是它们都有显示名字如下图这里说明一下为什么会有这种情况首先在蓝牙协议标准层面给人看的“名字”应该定义在描述项Descriptor里具体是 0x2901 Characteristic User Description。蓝牙联盟Bluetooth SIG维护了一个标准 UUID 数据库。如果特征值的 UUID 是蓝牙联盟规定的标准 DIS 服务 UUID例如 Model Number 是 0x2A24那么 BLE调试助手/nRF Connect 等工具不需要描述符直接通过 UUID 查表就能显示 “Model Number String”。用户自定义的 UUID 128 位或者未注册的 16/32 位是私有的。通用工具无法查表为了严谨它们只能显示 “Unknown Characteristic” 并列出原始的 UUID 字符串以防误导用户即使加了 0x2901 描述符。只有点进去看描述符才能看到文字如下所以总结一下只要你的 UUID 是标准的就不需要描述符来告诉工具名字自定义的 UUID 即便自己使用描述符定义了名字依然会显示 Unknown Characteristic 。2.2.4 可选的描述符再简单说一个问题我们前面有说过如果不具备 Notify / Indicate 属性的话描述符是可以不需要的对于示例中的特征值1 我们都可以直接把描述项部分给删除也不影响使用如下这里这样修改以后特征值依然读写正常。 只是有一个小点改了以后会发现手机读取不到 Notify 了这是因为我们的 Notify 的位置发生了变化在gattprofile.c的最开头因为少了一个属性项我们改成 10 就正常了2.3 特征值 2~3把特征值 1 介绍完毕后面的特征值类似简单的过一遍就好:特征值2Char2- 只读特征值3Char3- 只写这两个很基本没什么需要特别说明的。2.4 特征值 4 - 通知Notify特征值4 是示例中唯一带 CCCD 的特性因为支持 Notify必须要多了一个客户端配置描述符0x2902。// Characteristic 4 Declaration{{ATT_BT_UUID_SIZE,characterUUID},GATT_PERMIT_READ,0,simpleProfileChar4Props},//声明本特征值的属性 GATT_PROP_NOTIFY// Characteristic Value 4{{ATT_BT_UUID_SIZE,simpleProfilechar4UUID},0,// 权限0因为 Notify 是服务器主动推送客户端不直接读写这个值0,simpleProfileChar4},// Characteristic 4 configuration{{ATT_BT_UUID_SIZE,clientCharCfgUUID},// 描述符类型客户端特征配置 0x2902GATT_PERMIT_READ|GATT_PERMIT_WRITE,// 权限可读可写用于客户端开启/关闭通知0,(uint8_t*)simpleProfileChar4Config},//Notifications and Indications are Enabled/disabled// Characteristic 4 User Description{{ATT_BT_UUID_SIZE,charUserDescUUID},GATT_PERMIT_READ,0,simpleProfileChar4UserDesp},大家以后自己添加具备 Notify/Indicate 属性的特征时候必须带上上面代码中的 Characteristic 4 configuration 部分而且格式与上面保持一致。2.5 特征值 5 - 需要认证的读特征值 5 也只是在 特征值的第二项数值项里面的权限成员定义为GATT_PERMIT_AUTHEN_READ需要和手机绑定才能正常读取数据// Characteristic 5 Declaration{{ATT_BT_UUID_SIZE,characterUUID},GATT_PERMIT_READ,0,simpleProfileChar5Props},// 属性GATT_PROP_READ 这个一样// Characteristic Value 5{{ATT_BT_UUID_SIZE,simpleProfilechar5UUID},GATT_PERMIT_AUTHEN_READ,// ⭐ 需要配对认证才能读0,simpleProfileChar5},// Characteristic 5 User Description{{ATT_BT_UUID_SIZE,charUserDescUUID},GATT_PERMIT_READ,0,simpleProfileChar5UserDesp},我们如果把官方示例中的服务和特征值的定义完全搞清楚基本上在后面应用中想要自己定义什么特征值是完全没问题的至于数据通信流程逻辑那些也是按照示例中的框架来添加即可这个后面有需要可能还会单独写一篇主机与从机数据收发流程解析。2.6 特征值长度说明示例中特征值长度都定义得比较小如下图// Length of characteristic in bytes ( Default MTU is 23 )#defineSIMPLEPROFILE_CHAR1_LEN1#defineSIMPLEPROFILE_CHAR2_LEN1#defineSIMPLEPROFILE_CHAR3_LEN1#defineSIMPLEPROFILE_CHAR4_LEN1#defineSIMPLEPROFILE_CHAR5_LEN5这里实际支持多大呢在蓝牙官方文档中有说明文档地址https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Core-62/out/en/host/attribute-protocol–att-.html特征值的最大长度位 512 字节。但是因为单个包最大长度有限制读MTU - 1 写 / 通知MTU - 3 。所以建议一般最大设置为 MTU - 3 确保不会有意外发生理论上来说超过的部分会分包发送 。但是在实际应用中我们一般会把特征值的长度设置为 244 因为用户数据最大一帧就只能发 244 。这部分相关说明我在之前博文沁恒微 CH585 芯片 Ble 传输速率测试说明 探讨过虽然文档说过特征值的最大长度位 512 字节但是根据我们的分析我们最大应该设置为 MTU-3 就是 509 字节。但是由为了一帧发完只要可以满足应用需求最大设置为 244 即可。三、示例测试对于本文来说我们主要是学会去增加自定义的服务和特征现在就来完成我们前面说过的想要实现的效果。因为最后我会把我测试的源码给大家下载所以在本次测试只会把基础步骤和关键点加以说明大部分都是代码复制粘贴改名字那些工作大家自己操作起来也只是花点时间没有技术难点。3.1 增加 Indicate 属性特征值首先我们在示例基础上增加一个带 Indicate 属性的特征值 第六个特征值直接在simpleProfileAttrTbl数组后面添加完成上述操作我们就已经可以在连接后看到这个特征值了这里只是能够显示这个特征值但是数据交互逻辑我们还没有增加我们接下来还要增加一些逻辑交互数据处理的操作代码。我们需要实现几个数据处理的操作分别如下我们在之前《沁恒微蓝牙 GATT 应用框架说明》文章中讲过具备 CCCD 属性的特征值是需要绑定的在SimpleProfile_AddService函数对应位置添加绑定信息然后对应几个地方也需要增加一条代码实现一个simpleProfile_Indicate函数可以参考例程原始的函数这里比 Notify 多一个 taskID 是因为蓝牙协议规定 Indication 发送后对方必须发送一个确认响应Confirmation返回这个返回必须有个载体。我们可以不用做任何处理发送 Indication 协议栈会自动处理只有当我们想要 “确认手机是否真的收到” 时才需要应用层去处理这个响应。函数simpleProfile_WriteAttrCB中增加特征值 6 的 CCCD 读写处理最后还需要在peripheral.c中实现一下 Indicate 发送这个其实就是按照示例的 Notify 发送写就行了没特别的在周期任务中添加一下周期发送至此我们新的具备 Indicate 属性的特征值就添加完成了我们可以看一下测试效果3.2 增加服务增加服务基本就是复制一遍 gattprofile.c 和 gattprofile.h 然后还有在 peripheral.c 文件中需要增加的一些逻辑和数据收发的代码别这个部分大家需要下载示例代码自己看即可需要说明的在上面文章中基本都说明了。最后手机连接读到的效果如下不过上面 nRF Connect 我还真不知道怎么读取显示成字符串也没在网上找到如何设置因为这个服务的第一个特征值我是用字符串处理的使用沁恒微 的 Ble 调试助手可以读取显示字符串3.3 增加128bit UUID 服务最后来测试一下 128bit UUID 服务和特征值的增加。可以肯定的是即便是 128bit UUID 整体的逻辑依旧是和 16 bit 的 UUID 一样的就等于代码框架不变我们把需要注意的地方说明一下。首先是服务于特征的定义声明我们用图片可以更好的说明对于自定义的 UUID 而言服务和特征值的 UUID 可以是一样的但是同一个服务下面不同的特征值 UUID 必须不一样。其他的流程基本一致还有一个地方就是在数据读写的时候就是在my_128bit_app_read_attr_callback和my_128bit_app_write_attr_callback函数种需要处理一下分支示例为 16 bit UUID因为我们测试的时候使用 2个特征一个是 只写一个是 Notify 我们这里上一下 写回调的处理staticbStatus_tmy_128bit_app_write_attr_callback(uint16_tconnHandle,gattAttribute_t*pAttr,uint8_t*pValue,uint16_tlen,uint16_toffset,uint8_tmethod){bStatus_tstatusSUCCESS;// 初始状态设为成功uint8_tnotifyApp0xFF;// 通知应用的标志0xFF表示无通知// If attribute permissions require authorization to write, return error // 检查属性是否要求授权写入if(gattPermitAuthorWrite(pAttr-permissions)){// Insufficient authorizationreturn(ATT_ERR_INSUFFICIENT_AUTHOR);//返回授权不足错误0x08}//根据UUID类型16位或128位if(pAttr-type.lenATT_BT_UUID_SIZE){/* 16-bit UUID 的处理按照官方示例来本来觉得这里不需要 但是描述符那些是标准的 16bit 的UUID 这些需要 */// 16-bit UUIDuint16_tuuidBUILD_UINT16(pAttr-type.uuid[0],pAttr-type.uuid[1]);switch(uuid){caseGATT_CLIENT_CHAR_CFG_UUID:PRINT(write 128 CCCD\r\n);statusGATTServApp_ProcessCCCWriteReq(connHandle,pAttr,pValue,len,offset,GATT_CLIENT_CFG_NOTIFY);break;default:// Should never get here! (characteristics 2 and 4 do not have write permissions)statusATT_ERR_ATTR_NOT_FOUND;break;}}else{// 128-bit UUID// status ATT_ERR_INVALID_HANDLE;// uint64_t uuid BUILD_UINT16(pAttr-type.uuid[12], pAttr-type.uuid[13]); // 处理128位UUID/* 这里需要处理 以下到底是哪个特征值的 UUID 这个自定义的 UUID 区分当然是需要自己处理 比如我们上面定义的就是第一个成员不一样我们这里测试就用最简单的方式 */uint8_ttest_uuidpAttr-type.uuid[0];//char1 为 01 cha2为 02printf(Full 128-bit UUID: );for(inti0;i16;i){printf(%02X ,pAttr-type.uuid[i]);}printf(\n);switch(test_uuid){case1://Validate the value// Make sure its not a blob operif(offset0){if(lenmy_128bit_app_CHAR1_LEN){statusATT_ERR_INVALID_VALUE_SIZE;}}else{statusATT_ERR_ATTR_NOT_LONG;}//Write the valueif(statusSUCCESS){tmos_memcpy(pAttr-pValue,pValue,my_128bit_app_CHAR1_LEN);notifyAppmy_128bit_app_CHAR1;}break;default:// Should never get here! (characteristics 2 and 4 do not have write permissions)statusATT_ERR_ATTR_NOT_FOUND;break;}}/*若特征值发生变更通过回调通知应用层 */// If a charactersitic value changed then callback function to notify application of changeif((notifyApp!0xFF)my_128bit_app_AppCBsmy_128bit_app_AppCBs-pfnmy_128bit_app_Change){my_128bit_app_AppCBs-pfnmy_128bit_app_Change(notifyApp);}return(status);}上面主要是注意 16bit UUID 的分支依然要保留用来处理 CCCD 的写操作因为 UUID 固定是 16bit 的 0x2902。然后第二就是在判断是哪一个特征值的时候是根据自定义的 UUID 去做区分的对于上面示例而言特征值的 UUID 就只有1个字节不一样就做了一下最简单的区分实际应用需要自己考虑合理性。最后实际测试的效果如下好了到这里示例展示圆满结束代码放下载地址放在下面需要自取。四、示例下载示例工程没有做独立处理需要放在 EVT 包对应位置运行。测试芯片 CH585M下载地址 https://gitee.com/qzh_projects/CH58x/tree/master/结语本文我们分析了一下沁恒微官方从机示例种添加自定义服务和特征值的部分代码在官方示例基础上继续给大家展示了不同的服务和特征的添加示例。相信以后以后在沁恒微芯片蓝牙服务和特征值的添加上大家都游刃有余。接下来计划 GATT 部分还需要一篇文章要结合主机示例说明一下数据收发的接口流程。这种基础说明文章写起来还是蛮累的怕简单的东西都没表述好又怕涉及到的其他该知道的东西没有讲到位累 /(ㄒoㄒ)/~~好了本文就到这里谢谢大家
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2443580.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!