作者共發了19篇帖子。 字體大小:較小 - 100% (默認)▼  內容轉換:不轉換▼
 
點擊 回復
42 18
【ble_remote工程】给蓝牙设备添加Manufacturer Name String(0x2A29)属性
一派掌門 二十級
1樓 發表于:2026-1-9 17:09
打开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。
一派掌門 二十級
3樓 發表于:2026-1-9 17:13

改完之后,编译工程并烧录到开发板上。

手机如果之前已经配对了蓝牙设备,一定要先删除,不然看不到新增的属性或服务!!!

 
一派掌門 二十級
4樓 發表于:2026-1-9 17:14

手机上安装一个名叫nRF Connect的软件:

 
一派掌門 二十級
5樓 發表于:2026-1-9 17:17

打开nRF Connect软件,找到蓝牙设备,点进去。

开发板的蓝牙设备名为vhid(扫描设备时的名称)、VRemote(扫描设备时的名称)或tRemote(配对后的名称),三个名字都有可能。

 
一派掌門 二十級
6樓 發表于:2026-1-9 17:19
可以看到有Human Interface Device和Battery Service两个服务。点一下右上角的Connect。

 
一派掌門 二十級
7樓 發表于:2026-1-9 17:21
点了Connect后,配对一下,就能看到Manufacturer Name String(0x2A29)的属性值为Hello Purasbar!了。

 
一派掌門 二十級
8樓 發表于:2026-1-9 17:25
如果只修改了属性值的话,不需要配对,重新连接就可以看到新内容。

但如果添加了新的属性或服务的话,那就必须重新配对才行,不然完全看不到新添加的属性或服务。

 
一派掌門 二十級
9樓 發表于:2026-1-9 17:29

{0,ATT_PERMISSIONS_READ,2,12,(u8*)&my_ManufacturerNameStringVal[3],(u8*)"Hello World!",0,0},

长度12就是字符串的总长度,不需要在末尾加\0。

 
一派掌門 二十級
10樓 發表于:2026-1-9 17:53

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/

 
一派掌門 二十級
11樓 發表于:2026-1-9 17:55
写函数的固定格式:
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。
 
一派掌門 二十級
12樓 發表于:2026-1-9 18:03

如果想在收到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
}
 
一派掌門 二十級
13樓 發表于:2026-1-9 19:25
自定义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},


 
一派掌門 二十級
14樓 發表于:2026-1-12 12:08
自定义服务(Unknown Service)的Manufacturer Name String的Value显示为N/A的解决方案:

点一下右边的下箭头按钮,再点一下右上角的Disconnect,然后再重新Connect,Value的值Hello Hello Hello World就出来了。

 
一派掌門 二十級
15樓 發表于:2026-1-12 18:09
关于GATT中的特征声明(Characteristic Declaration)‌:https://zh.purasbar.com/post.php?t=35099
 
一派掌門 二十級
16樓 發表于:2026-1-12 18:19
把特征值改成可读可写:
(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;
}
 
一派掌門 二十級
17樓 發表于:2026-1-12 18:26

点一下最右边的上箭头按钮,就可以写值(UTF8字符串值),写完后值会出现在Value Sent上,而Value值保持不变。同时在开发板的串口中可以看到写入的值。

 
巨大八爪鱼:一个汉字占3字节,一个emoji表情占4字节。三个汉字加一个表情,加起来就是13字节。
  2026-1-12 18:28 回復
一派掌門 二十級
18樓 發表于:2026-1-12 18:48
在同一个服务中再声明一个特征值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。
 
一派掌門 二十級
19樓 發表于:2026-1-12 18:49

程序运行结果:

可以看到一个自定义服务中同时拥有两个不同的特征值。

 

回復帖子

內容:
用戶名: 您目前是匿名發表
驗證碼:
(快捷鍵:Ctrl+Enter)
 

本帖信息

點擊數:42 回複數:18
評論數: ?
作者:巨大八爪鱼
最後回復:巨大八爪鱼
最後回復時間:2026-1-12 18:49
 
©2010-2026 Purasbar Ver2.0
除非另有聲明,本站採用創用CC姓名標示-相同方式分享 3.0 Unported許可協議進行許可。