【BLE】CC2541之添加特征值

来源:互联网 发布:华为4 a数据分区破坏 编辑:程序博客网 时间:2024/05/21 11:20

本篇博文最后修改时间:2017年03月06日,23:51。


一、简介

本文以SimpleBLEPeripheral工程为例,介绍如何添加一个可读、可写、可通知、20字节长的特征值char6,并用app实现数据的收发。


二、实验平台

协议栈版本:BLE-CC254x-1.4.0

编译软件: IAR 8.20.2

硬件平台: Smart RF开发板(主芯片CC2541)

手机型号: 小米4S

安卓版本:安卓5.1

安卓app:TruthBlue2_1


版权声明

博主:甜甜的大香瓜

声明:喝水不忘挖井人,转载请注明出处。

原文地址:http://blog.csdn.NET/feilusia

联系方式:897503845@qq.com

香瓜BLE之CC2541群:127442605

香瓜BLE之CC2640群:557278427

香瓜BLE之Android群:541462902

香瓜单片机之STM8/STM32群:164311667
甜甜的大香瓜的小店(淘宝店):https://shop217632629.taobao.com/?spm=2013.1.1000126.d21.hd2o8i

四、实验前提
1、在进行本文步骤前,请先阅读以下博文:
暂无

2、在进行本文步骤前,请先实现以下博文:
暂无


五、基础知识

1、特征值是什么?

答:特征值是一个变量或者一个数组,它被定义在从机端,它是主从机之间传输数据的缓冲区。

比如添加一个char6[20],它的值初始化为1、2、3、4、5、6、7、8、9、10、11、12、13、14、15、16、17、18、19、20。

当char6具有读、写属性时,主机可以通过GATT_ReadCharValue、GATT_WriteCharValue进行读、写从机的char6。

当char6具有notify通知属性时,从机可以将char6的值通知给主机。(通知的两种方式可参见本博客的《CC2541的notify》)


六、实验步骤

