目前共有20篇帖子。 字體大小:較小 - 100% (默認)▼  內容轉換:台灣正體▼
 
點擊 回復
448 19
【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
}
 
巨大八爪鱼

a) 如果user設置了回調讀函數,執行該函數,根據該函數的返回值決定是否回復Read Response/Read Blob Response:



若返回值為1,slave不回復Read Response/Read Blob Response給master。


若返回值為其他值,slave從pAttrValue指針所指向的區域讀attrLen個值用Read Response/Read Blob Response回復給master。

  2026-1-28 16:56 回復
一派掌門 二十級
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)
 

本帖信息

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