【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, &noti) != SUCCESS)  //调用 simpleProfile_Notify 函数向连接的设备发送通知
17         {
18             GATT_bm_free((gattMsg_t *)&noti, 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_idevents两个参数,判断当前事件类型,并根据不同事件类型执行相应的程序。

所以在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_EVTSBP_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的数据,这是与程序中用于执行周期性任务所设置的数据相一致。

这样实现了添加一组自定义属性服务,包含五种不同属性的服务,包含可读、可写、通知、可读可写、安全可读;并都能实现其对应功能。

 

posted on 2023-09-11 14:41  凡仕  阅读(799)  评论(3编辑  收藏  举报