1、增加char6的宏定义(替换simpleGATTprofile.h中CONSTANTS段部分

// Profile Parameters#define SIMPLEPROFILE_CHAR1                   0  // RW uint8 - Profile Characteristic 1 value #define SIMPLEPROFILE_CHAR2                   1  // RW uint8 - Profile Characteristic 2 value#define SIMPLEPROFILE_CHAR3                   2  // RW uint8 - Profile Characteristic 3 value#define SIMPLEPROFILE_CHAR4                   3  // RW uint8 - Profile Characteristic 4 value#define SIMPLEPROFILE_CHAR5                   4  // RW uint8 - Profile Characteristic 5 value#define SIMPLEPROFILE_CHAR6                   5  // RW uint8 - Profile Characteristic 6 value //GUA   // Simple Profile Service UUID#define SIMPLEPROFILE_SERV_UUID               0xFFF0    // Key Pressed UUID#define SIMPLEPROFILE_CHAR1_UUID            0xFFF1#define SIMPLEPROFILE_CHAR2_UUID            0xFFF2#define SIMPLEPROFILE_CHAR3_UUID            0xFFF3#define SIMPLEPROFILE_CHAR4_UUID            0xFFF4#define SIMPLEPROFILE_CHAR5_UUID            0xFFF5#define SIMPLEPROFILE_CHAR6_UUID            0xFFF6      //GUA  // Simple Keys Profile Services bit fields#define SIMPLEPROFILE_SERVICE               0x00000001// Length of Characteristic 5 in bytes#define SIMPLEPROFILE_CHAR5_LEN           5  // Length of Characteristic 6 in bytes#define SIMPLEPROFILE_CHAR6_LEN           20       //GUA

2、增加char6的UUID(simpleGATTprofile.c的GLOBAL VARIABLES段中

// Characteristic 6 UUID: 0xFFF6CONST uint8 simpleProfilechar6UUID[ATT_BT_UUID_SIZE] ={   LO_UINT16(SIMPLEPROFILE_CHAR6_UUID), HI_UINT16(SIMPLEPROFILE_CHAR6_UUID)};
将16位的UUID拆成2个字节放到数组里。


3、增加char6的配置属性simpleGATTprofile.c的Profile Attributes - variables段中

// Simple Profile Characteristic 6 Propertiesstatic uint8 simpleProfileChar6Props = GATT_PROP_READ | GATT_PROP_WRITE | GATT_PROP_NOTIFY;// Characteristic 6 Valuestatic uint8 simpleProfileChar6[SIMPLEPROFILE_CHAR6_LEN] = {0};// Simple Profile Characteristic 6 Configuration Each client has its own// instantiation of the Client Characteristic Configuration. Reads of the// Client Characteristic Configuration only shows the configuration for// that client and writes only affect the configuration of that client.static gattCharCfg_t simpleProfileChar6Config[GATT_MAX_NUM_CONN];// Simple Profile Characteristic 6 User Descriptionstatic uint8 simpleProfileChar6UserDesp[17] = "Characteristic 6\0";

由于属性包含GATT_PROP_NOTIFY方式,所以必须要有个通知开关simpleProfileChar6Config。


4、修改属性表

1)修改属性表的大小simpleGATTprofile.c的CONSTANTS段中

#define SERVAPP_NUM_ATTR_SUPPORTED              21  
增加上面定义的char6的4个属性变量。


2)修改属性表simpleGATTprofile.c的simpleProfileAttrTbl数组中
      // Characteristic 6 Declaration      {         { ATT_BT_UUID_SIZE, characterUUID },        GATT_PERMIT_READ,         0,        &simpleProfileChar6Props       },      // Characteristic Value 6      {         { ATT_BT_UUID_SIZE, simpleProfilechar6UUID },        GATT_PERMIT_READ | GATT_PERMIT_WRITE,        0,         simpleProfileChar6       },      // Characteristic 6 configuration      {         { ATT_BT_UUID_SIZE, clientCharCfgUUID },        GATT_PERMIT_READ | GATT_PERMIT_WRITE,         0,         (uint8 *)simpleProfileChar6Config       },            // Characteristic 6 User Description      {         { ATT_BT_UUID_SIZE, charUserDescUUID },        GATT_PERMIT_READ,         0,         simpleProfileChar6UserDesp       },
此处注意两点:

第一点,

读、写属性的只有3个变量,而含有notify属性的特征值会多一个开关config,所以是4个变量。


第二点,

大多数新手搞不清楚特征值属性的“GATT_PROP_READ”与属性表的“GATT_PERMIT_READ”的区别:

打个比方说明,

属性表是一列火车,它有SERVAPP_NUM_ATTR_SUPPORTED这么多节车厢,GATT_PERMIT_READ是每节车厢的钥匙。

此时第18节~21节车厢装的是宝箱char6,GATT_PROP_READ是宝箱char6的钥匙。

虽然两把都是钥匙,但是作用的对象不一样。

实际上GATT_PERMIT_READ是针对属性表使用的,而GATT_PROP_READ是针对特征值使用的。


5、修改特征值的参数函数

1)增加char6的数值可设置的处理simpleGATTprofile.c的SimpleProfile_SetParameter函数

    case SIMPLEPROFILE_CHAR6:        if ( len == SIMPLEPROFILE_CHAR6_LEN )         {          VOID osal_memcpy( simpleProfileChar6, value, SIMPLEPROFILE_CHAR6_LEN );       }        else        {          ret = bleInvalidRange;        }        break;


2)增加char6的数值可获取的处理simpleGATTprofile.c的SimpleProfile_GetParameter函数中

    case SIMPLEPROFILE_CHAR6:        VOID osal_memcpy( value, simpleProfileChar6, SIMPLEPROFILE_CHAR6_LEN );        break; 

6、修改特征值的读写函数

1)增加char6的数值读取的处理simpleGATTprofile.c的simpleProfile_ReadAttrCB函数中

      case SIMPLEPROFILE_CHAR6_UUID:          *pLen = SIMPLEPROFILE_CHAR6_LEN;          VOID osal_memcpy( pValue, pAttr->pValue, SIMPLEPROFILE_CHAR6_LEN );          break;  

