| |
|
【ble_remote工程】给蓝牙设备添加Manufacturer Name String(0x2A29)属性 |
一派掌門 二十級 |
打开vendor/ble_remote/app_att.c文件,找到static const attribute_t my_Attributes[]数组: // 0x000C - 0x000E Device Information Service {3,ATT_PERMISSIONS_READ,2,2,(u8*)(&my_primaryServiceUUID), (u8*)(&my_devServiceUUID), 0, 0}, {0,ATT_PERMISSIONS_READ,2,sizeof(my_PnCharVal),(u8*)(&my_characterUUID), (u8*)(my_PnCharVal), 0, 0}, {0,ATT_PERMISSIONS_READ,2,sizeof (my_PnPtrs),(u8*)(&my_PnPUUID), (u8*)(my_PnPtrs), 0, 0}, 把后面两行改成: {0,ATT_PERMISSIONS_READ,2,sizeof(my_ManufacturerNameStringVal),(u8*)(&my_characterUUID), (u8*)(my_ManufacturerNameStringVal), 0, 0}, {0,ATT_PERMISSIONS_READ,2,15,(u8*)&my_ManufacturerNameStringVal[3],(u8*)"Hello Purasbar!",0,0}, 其中15是字符串"Hello Purasbar!"的长度。
找到static const u8 my_PnCharVal[5]数组,在下面再定义一个类似的数组: static const u8 my_ManufacturerNameStringVal[5] = { CHAR_PROP_READ, U16_LO(DeviceInformation_pnpID_DP_H), U16_HI(DeviceInformation_pnpID_DP_H), U16_LO(CHARACTERISTIC_UUID_MANU_NAME_STRING), U16_HI(CHARACTERISTIC_UUID_MANU_NAME_STRING) }; 其中CHARACTERISTIC_UUID_MANU_NAME_STRING的值是0x2A29。
|
一派掌門 二十級 |
改完之后,编译工程并烧录到开发板上。 手机如果之前已经配对了蓝牙设备,一定要先删除,不然看不到新增的属性或服务!!!
| |
一派掌門 二十級 |
手机上安装一个名叫nRF Connect的软件: 
| |
一派掌門 二十級 |
打开nRF Connect软件,找到蓝牙设备,点进去。 开发板的蓝牙设备名为vhid(扫描设备时的名称)、VRemote(扫描设备时的名称)或tRemote(配对后的名称),三个名字都有可能。 
| |
一派掌門 二十級 |
可以看到有Human Interface Device和Battery Service两个服务。点一下右上角的Connect。 
| |
一派掌門 二十級 |
点了Connect后,配对一下,就能看到Manufacturer Name String(0x2A29)的属性值为Hello Purasbar!了。 
| |
一派掌門 二十級 |
如果只修改了属性值的话,不需要配对,重新连接就可以看到新内容。 但如果添加了新的属性或服务的话,那就必须重新配对才行,不然完全看不到新添加的属性或服务。
| |
一派掌門 二十級 |
{0,ATT_PERMISSIONS_READ,2,12,(u8*)&my_ManufacturerNameStringVal[3],(u8*)"Hello World!",0,0}, 长度12就是字符串的总长度,不需要在末尾加\0。 
| |
一派掌門 二十級 |
my_Attributes数组每个项目的最后两个0,0分别是写函数和读函数,0表示没有。 typedef int (*att_readwrite_callback_t)(void* p);
typedef struct attribute { u16 attNum; u8 perm; u8 uuidLen; u32 attrLen; //4 bytes aligned u8* uuid; u8* pAttrValue; att_readwrite_callback_t w; att_readwrite_callback_t r; } attribute_t;
关于写函数和读函数的格式,可参阅官方文档:https://doc.telink-semi.cn/doc/zh/software/res/sdk/ble/b85m_ble_cn/b85m_ble_single_connection_cn/
| |
一派掌門 二十級 |
写函数的固定格式: int my_WriteCallback (void *p) { rf_packet_att_data_t *pw = (rf_packet_att_data_t *)p; int len = pw->l2cap - 3; //add your code //valid data is pw->dat[0] ~ pw->dat[len-1] return 1; } 要写入的数据内容是pw->dat,要写入的长度是len。 返回值固定为1。
| |
一派掌門 二十級 |
如果想在收到master的Read Request/Read Blob Request后修改即将回复的Read Response/Read Blob Response的内容,就可以注册对应的回调函数r,在回调函数里修改pAttrValue指针所指RAM的内容,并且return的值只能是0。 只能修改内容pAttrValue,无法修改长度attrLen。 这两个变量都是在static const attribute_t my_Attributes[]数组里面定义的,跟回调函数的参数p无关。
读函数的固定格式: int my_ReadCallback(void *p) { // 参数p没有用 pAttrValue[0] = xxx; pAttrValue[1] = xxx; ... pAttrValue[attrLen - 1] = xxx; return 0; // 返回值必须是0 }
| |
一派掌門 二十級 |
自定义service: [stack/ble/service/uuid.h] #define HELLO_SERVICE 0x22,0x19,0x0d,0x0c,0x0b,0x0a,0x09,0x08,0x07,0x06,0x05,0x04,0x03,0x02,0x01,0x00 [vendor/ble_remote/app_att.h] 在typedef enum的OTA_CMD_OUT_DESC_H下方加上 // hello_service hello_PS_H, hello_str_CD_H, hello_str_DP_H, [vendor/ble_remote/app_att.c] static const u8 my_HelloServiceUUID[16] = WRAPPING_BRACES(HELLO_SERVICE); static const u8 my_HelloServiceManufacturerNameStringVal[5] = { CHAR_PROP_READ, U16_LO(hello_str_DP_H), U16_HI(hello_str_DP_H), U16_LO(CHARACTERISTIC_UUID_MANU_NAME_STRING), U16_HI(CHARACTERISTIC_UUID_MANU_NAME_STRING) }; 在static const attribute_t my_Attributes[] = {的 {0,ATT_PERMISSIONS_READ, 2,sizeof (my_OtaName),(u8*)(&userdesc_UUID), (u8*)(my_OtaName), 0, 0}, #endif 的下方加上 // hello service {3,ATT_PERMISSIONS_READ,2,16,(u8*)&my_primaryServiceUUID, (u8*)my_HelloServiceUUID, 0, 0}, {0,ATT_PERMISSIONS_READ,2,sizeof(my_HelloServiceManufacturerNameStringVal),(u8*)&my_characterUUID, (u8*)my_HelloServiceManufacturerNameStringVal, 0, 0}, {0,ATT_PERMISSIONS_READ,2,23,(u8*)&my_HelloServiceManufacturerNameStringVal[3],(u8*)"Hello Hello Hello World",0,0},

| |
一派掌門 二十級 |
自定义服务(Unknown Service)的Manufacturer Name String的Value显示为N/A的解决方案: 点一下右边的下箭头按钮,再点一下右上角的Disconnect,然后再重新Connect,Value的值Hello Hello Hello World就出来了。 
| |
一派掌門 二十級 |
关于GATT中的特征声明(Characteristic Declaration): https://zh.purasbar.com/post.php?t=35099
| |
一派掌門 二十級 |
把特征值改成可读可写: (1)把my_Attributes里面的特征值的操作权限由ATT_PERMISSIONS_READ改成ATT_PERMISSIONS_RDWR。 (2)给特征声明my_HelloServiceManufacturerNameStringVal的操作权限加上CHAR_PROP_WRITE。 (3)【可选操作】可以实现写函数write_my_HelloServiceManufacturerNameString,写数据时在控制台打印要写的内容。 static const attribute_t my_Attributes[] = { ... // hello service // 服务声明 {3,ATT_PERMISSIONS_READ,2,16,(u8*)&my_primaryServiceUUID, (u8*)my_HelloServiceUUID, 0, 0}, // 特征声明 {0,ATT_PERMISSIONS_READ,2,sizeof(my_HelloServiceManufacturerNameStringVal),(u8*)&my_characterUUID, (u8*)my_HelloServiceManufacturerNameStringVal, 0, 0}, // 特征值 {0,ATT_PERMISSIONS_RDWR,2,sizeof(my_HelloServiceManufacturerNameString),(u8*)&my_HelloServiceManufacturerNameStringVal[3],my_HelloServiceManufacturerNameString,write_my_HelloServiceManufacturerNameString,0}, ... }; // 服务UUID static const u8 my_HelloServiceUUID[16] = WRAPPING_BRACES(HELLO_SERVICE); // 特征声明 static const u8 my_HelloServiceManufacturerNameStringVal[5] = { CHAR_PROP_READ | CHAR_PROP_WRITE, U16_LO(hello_str_DP_H), U16_HI(hello_str_DP_H), U16_LO(CHARACTERISTIC_UUID_MANU_NAME_STRING), U16_HI(CHARACTERISTIC_UUID_MANU_NAME_STRING) }; // 特征值 static u8 my_HelloServiceManufacturerNameString[] = " https://zh.purasbar.com/post.php?t=34981"; // 特征值的写函数 static int write_my_HelloServiceManufacturerNameString(void *p) { rf_packet_att_data_t *pw = (rf_packet_att_data_t *)p; int len = pw->l2cap - 3; printf("len=%d\n", len); if (len > sizeof(my_HelloServiceManufacturerNameString) - 1) len = sizeof(my_HelloServiceManufacturerNameString) - 1; memcpy(my_HelloServiceManufacturerNameString, pw->dat, len); my_HelloServiceManufacturerNameString[len] = '\0'; printf("my_HelloServiceManufacturerNameString=%s\n", my_HelloServiceManufacturerNameString); return 1; }
| |
一派掌門 二十級 |
点一下最右边的上箭头按钮,就可以写值(UTF8字符串值),写完后值会出现在Value Sent上,而Value值保持不变。同时在开发板的串口中可以看到写入的值。


| |
一派掌門 二十級 |
在同一个服务中再声明一个特征值2: [app_att.h] typedef enum { ... // hello_service hello_PS_H, hello_str_CD_H, hello_str_DP_H, hello_str2_CD_H, hello_str2_DP_H, ... }; [app_att.c] // 特征声明2 static const u8 my_HelloServiceManufacturerNameString2Val[5] = { CHAR_PROP_READ, // 特征值2的操作权限 U16_LO(hello_str2_DP_H), U16_HI(hello_str2_DP_H), // 特征值2的数组下标 U16_LO(CHARACTERISTIC_UUID_MODEL_NUM_STRING), U16_HI(CHARACTERISTIC_UUID_MODEL_NUM_STRING) // 特征值2的UUID }; static const attribute_t my_Attributes[] = { ... // hello service // 服务声明 {5,ATT_PERMISSIONS_READ,2,16,(u8*)&my_primaryServiceUUID, (u8*)my_HelloServiceUUID, 0, 0}, // 特征声明1 {0,ATT_PERMISSIONS_READ,2,sizeof(my_HelloServiceManufacturerNameStringVal),(u8*)&my_characterUUID, (u8*)my_HelloServiceManufacturerNameStringVal, 0, 0}, // 特征值1 {0,ATT_PERMISSIONS_RDWR,2,sizeof(my_HelloServiceManufacturerNameString),(u8*)&my_HelloServiceManufacturerNameStringVal[3],my_HelloServiceManufacturerNameString,write_my_HelloServiceManufacturerNameString,0}, // 特征声明2 {0,ATT_PERMISSIONS_READ,2,sizeof(my_HelloServiceManufacturerNameString2Val),(u8*)&my_characterUUID, (u8*)my_HelloServiceManufacturerNameString2Val, 0, 0}, // 特征值2 {0,ATT_PERMISSIONS_READ,2,19,(u8*)&my_HelloServiceManufacturerNameString2Val[3],(u8*)" @啊啊是谁都对",0,0}, ... }; 注意同一个服务的两个特征值UUID不能相同,否则会出错。 特征值1的UUID是CHARACTERISTIC_UUID_MANU_NAME_STRING=0x2a29。 特征值2换了个UUID:CHARACTERISTIC_UUID_MODEL_NUM_STRING=0x2a24。
| |
一派掌門 二十級 |
程序运行结果: 可以看到一个自定义服务中同时拥有两个不同的特征值。 
| |
|
|
|