本篇博文最后修改时间:2017年01月06日,11:06。
一、简介
本文以SimpleBLEPeripheral工程为例,介绍如何在工程中添加一个自定义的服务。
二、实验平台
协议栈版本:BLE-CC254x-1.4.0
编译软件:IAR 8.20.2
硬件平台:Smart RF开发板(主芯片CC2541)
手机平台:小米1S
APP:TruthBlue2.7
三、版权声明
博主:甜甜的大香瓜
声明:喝水不忘挖井人,转载请注明出处。
原文地址:http://blog.csdn.NET/feilusia
联系方式:897503845@qq.com
香瓜BLE之CC2541群:127442605
香瓜BLE之CC2640群:557278427
五、基础知识
暂无
六、实验步骤
1、编写并添加自定义的服务
1)编写一个GUA_Profile.c(存放在“……\BLE-CC254x-1.4.0\Projects\ble\SimpleBLEPeripheral\Source\GUA”路径下)
//******************************************************************************
//name: GUA_profile.c
//introduce: 香瓜自定义的服务,内含一个可读、可写、可通知的特征值
//author: 甜甜的大香瓜
//changetime: 2016.2.23
//email: 897503845@qq.com
//******************************************************************************
/*********************************************************************
* INCLUDES
*/
#include "bcomdef.h"
#include "OSAL.h"
#include "linkdb.h"
#include "att.h"
#include "gatt.h"
#include "gatt_uuid.h"
#include "gattservapp.h"
#include "gapbondmgr.h"
#include "GUA_Profile.h"
/*********************************************************************
* MACROS
*/
/*********************************************************************
* CONSTANTS
*/
#define SERVAPP_NUM_ATTR_SUPPORTED 5
//属性在属性表中的偏移值
#define ATTRTBL_GUA_CHAR1_IDX 2
#define ATTRTBL_GUA_CHAR1_CCC_IDX 3
/*********************************************************************
* TYPEDEFS
*/
/*********************************************************************
* GLOBAL VARIABLES
*/
// GUA Service UUID: 0xFFE0
CONST uint8 GUAServUUID[ATT_BT_UUID_SIZE] =
{
LO_UINT16(GUAPROFILE_SERV_UUID), HI_UINT16(GUAPROFILE_SERV_UUID)
};
// GUA char1 UUID: 0xFFE1
CONST uint8 GUAChar1UUID[ATT_BT_UUID_SIZE] =
{
LO_UINT16(GUAPROFILE_CHAR1_UUID), HI_UINT16(GUAPROFILE_CHAR1_UUID)
};
/*********************************************************************
* EXTERNAL VARIABLES
*/
/*********************************************************************
* EXTERNAL FUNCTIONS
*/
/*********************************************************************
* LOCAL VARIABLES
*/
static GUAProfileCBs_t *GUAProfile_AppCBs = NULL;
/*********************************************************************
* Profile Attributes - variables
*/
// GUA Service attribute
static CONST gattAttrType_t GUAProfile_Service = { ATT_BT_UUID_SIZE, GUAServUUID };
// GUA Characteristic 1 Properties
static uint8 GUAProfile_Char1_Props = GATT_PROP_READ | GATT_PROP_WRITE | GATT_PROP_NOTIFY;
// GUA Characteristic 1 Value
static uint8 GUAProfile_Char1[GUAPROFILE_CHAR1_LEN] = {0};
// GUA Characteristic 1 Configs
static gattCharCfg_t GUAProfile_Char1_Config[GATT_MAX_NUM_CONN];
// GUA Characteristic 1 User Description
static uint8 GUAProfile_Char1_UserDesp[10] = "GUA Char1\0";
/*********************************************************************
* Profile Attributes - Table
*/
static gattAttribute_t GUAProfileAttrTbl[SERVAPP_NUM_ATTR_SUPPORTED] =
{
// GUA Service
{
{ ATT_BT_UUID_SIZE, primaryServiceUUID }, /* type */
GATT_PERMIT_READ, /* permissions */
0, /* handle */
(uint8 *)&GUAProfile_Service /* pValue */
},
// GUA Characteristic 1 Declaration
{
{ ATT_BT_UUID_SIZE, characterUUID },
GATT_PERMIT_READ,
0,
&GUAProfile_Char1_Props
},
// GUA Characteristic 1 Value
{
{ ATT_BT_UUID_SIZE, GUAChar1UUID },
GATT_PERMIT_READ | GATT_PERMIT_WRITE,
0,
GUAProfile_Char1
},
// GUA Characteristic 1 configuration
{
{ ATT_BT_UUID_SIZE, clientCharCfgUUID },
GATT_PERMIT_READ | GATT_PERMIT_WRITE,
0,
(uint8 *)GUAProfile_Char1_Config
},
// GUA Characteristic 1 User Description
{
{ ATT_BT_UUID_SIZE, charUserDescUUID },
GATT_PERMIT_READ,
0,
GUAProfile_Char1_UserDesp
},
};
/*********************************************************************
* LOCAL FUNCTIONS
*/
static uint8 GUAProfile_ReadAttrCB( uint16 connHandle, gattAttribute_t *pAttr,
uint8 *pValue, uint8 *pLen, uint16 offset, uint8 maxLen );
static bStatus_t GUAProfile_WriteAttrCB( uint16 connHandle, gattAttribute_t *pAttr,
uint8 *pValue, uint8 len, uint16 offset );
static void GUAProfile_HandleConnStatusCB( uint16 connHandle, uint8 changeType );
/*********************************************************************
* PROFILE CALLBACKS
*/
// GUAProfile Service Callbacks
CONST gattServiceCBs_t GUAProfileCBs =
{
GUAProfile_ReadAttrCB, // Read callback function pointer
GUAProfile_WriteAttrCB, // Write callback function pointer
NULL // Authorization callback function pointer
};
/*********************************************************************
* PUBLIC FUNCTIONS
*/
/*********************************************************************
* @fn GUAProfile_AddService
*
* @brief Initializes the GUA service by registering
* GATT attributes with the GATT server.
*
* @param services - services to add. This is a bit map and can
* contain more than one service.
*
* @return Success or Failure
*/
bStatus_t GUAProfile_AddService( uint32 services )
{
uint8 status = SUCCESS;
// Initialize Client Characteristic Configuration attributes
GATTServApp_InitCharCfg( INVALID_CONNHANDLE, GUAProfile_Char1_Config );
// Register with Link DB to receive link status change callback
VOID linkDB_Register( GUAProfile_HandleConnStatusCB );
if ( services & GUAPROFILE_SERVICE )
{
// Register GATT attribute list and CBs with GATT Server App
status = GATTServApp_RegisterService( GUAProfileAttrTbl,
GATT_NUM_ATTRS( GUAProfileAttrTbl ),
&GUAProfileCBs );
}
return ( status );
}
/*********************************************************************
* @fn GUAProfile_RegisterAppCBs
*
* @brief Registers the application callback function. Only call
* this function once.
*
* @param callbacks - pointer to application callbacks.
*
* @return SUCCESS or bleAlreadyInRequestedMode
*/
bStatus_t GUAProfile_RegisterAppCBs( GUAProfileCBs_t *appCallbacks )
{
if ( appCallbacks )
{
GUAProfile_AppCBs = appCallbacks;
return ( SUCCESS );
}
else
{
return ( bleAlreadyInRequestedMode );
}
}
/*********************************************************************
* @fn GUAProfile_SetParameter
*
* @brief Set a GUA Profile parameter.
*
* @param param - Profile parameter ID
* @param len - length of data to right
* @param pValue - pointer to data to write. This is dependent on
* the parameter ID and WILL be cast to the appropriate
* data type (example: data type of uint16 will be cast to
* uint16 pointer).
*
* @return bStatus_t
*/
bStatus_t GUAProfile_SetParameter( uint8 param, uint8 len, void *pValue )
{
bStatus_t ret = SUCCESS;
switch ( param )
{
case GUAPROFILE_CHAR1:
if ( len == GUAPROFILE_CHAR1_LEN )
{
VOID osal_memcpy( GUAProfile_Char1, pValue, GUAPROFILE_CHAR1_LEN );
}
else
{
ret = bleInvalidRange;
}
break;
default:
ret = INVALIDPARAMETER;
break;
}
return ( ret );
}
/*********************************************************************
* @fn GUAProfile_GetParameter
*
* @brief Get a GUA Profile parameter.
*
* @param param - Profile parameter ID
* @param pValue - pointer to data to put. This is dependent on
* the parameter ID and WILL be cast to the appropriate
* data type (example: data type of uint16 will be cast to
* uint16 pointer).
*
* @return bStatus_t
*/
bStatus_t GUAProfile_GetParameter( uint8 param, void *pValue )
{
bStatus_t ret = SUCCESS;
switch ( param )
{
case GUAPROFILE_CHAR1:
VOID osal_memcpy( pValue, GUAProfile_Char1, GUAPROFILE_CHAR1_LEN );
break;
default:
ret = INVALIDPARAMETER;
break;
}
return ( ret );
}
/*********************************************************************
* @fn GUAProfile_ReadAttrCB
*
* @brief Read an attribute.
*
* @param connHandle - connection message was received on
* @param pAttr - pointer to attribute
* @param pValue - pointer to data to be read
* @param pLen - length of data to be read
* @param offset - offset of the first octet to be read
* @param maxLen - maximum length of data to be read
*
* @return Success or Failure
*/
static uint8 GUAProfile_ReadAttrCB( uint16 connHandle, gattAttribute_t *pAttr,
uint8 *pValue, uint8 *pLen, uint16 offset, uint8 maxLen )
{
bStatus_t status = SUCCESS;
// If attribute permissions require authorization to read, return error
if ( gattPermitAuthorRead( pAttr->permissions ) )
{
// Insufficient authorization
return ( ATT_ERR_INSUFFICIENT_AUTHOR );
}
// Make sure it's not a blob operation (no attributes in the profile are long
if ( offset > 0 )
{
return ( ATT_ERR_ATTR_NOT_LONG );
}
if ( pAttr->type.len == ATT_BT_UUID_SIZE )
{
// 16-bit UUID
uint16 uuid = BUILD_UINT16( pAttr->type.uuid[0], pAttr->type.uuid[1]);
switch ( uuid )
{
// No need for "GATT_SERVICE_UUID" or "GATT_CLIENT_CHAR_CFG_UUID" cases;
// gattserverapp handles this type for reads
// GUA characteristic does not have read permissions, but because it
// can be sent as a notification, it must be included here
case GUAPROFILE_CHAR1_UUID:
*pLen = GUAPROFILE_CHAR1_LEN;
VOID osal_memcpy( pValue, pAttr->pValue, GUAPROFILE_CHAR1_LEN );
break;
default:
// Should never get here!
*pLen = 0;
status = ATT_ERR_ATTR_NOT_FOUND;
break;
}
}
else
{
// 128-bit UUID
*pLen = 0;
status = ATT_ERR_INVALID_HANDLE;
}
return ( status );
}
/*********************************************************************
* @fn GUAProfile_WriteAttrCB
*
* @brief Validate attribute data prior to a write operation
*
* @param connHandle - connection message was received on
* @param pAttr - pointer to attribute
* @param pValue - pointer to data to be written
* @param len - length of data
* @param offset - offset of the first octet to be written
*
* @return Success or Failure
*/
static bStatus_t GUAProfile_WriteAttrCB( uint16 connHandle, gattAttribute_t *pAttr,
uint8 *pValue, uint8 len, uint16 offset )
{
bStatus_t status = SUCCESS;
uint8 notifyApp = 0xFF;
// If attribute permissions require authorization to write, return error
if ( gattPermitAuthorWrite( pAttr->permissions ) )
{
// Insufficient authorization
return ( ATT_ERR_INSUFFICIENT_AUTHOR );
}
if ( pAttr->type.len == ATT_BT_UUID_SIZE )
{
// 16-bit UUID
uint16 uuid = BUILD_UINT16( pAttr->type.uuid[0], pAttr->type.uuid[1]);
switch ( uuid )
{
case GUAPROFILE_CHAR1_UUID:
if ( offset == 0 )
{
if ( len != GUAPROFILE_CHAR1_LEN )
{
status = ATT_ERR_INVALID_VALUE_SIZE;
}
}
else
{
status = ATT_ERR_ATTR_NOT_LONG;
}
//将接收到的数据写进特征值中,并且置标志位
if ( status == SUCCESS )
{
VOID osal_memcpy( pAttr->pValue, pValue, GUAPROFILE_CHAR1_LEN );
notifyApp = GUAPROFILE_CHAR1;
}
break;
case GATT_CLIENT_CHAR_CFG_UUID:
//char1通道,则打开notify开关
if ( pAttr->handle == GUAProfileAttrTbl[ATTRTBL_GUA_CHAR1_CCC_IDX].handle )//GUA CHAR1 NOTIFY
{
status = GATTServApp_ProcessCCCWriteReq( connHandle, pAttr, pValue, len,
offset, GATT_CLIENT_CFG_NOTIFY );
}
else
{
status = ATT_ERR_INVALID_HANDLE;
}
break;
default:
status = ATT_ERR_ATTR_NOT_FOUND;
break;
}
}
else
{
// 128-bit UUID
status = ATT_ERR_INVALID_HANDLE;
}
// If a charactersitic value changed then callback function to notify application of change
if ( (notifyApp != 0xFF ) && GUAProfile_AppCBs && GUAProfile_AppCBs->pfnGUAProfileChange )
{
GUAProfile_AppCBs->pfnGUAProfileChange( notifyApp );
}
return ( status );
}
/*********************************************************************
* @fn GUAProfile_HandleConnStatusCB
*
* @brief GUA Profile link status change handler function.
*
* @param connHandle - connection handle
* @param changeType - type of change
*
* @return none
*/
static void GUAProfile_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, GUAProfile_Char1_Config );
}
}
}
//******************************************************************************
//name: GUAprofile_Notify
//introduce: notify发送函数
//parameter: param:特征值通道参数
// connHandle:连接句柄
// pValue:要通知的数据,范围为0~SIMPLEPROFILE_CHAR6,最多20个字节
// len:要通知的数据的长度
//return: none
//******************************************************************************
void GUAprofile_Notify( uint8 param, uint16 connHandle, uint8 *pValue, uint8 len)
{
attHandleValueNoti_t noti;
uint16 value;
switch ( param )
{
case GUAPROFILE_CHAR1:
value = GATTServApp_ReadCharCfg( connHandle, GUAProfile_Char1_Config ); //读出CCC的值
if ( value & GATT_CLIENT_CFG_NOTIFY ) //判断是否打开通知开关,打开了则发送数据
{
noti.handle = GUAProfileAttrTbl[ATTRTBL_GUA_CHAR1_IDX].handle;
noti.len = len;
osal_memcpy( noti.value, pValue, len); //数据
GATT_Notification( connHandle, ¬i, FALSE );
}
break;
default:
break;
}
}
2)写一个头文件GUA_Profile.h(存放在“……\BLE-CC254x-1.4.0\Projects\ble\SimpleBLEPeripheral\Source\GUA”路径下)
//******************************************************************************
//name: GUA_profile.h
//introduce: 香瓜自定义的服务,内含一个可读、可写、可通知的特征值
//author: 甜甜的大香瓜
//changetime: 2016.2.23
//email: 897503845@qq.com
//******************************************************************************
#ifndef GUA_PROFILE_H
#define GUA_PROFILE_H
#ifdef __cplusplus
extern "C"
{
#endif
/*********************************************************************
* INCLUDES
*/
/*********************************************************************
* CONSTANTS
*/
// Profile Parameters
#define GUAPROFILE_CHAR1 0 // RW uint8 - Profile GUA Characteristic 1 value
// GUA Service UUID
#define GUAPROFILE_SERV_UUID 0xFFE0
// GUA CHAR1 UUID
#define GUAPROFILE_CHAR1_UUID 0xFFE1
// GUA Profile Services bit fields
#define GUAPROFILE_SERVICE 0x00000001
// Length of GUA Characteristic 1 in bytes
#define GUAPROFILE_CHAR1_LEN 20
/*********************************************************************
* TYPEDEFS
*/
/*********************************************************************
* MACROS
*/
/*********************************************************************
* Profile Callbacks
*/
// Callback when a characteristic value has changed
typedef void (*GUAProfileChange_t)( uint8 paramID );
typedef struct
{
GUAProfileChange_t pfnGUAProfileChange; // Called when characteristic value changes
} GUAProfileCBs_t;
/*********************************************************************
* API FUNCTIONS
*/
/*
* GUAProfile_AddService- Initializes the GUA service by registering
* GATT attributes with the GATT server.
*
* @param services - services to add. This is a bit map and can
* contain more than one service.
*/
extern bStatus_t GUAProfile_AddService( uint32 services );
/*
* GUAProfile_RegisterAppCBs - Registers the application callback function.
* Only call this function once.
*
* appCallbacks - pointer to application callbacks.
*/
extern bStatus_t GUAProfile_RegisterAppCBs( GUAProfileCBs_t *appCallbacks );
/*
* GUAProfile_SetParameter - Set a Simple Key Profile parameter.
*
* param - Profile parameter ID
* len - length of data to right
* pValue - pointer to data to write. This is dependent on
* the parameter ID and WILL be cast to the appropriate
* data type (example: data type of uint16 will be cast to
* uint16 pointer).
*/
extern bStatus_t GUAProfile_SetParameter( uint8 param, uint8 len, void *pValue );
/*
* GUA_GetParameter - Get a Simple Key Profile parameter.
*
* param - Profile parameter ID
* pValue - pointer to data to write. This is dependent on
* the parameter ID and WILL be cast to the appropriate
* data type (example: data type of uint16 will be cast to
* uint16 pointer).
*/
extern bStatus_t GUAProfile_GetParameter( uint8 param, void *pValue );
//******************************************************************************
//name: GUAprofile_Notify
//introduce: notify发送函数
//parameter: param:特征值通道参数
// connHandle:连接句柄
// pValue:要通知的数据,范围为0~SIMPLEPROFILE_CHAR6,最多20个字节
// len:要通知的数据的长度
//return: none
//******************************************************************************
extern void GUAprofile_Notify( uint8 param, uint16 connHandle, uint8 *pValue, uint8 len);
/*********************************************************************
*********************************************************************/
#ifdef __cplusplus
}
#endif
#endif /* GUA_PROFILE_H */
3) 在工程的PROFILE分类之中,添加“GUA_Profile.c、GUA_Profile.h”
4)在IAR设置中添加按键驱动源文件路径
$PROJ_DIR$\..\..\SimpleBLEPeripheral\Source\GUA
2、在应用层中添加服务相关的代码
1)添加GUA_Profile头文件(SimpleBLEPeripheral.c中)
//香瓜
#include "GUA_Profile.h"
//香瓜
2)服务初始化(SimpleBLEPeripheral.c的SimpleBLEPeripheral_Init中)
//香瓜
//增加服务
GUAProfile_AddService(GATT_ALL_SERVICES);
//初始化特征值
uint8 GUAProfile_Char1Value[GUAPROFILE_CHAR1_LEN] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
GUAProfile_SetParameter( GUAPROFILE_CHAR1, GUAPROFILE_CHAR1_LEN, &GUAProfile_Char1Value );
//添加回调函数
VOID GUAProfile_RegisterAppCBs( &simpleBLEPeripheral_GUAProfileCBs );
//香瓜
3)定义服务的回调函数(SimpleBLEPeripheral.c中)
/*********************************************************************
* @fn GUAProfileChangeCB
*
* @brief Callback from GUAProfile indicating a value change
*
* @param paramID - parameter ID of the value that was changed.
*
* @return none
*/
static void GUAProfileChangeCB( uint8 paramID )
{
switch( paramID )
{
case GUAPROFILE_CHAR1:
uint16 notify_Handle;
uint8 bBuf[20] = {0};
uint8 *p = bBuf;
GAPRole_GetParameter( GAPROLE_CONNHANDLE, ¬ify_Handle); //获取Connection Handle
for(uint8 i = 0; i < 20; i++) //写一个20字节的测试缓冲区的数据
{
*(p+i) = i;
}
GUAprofile_Notify(GUAPROFILE_CHAR1, notify_Handle, p, 20);
break;
default:
// should not reach here!
break;
}
}
里面添加了测试代码,一旦接收到数据,就把0~19发给主机。
4)声明服务的回调函数(SimpleBLEPeripheral.c中)
//香瓜
static void GUAProfileChangeCB( uint8 paramID );
//香瓜
5)注册回调函数
//香瓜
// GUA Profile Callbacks
static GUAProfileCBs_t simpleBLEPeripheral_GUAProfileCBs =
{
GUAProfileChangeCB // Charactersitic value change callback
};
//香瓜
手机可能缓存了之前的代码(在更新过CC2541的代码之后,都需要清除手机端的缓存!!!),因此要清除缓存,清除缓存的方法如下:
方法一:关闭app、关闭蓝牙总开关、打开蓝牙总开关、打开app。
方法二:手机重启。
八、实验结果
1、所有服务的界面
添加好上面的代码之后,用手机app即可查看到新增的服务ffe0,与该服务下的特征值ffe1。
2、数据收发
在app上任意写20个字节发往从机,从机会回0~19。
注:图中显示的格式是HEX。