2)增加char6的数值写入的处理simpleGATTprofile.c的simpleProfile_WriteAttrCB函数中

      case SIMPLEPROFILE_CHAR6_UUID:                //Validate the value        // Make sure it's not a blob oper        if ( offset == 0 )        {          if ( len != SIMPLEPROFILE_CHAR6_LEN )          {            status = ATT_ERR_INVALID_VALUE_SIZE;          }        }        else        {          status = ATT_ERR_ATTR_NOT_LONG;        }                //Write the value        if ( status == SUCCESS )        {          VOID osal_memcpy( pAttr->pValue, pValue, SIMPLEPROFILE_CHAR6_LEN );          notifyApp = SIMPLEPROFILE_CHAR6;        }                     break;  

3)增加通知开关的处理(替换simpleGATTprofile.c的simpleProfile_WriteAttrCB函数中的GATT_CLIENT_CHAR_CFG_UUID部分)

      //通知开关管理      case GATT_CLIENT_CHAR_CFG_UUID:          //CHAR4的通知开关        if(pAttr->handle == simpleProfileAttrTbl[GUA_ATTRTBL_CHAR4_CCC_IDX].handle)         {             status = GATTServApp_ProcessCCCWriteReq( connHandle, pAttr, pValue, len,                                                     offset, GATT_CLIENT_CFG_NOTIFY );          }          //CHAR6的通知开关                else if(pAttr->handle == simpleProfileAttrTbl[GUA_ATTRTBL_CHAR6_CCC_IDX].handle)           {             status = GATTServApp_ProcessCCCWriteReq( connHandle, pAttr, pValue, len,                                                     offset, GATT_CLIENT_CFG_NOTIFY );          }            //其他情况不打开通知开关        else          {            status = ATT_ERR_INVALID_HANDLE;          }                          break;  
此处非常重要,如果没添加会导致通知开关打不开,以至于从机无法主动发送数据到主机。


4)添加两个通知开关的宏simpleGATTprofile.c的simpleProfile_WriteAttrCB函数中

#define GUA_ATTRTBL_CHAR4_VALUE_IDX             11     #define GUA_ATTRTBL_CHAR4_CCC_IDX               12     #define GUA_ATTRTBL_CHAR6_VALUE_IDX             18#define GUA_ATTRTBL_CHAR6_CCC_IDX               19
char4与char6为通知属性,因此需要明确该特征值开关的位置。

char4的特征值数据在属性表simpleProfileAttrTbl中的位置为第11位(属性表是数组,从0位开始),因此GUA_ATTRTBL_CHAR4_VALUE_IDX宏定义为11。

char4的特征值开关在属性表simpleProfileAttrTbl中的位置为第12位(属性表是数组,从0位开始),因此GUA_ATTRTBL_CHAR4_CCC_IDX宏定义为12。

char6的特征值数据在属性表simpleProfileAttrTbl中的位置为第18位(属性表是数组,从0位开始),因此GUA_ATTRTBL_CHAR6_VALUE_IDX宏定义为18。

char6的特征值开关在属性表simpleProfileAttrTbl中的位置为第19位(属性表是数组,从0位开始),因此GUA_ATTRTBL_CHAR6_CCC_IDX宏定义为19。


