【WCH蓝牙系列芯片】-基于CH582开发板—添加一组自定义属性服务
-------------------------------------------------------------------------------------------------------------------------------------
在WCH沁恒官方提供的CH583的EVT资源包中,找到BLE文件中找到BLE_UART这个工程文件,
在这个程序中添加一组自定义的属性服务,自定义包含五种不同属性的服务,包含可读、可写、通知、可读可写、安全可读。
在没有添加之前,通过手机的BLE调试助手连接对应的蓝牙,可以观察到只有一个属性服务,这个属性服务是在BLE_UART程序中利用串口3与蓝牙进行数据收发功能。
因此在此基础上,将整个gattprofile.c文件添加到BLE_UART程序中,这样就可以添加一组自定义属性服务,包含五种不同属性的服务,包含可读、可写、通知、可读可写、安全可读。
具体实现步骤如下:
第一步:
在peripheral.c中找到void Peripheral_Init()初始化BLE外设中,在初始化GATT属性中添加SimpleProfile_AddService(GATT_ALL_SERVICES);
是添加一个SimpleProfile的GATT属性服务到BLE设备上,并指定添加所有的GATT属性服务。可以根据定义的特征和属性,实现自定义的功能好数据交换。
并设置SimpleProfile的特征值,定义5个特征值和对应的特征参数,包括ID、长度、数据。
1 GGS_AddService(GATT_ALL_SERVICES); // GAP
2 GATTServApp_AddService(GATT_ALL_SERVICES); // GATT attributes
3 DevInfo_AddService(); // Device Information Service 4 ble_uart_add_service(on_bleuartServiceEvt); 5 6 SimpleProfile_AddService(GATT_ALL_SERVICES); //添加了一个简单的自定义Profile服务 7 8 // Setup the SimpleProfile Characteristic Values //设置SimpleProfile的特征值 9 { 10 //定义5个特征值 11 uint8_t charValue1[SIMPLEPROFILE_CHAR1_LEN] = {1}; 12 uint8_t charValue2[SIMPLEPROFILE_CHAR2_LEN] = {23}; //23是十进制数,在手机APP中读取的数值是十六进制17 13 uint8_t charValue3[SIMPLEPROFILE_CHAR3_LEN] = {3}; 14 uint8_t charValue4[SIMPLEPROFILE_CHAR4_LEN] = {4}; 15 uint8_t charValue5[SIMPLEPROFILE_CHAR5_LEN] = {1, 2, 3, 4, 5}; 16 17 //将特征值设置对应的特征参数(特征参数的ID、长度、数据) 18 SimpleProfile_SetParameter(SIMPLEPROFILE_CHAR1, SIMPLEPROFILE_CHAR1_LEN, charValue1); 19 SimpleProfile_SetParameter(SIMPLEPROFILE_CHAR2, SIMPLEPROFILE_CHAR2_LEN, charValue2); 20 SimpleProfile_SetParameter(SIMPLEPROFILE_CHAR3, SIMPLEPROFILE_CHAR3_LEN, charValue3); 21 SimpleProfile_SetParameter(SIMPLEPROFILE_CHAR4, SIMPLEPROFILE_CHAR4_LEN, charValue4); 22 SimpleProfile_SetParameter(SIMPLEPROFILE_CHAR5, SIMPLEPROFILE_CHAR5_LEN, charValue5); 23 }
第二步:
可通过手机连接CH582,可以发现多了一个属性服务,包含可读、可写、通知、可读可写、安全可读功能。虽然是能观察到添加了属性服务,但是只有READ功能是正常使用的,其他功能还需要添加其他函数和操作。
在第一个可读可写中,只能可读功能,通过图片可以看到,接收到数据与之前定义的CHAR1特征的数据一样,都为01。
在第二个可读中,通过图片可以看到,接收到数据是十六进制的17,在初始化定义CHAR2特征的数据是十进制的23,这样转化为十六进制的17是吻合接收数据的结果。
在第五个安全可读中,通过图片可以看到,首先要蓝牙配对请求 输入初始化设定的密码为000000,才能进行配对绑定,这样接收到数据与之前定义的CHAR5特征的数据一样,都为1、2、3、4、5。
第三步:
在peripheral.c中,添加一个处理 Simple Profile 的特征值改变事件的函数,通过paramID参数,它会判断是哪个特征值发生了改变,并执行相对应的操作。其中有CHAR1 特征值改变事件和CHAR3 特征值改变事件。
1 //用于处理 Simple Profile 的特征值改变事件 2 static void simpleProfileChangeCB(uint8_t paramID, uint8_t *pValue, uint16_t len) 3 { 4 switch(paramID) 5 { 6 //表示 CHAR1 特征值改变事件 7 case SIMPLEPROFILE_CHAR1: 8 { 9 uint8_t newValue[SIMPLEPROFILE_CHAR1_LEN]; 10 tmos_memcpy(newValue, pValue, len); //将 pValue 中的数据拷贝到 newValue 数组中 11 PRINT("profile ChangeCB CHAR1.. \n"); 12 13 //打印发送的CHAR1数据 14 for (int i = 0; i < len; i++) 15 { 16 PRINT("Sent data-char1 = %02X \n", newValue[i]); // 使用十六进制格式打印每个字节的值 17 } 18 19 break; 20 } 21 22 //表示 CHAR3 特征值改变事件 23 case SIMPLEPROFILE_CHAR3: 24 { 25 uint8_t newValue[SIMPLEPROFILE_CHAR3_LEN]; 26 tmos_memcpy(newValue, pValue, len); //将 pValue 中的数据拷贝到 newValue 数组中 27 PRINT("profile ChangeCB CHAR3..\n"); //CHAR1特征值改变 28 29 //打印发送的CHAR3数据 30 for (int i = 0; i < len; i++) 31 { 32 PRINT("Sent data-char3 = %02X \n", newValue[i]); // 使用十六进制格式打印每个字节的值 33 } 34 break; 35 } 36 37 default: 38 // should not reach here! 39 break; 40 } 41 }
第四步:
添加一个指定GATT简单配置文件所需的回调函数,当特征值发生变化时,系统会自动调用相应的回调函数,以便进行特征值变化事件的处理。通过将simpleProfileChangeCB函数指定为回调函数,当Simple Profile的特征值发生变化时,系统将调用simpleProfileChangeCB函数来处理相应的事件。
1 // Simple GATT Profile Callbacks 指定GATT简单配置文件所需的回调函数 2 static simpleProfileCBs_t Peripheral_SimpleProfileCBs = 3 { 4 simpleProfileChangeCB // Characteristic value change callback //特征值发生变化时,系统会进行回调函数的处理 5 };
但是在这段程序添加后,通过编译后会报一个警告为
initialization of 'void (*)(uint8)' {aka 'void (*)(unsigned char)'} from incompatible pointer type 'void (*)(uint8_t, uint8_t *, uint16_t)' {aka 'void (*)(unsigned char, unsigned char *, short unsigned int)'} [-Wincompatible-pointer-types]
可以找到在gattprofile.h中,找到定义一个回调函数的类型,其参数为uint8_t paramID,但是在处理 Simple Profile 的特征值改变事件的函数中,定义三个参数,
所以这里需要更改一下,
将typedef void (*simpleProfileChange_t)(uint8 paramID);
更改为:typedef void (*simpleProfileChange_t)(uint8_t paramID, uint8_t *pValue, uint16_t len);定义三个参数声明
第五步:
然后在gattprofile.c中,在调用
simpleProfile_AppCBs->pfnSimpleProfileChange(notifyApp)函数,并传入参数只有一个notifyApp,
需要在加入其它两个参数,分别为pValue和len。
这样通过编译则不会报错和警告。
第六步:
定义了一个名为peripheralMTU的变量,其类型为uint8_t,用于表示连接设备的最大传输单元(MTU)大小。
MTU是指在蓝牙通信中一次数据传输的最大长度。
在蓝牙通信中,数据传输的过程是将数据分割成一个个的数据包进行传输。每个数据包的大小受到MTU的限制,即每次传输的数据大小不会超过MTU的设定值。
MTU的设定值可以影响蓝牙通信的效率和性能。
第七步:
在peripheral.c中,找到void Peripheral_Init()初始化BLE外设中,添加一个通过调用SimpleProfile_RegisterAppCBs函数将回调函数注册到SimpleGATTprofile上。
通过注册回调函数,当相关事件发生时,SimpleGATTprofile会调用注册的回调函数来处理相应的通知和响应。
这样当特征值发生变化时,系统就会进行回调函数的处理。
第八步:
现在用手机连接582蓝牙,在第一个可读可写中,可以利用WRITE发送数据,以十六进制形式发送一个值为23,通过串口打印观察数据是否正常已经被发送。
通过手机发送数据,串口观察。证实在第一个可读可写中,WRITE的属性是正常使用的。
在第三可写个中,可以利用WRITE发送数据,以十六进制形式发送一个值为77,通过串口打印观察数据是否正常已经被发送。
证实在第三个可写中,WRITE的属性是正常使用的。
第九步:
最后处理通知属性功能。在peripheral.c中添加函数,用于发送通知给连接设备的peripheraChar4 特征
1 //用于发送通知给连接设备的peripheraChar4 特征 2 static void peripheralChar4Notify(uint8_t *pValue, uint16_t len) 3 { 4 attHandleValueNoti_t noti; //用于存储通知的相关信息 5 if(len > (peripheralMTU - 3)) 6 { 7 PRINT("Too large noti\n"); 8 return; 9 } 10 noti.len = len; //表示通知中的数据长度 11 //用于存储通知的数据 12 noti.pValue = GATT_bm_alloc(peripheralConnList.connHandle, ATT_HANDLE_VALUE_NOTI, noti.len, NULL, 0); 13 if(noti.pValue) 14 { 15 tmos_memcpy(noti.pValue, pValue, noti.len); //将将 pValue 中的数据拷贝到 noti.pValue 中 16 if(simpleProfile_Notify(peripheralConnList.connHandle, ¬i) != SUCCESS) //调用 simpleProfile_Notify 函数向连接的设备发送通知 17 { 18 GATT_bm_free((gattMsg_t *)¬i, ATT_HANDLE_VALUE_NOTI); //调用 GATT_bm_free 函数释放之前分配的内存 19 } 20 } 21 }
再添加一个用于执行周期性任务的函数
1 static void performPeriodicTask(void) //用于执行周期性任务 2 { 3 uint8_t notiData[SIMPLEPROFILE_CHAR4_LEN] = {0x77}; 4 peripheralChar4Notify(notiData, SIMPLEPROFILE_CHAR4_LEN); //发送单个字节数据 0x77 的通知给连接的设备peripheraChar4 特征 5 }
这样就能发送单个字节数据 0x77 的通知给连接的设备peripheraChar4 特征。
并定义这两个静态函数
第十步:
在用于处理事件函数中uint16 Peripheral_ProcessEvent(uint8 task_id, uint16 events),根据传入的task_id和events两个参数,判断当前事件类型,并根据不同事件类型执行相应的程序。
所以在uint16 Peripheral_ProcessEvent(uint8 task_id, uint16 events)函数中,加入处理周期性事件函数和处理物理层更新事件函数
1 if(events & SBP_PERIODIC_EVT) //处理周期性事件 2 { 3 // Restart timer 4 if(SBP_PERIODIC_EVT_PERIOD) 5 { 6 tmos_start_task(Peripheral_TaskID, SBP_PERIODIC_EVT, SBP_PERIODIC_EVT_PERIOD); 7 } 8 // Perform periodic application task 9 performPeriodicTask(); 10 return (events ^ SBP_PERIODIC_EVT); 11 }
在处理周期性事件中,通过判断,进入tmos_start_task函数中,启动一个时间间隔,表示任务的执行周期,即启动任务后,它将以多久的时间间隔执行一次。调用 performPeriodicTask()函数,执行周期性的应用任务。
if(events & SBP_PHY_UPDATE_EVT) //处理物理层更新事件 { // start phy update PRINT("PHY Update %x...\n", GAPRole_UpdatePHY(peripheralConnList.connHandle, 0, GAP_PHY_BIT_LE_2M, GAP_PHY_BIT_LE_2M, GAP_PHY_OPTIONS_NOPRE)); return (events ^ SBP_PHY_UPDATE_EVT); }
根据调用GAPRole_UpdatePHY 函数更新物理层属性:通过调用 GAPRole_UpdatePHY 函数,传入 peripheralConnList.connHandle(连接句柄)、GAP_PHY_BIT_LE_2M(为物理层设置的参数)和 GAP_PHY_OPTIONS_NOPRE(物理层选项),来启动物理层更新。
然后,添加定义SBP_PERIODIC_EVT和SBP_PHY_UPDATE_EVT这两个事件标志常量,用于在任务中标识不同的事件。
第十一步:
在static void Peripheral_LinkEstablished(gapRoleEvent_t *pEvent)函数中,加入tmos_start_task(Peripheral_TaskID,SBP_PERIODIC_EVT, SBP_PERIODIC_EVT_PERIOD);这是在外围设备的建立连接成功后,启动一个周期性任务或定时器,周期或触发时间间隔由 SBP_PERIODIC_EVT_PERIOD 定义。这个周期性任务或定时器可能被用来执行一些定期的操作或任务,如定期发送数据、定期检查连接状态等。
第十二步:
利用手机连接582蓝牙后,在打开通知notify属性,打开接收通知数据后,数据就会收到十六进制的77的数据,这是与程序中用于执行周期性任务所设置的数据相一致。
这样实现了添加一组自定义属性服务,包含五种不同属性的服务,包含可读、可写、通知、可读可写、安全可读;并都能实现其对应功能。