| |
|
【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。
| |
一派掌門 二十級 |
程序運行結果: 可以看到一個自定義服務中同時擁有兩個不同的特徵值。 
| |
|
|
|