7、增加char6的通知开关初始化(替换simpleGATTprofile.c的SimpleProfile_AddService函数

//******************************************************************************                //name:             SimpleProfile_AddService               //introduce:        通过注册GATT属性与GATT服务,从而初始化simple服务           //parameter:        services: 服务号      //return:           none             //author:           甜甜的大香瓜                     //email:            897503845@qq.com         //QQ group          香瓜BLE之CC2541(127442605)                      //changetime:       2016.12.08                     //******************************************************************************  bStatus_t SimpleProfile_AddService( uint32 services ){  uint8 status = SUCCESS;  // Initialize Client Characteristic Configuration attributes  GATTServApp_InitCharCfg( INVALID_CONNHANDLE, simpleProfileChar4Config );  GATTServApp_InitCharCfg( INVALID_CONNHANDLE, simpleProfileChar6Config );      //GUA    // Register with Link DB to receive link status change callback  VOID linkDB_Register( simpleProfile_HandleConnStatusCB );      if ( services & SIMPLEPROFILE_SERVICE )  {    // Register GATT attribute list and CBs with GATT Server App    status = GATTServApp_RegisterService( simpleProfileAttrTbl,                                           GATT_NUM_ATTRS( simpleProfileAttrTbl ),                                          &simpleProfileCBs );  }  return ( status );}
只有通知属性的才需要初始化通知开关属性。


8、增加char6的通知开关初始化的实时更新(替换simpleGATTprofile.c的simpleProfile_HandleConnStatusCB函数

//******************************************************************************                //name:             simpleProfile_HandleConnStatusCB               //introduce:        simple服务连接状态的改变函数           //parameter:        connHandle: 连接句柄 //                  changeType: 改变类型   //return:           none             //author:           甜甜的大香瓜                     //email:            897503845@qq.com         //QQ group          香瓜BLE之CC2541(127442605)                      //changetime:       2016.12.08                     //******************************************************************************  static void simpleProfile_HandleConnStatusCB( uint16 connHandle, uint8 changeType ){   // Make sure this is not loopback connection  if ( connHandle != LOOPBACK_CONNHANDLE )  {    // Reset Client Char Config if connection has dropped    if ( ( changeType == LINKDB_STATUS_UPDATE_REMOVED )      ||         ( ( changeType == LINKDB_STATUS_UPDATE_STATEFLAGS ) &&            ( !linkDB_Up( connHandle ) ) ) )    {       GATTServApp_InitCharCfg( connHandle, simpleProfileChar4Config );      GATTServApp_InitCharCfg( connHandle, simpleProfileChar6Config );  //GUA          }  }}
会根据设备连接状态来改变特征值通知开关的状态。


9、增加char6的发送通知数据的函数

1)定义char6的发送通知数据的函数(simpleGATTprofile.c中)

//******************************************************************************                //name:             GUA_SimpleGATTprofile_Char6_Notify               //introduce:        发送char6通道的数据           //parameter:        nGUA_ConnHandle: 连接句柄 //                  npGUA_Value: 要通知的数据,范围为0~SIMPLEPROFILE_CHAR6,最多20个字节 //                  nGUA_Len: 要通知的数据的长度  //return:           none             //author:           甜甜的大香瓜                     //email:            897503845@qq.com         //QQ group          香瓜BLE之CC2541(127442605)                      //changetime:       2016.12.29                     //******************************************************************************   void GUA_SimpleGATTprofile_Char6_Notify(uint16 nGUA_ConnHandle, uint8 *pGUA_Value, uint8 nGUA_Len)  {    attHandleValueNoti_t  stGUA_Noti;    uint16 nGUA_Return;      //读出CCC的值   nGUA_Return = GATTServApp_ReadCharCfg(nGUA_ConnHandle, simpleProfileChar6Config);     //判断是否打开通知开关,打开了则发送数据    if (nGUA_Return & GATT_CLIENT_CFG_NOTIFY)   {      //填充数据    stGUA_Noti.handle = simpleProfileAttrTbl[GUA_ATTRTBL_CHAR6_VALUE_IDX].handle;      stGUA_Noti.len = nGUA_Len;      osal_memcpy(stGUA_Noti.value, pGUA_Value, nGUA_Len);        //发送数据    GATT_Notification(nGUA_ConnHandle, &stGUA_Noti, FALSE);    }  } 
注意,本函数仅适用于协议栈1.3.2和1.4.0版本。

1.4.2版本的attHandleValueNoti_t 结构体发生变化,需要多一条分配发送数据缓冲区的代码。可以参考《CC2640之自定义服务》的notify代码(不一定完全一样):

//分配发送数据缓冲区  stGUA_Noti.pValue = GATT_bm_alloc(nGUA_ConnHandle, ATT_HANDLE_VALUE_NOTI, GUAPROFILE_CHAR6_LEN, NULL);


2)声明char6的发送通知数据的函数(simpleGATTprofile.h中)

extern void GUA_SimpleGATTprofile_Char6_Notify(uint16 nGUA_ConnHandle, uint8 *pGUA_Value, uint8 nGUA_Len);

10、应用层修改

1)修改特征值初始化的数值simpleBLEPeripheral.c的SimpleBLEPeripheral_Init函数中

  // Setup the SimpleProfile Characteristic Values  {    uint8 charValue1 = 1;    uint8 charValue2 = 2;    uint8 charValue3 = 3;    uint8 charValue4 = 4;    uint8 charValue5[SIMPLEPROFILE_CHAR5_LEN] = { 1, 2, 3, 4, 5 };    uint8 charValue6[SIMPLEPROFILE_CHAR6_LEN] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20};        SimpleProfile_SetParameter( SIMPLEPROFILE_CHAR1, sizeof ( uint8 ), &charValue1 );    SimpleProfile_SetParameter( SIMPLEPROFILE_CHAR2, sizeof ( uint8 ), &charValue2 );    SimpleProfile_SetParameter( SIMPLEPROFILE_CHAR3, sizeof ( uint8 ), &charValue3 );    SimpleProfile_SetParameter( SIMPLEPROFILE_CHAR4, sizeof ( uint8 ), &charValue4 );    SimpleProfile_SetParameter( SIMPLEPROFILE_CHAR5, SIMPLEPROFILE_CHAR5_LEN, charValue5 );    SimpleProfile_SetParameter( SIMPLEPROFILE_CHAR6, SIMPLEPROFILE_CHAR6_LEN, charValue6 );      }

2)修改应用层的回调函数simpleBLEPeripheral.c的simpleProfileChangeCB函数中

//******************************************************************************                //name:             simpleProfileChangeCB               //introduce:        simple服务的回调函数//parameter:        paramID: 特征值ID //return:           none             //author:           甜甜的大香瓜                     //email:            897503845@qq.com         //QQ group          香瓜BLE之CC2541(127442605)                      //changetime:       2016.12.08                     //******************************************************************************  static void simpleProfileChangeCB( uint8 paramID ){  uint16 nGUA_ConnHandle;  uint8 nbGUA_Char6[20] = {0};      switch( paramID )  {    //char1    case SIMPLEPROFILE_CHAR1:    {      break;          }    //char3    case SIMPLEPROFILE_CHAR3:    {      break;          }    //char6    case SIMPLEPROFILE_CHAR6:      {      //获取连接句柄      GAPRole_GetParameter(GAPROLE_CONNHANDLE, &nGUA_ConnHandle);            //读取char6的数值      SimpleProfile_GetParameter(SIMPLEPROFILE_CHAR6, &nbGUA_Char6);      //发送数据        GUA_SimpleGATTprofile_Char6_Notify(nGUA_ConnHandle, nbGUA_Char6, 20);            break;          }                  default:    {      break;    }    }}


七、注意事项

手机可能缓存了之前的代码(在更新过CC2541的代码之后,都需要清除手机端的缓存!!!),因此要清除缓存,清除缓存的方法如下:

方法一:关闭app、关闭蓝牙总开关、打开蓝牙总开关、打开app。
方法二:手机重启。


八、实验结果

1、仿真并全速运行工程。

2、用安卓手机的TruthBlue2_1扫描并连接设备,可发现新增的特征值char6



3、数据通信过程如下图


①红框为app主动读取到的数值,为默认的1~20(hex显示)。

②蓝框为app主动写入cc2541的数值。

③紫框为cc2541接收到app的数值后,再将char6的数值通过通知发送出来,可见当前char6的数值已被app改变。


因此,实验成功。



4 0