一、开篇
本篇主要介绍一下关于BLE开发过程中必须了解的两个协议:GAP(通用访问协议)、GATT(通用属性协议)。两个协议都隶属于Host层,直接关系到应用层开发,与BLE开发人员的关系比较密切,其分别负责连接前数据广播和连接后的数据传输。
三、试验平台
Software Version:BLE_STACK_CC26XX_2.1.0
Hardware Version:CC2640/CC2650
IDE:IAR 7.40
四、GAP
1、蓝牙低能耗技术“完成”一次连接(即扫描其它设备、建立链路、发送数据、认证和适当地结束)只需3ms。而标准蓝牙技术完成相同的连接周期需要数百毫秒。
种不同类型的广播:通用的、定向的、不可连接的以及可发现的。
如上面所述,BLE设备可以进行广播。但是,一个广播设备必须在广播中包含一些有用的数据。这意味着可以通过4种广播事件中的3种进行广播:通用广播、不可连接广播以及可发现广播。进行广播时,需要在广播报文中给数据打上标签。之所以要这么做,是因为并非所有设备都能理解所有可能的广播数据。因此,需要给广播数据打上标签并指出其长度。每个数据片段均起始于一个长度域,用以指示后面的类型及数据域的长度;接下来是类型域,接收机可根据其内容判断自己是否能够理解后面的数据。事例代码:
// GAP - Advertisement data (max size = 31 bytes, though this is
// best kept short to conserve power while advertisting)
static uint8_t advertData[] =
{
// Flags; this sets the device to use limited discoverable
// mode (advertises for 30 seconds at a time) instead of general
// discoverable mode (advertises indefinitely)
0x02, // length of this data
GAP_ADTYPE_FLAGS,
DEFAULT_DISCOVERABLE_MODE | GAP_ADTYPE_FLAGS_BREDR_NOT_SUPPORTED,
// service UUID, to notify central devices what services are included
// in this peripheral
0x03, // length of this data
GAP_ADTYPE_16BIT_MORE, // some of the UUID's, but not all
#ifdef FEATURE_OAD
LO_UINT16(OAD_SERVICE_UUID),
HI_UINT16(OAD_SERVICE_UUID)
#else
LO_UINT16(SIMPLEPROFILE_SERV_UUID),
HI_UINT16(SIMPLEPROFILE_SERV_UUID)
#endif //!FEATURE_OAD
};
五、GATT
1)GATT定义了两个角色:服务器和客户端。
凭借经定义的服务、特征和特征描述符架构,并非配置文件特定的GATT客户端仍然可以遍历GATT服务器,并向用户显示特征值。特征描述符可用于显示特征值的描述符,从而可让用户了解该值。
2)GATT配置文件层级
该层级的最高层是配置文件(profile)。配置文件由实现用例所需的一个或多个服务组成。服务由特征或有关其它服务的引用组成。每一个特征包括一个值,还可能包括有关该值的可选信息。服务、特征以及特征的组件(即特征值和特征描述符)构成了配置文件数据,并全部存储在服务器的属性中。
3)服务
服务分为两种类型:主要服务和次要服务。主要服务提供设备的主要功能。次要服务提供设备的辅助功能,引用自该设备至少一项主要服务。
为了令早前的客户端保持向后兼容性,服务定义的其后修订仅可增加新引用的服务或可选特征。服务定义的其后修订也不得改变该服务定义先前修订的特征。
服务可能用于一个或多个配置文件,以实现特定用例。
4)特征
协议栈代码实现如下:
/*********************************************************************
* Profile Attributes - variables
*/
// Simple Profile Service attribute
static CONST gattAttrType_t simpleProfileService = { ATT_BT_UUID_SIZE, simpleProfileServUUID };
// Simple Profile Characteristic 1 Properties
static uint8 simpleProfileChar1Props = GATT_PROP_READ | GATT_PROP_WRITE;
// Characteristic 1 Value
static uint8 simpleProfileChar1 = 0;
// Simple Profile Characteristic 1 User Description
static uint8 simpleProfileChar1UserDesp[17] = "Characteristic 1";
/*********************************************************************
* Profile Attributes - Table
*/
static gattAttribute_t simpleProfileAttrTbl[SERVAPP_NUM_ATTR_SUPPORTED] =
{
// Simple Profile Service
{
{ ATT_BT_UUID_SIZE, primaryServiceUUID }, /* type */
GATT_PERMIT_READ, /* permissions */
0, /* handle */
(uint8 *)&simpleProfileService /* pValue */
},
// Characteristic 1 Declaration
{
{ ATT_BT_UUID_SIZE, characterUUID },
GATT_PERMIT_READ,
0,
&simpleProfileChar1Props
},
// Characteristic Value 1
{
{ ATT_BT_UUID_SIZE, simpleProfilechar1UUID },
GATT_PERMIT_READ | GATT_PERMIT_WRITE,
0,
&simpleProfileChar1
},
// Characteristic 1 User Description
{
{ ATT_BT_UUID_SIZE, charUserDescUUID },
GATT_PERMIT_READ,
0,
simpleProfileChar1UserDesp
},
5)关于句柄handle 和UUID
即是地址(记住它,类比于C语言的指针操作)
属性句柄:一台设备可以有许多的属性,例如温度传感器可能包含温度属性、设备名称属性和电池电量属性。表面看来,通过属性类型似乎足以判别某种属性。比如使用温度属性来获取温度,通过设备名称属性来获取设备名等。但是,如果设备包含了两种温度属性,比如一个室内温度传感器加上室外温度传感器,情况会变得怎样。这时你便无法直接读取温度传感器,而必须读取第一个或第二个温度属性。考虑到可能有任意多个温度传感器,问题将变得更加复杂。为了解决这个同题,我们使用了一个16位的地址,也就是属性句柄。有效的句柄范围从0x0001--xFFFF。0x0000为无效句柄,不能用于寻址属性。可以根据在软硬件或嵌入式方面的背景,把句柄(Handle)相应地想象为内存地址、端口号、属性值对应的硬件寄存器地址。
蓝牙UUID基数如下:
00000000—0000—1000—8000—00805F9B34FB
例如要发送的16位识别码位0X2A01,完整的128位UUID便是:
00002A01—0000—1000—8000—00805F9B34FB
// 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
谈到16位的UUID,通常不直接使用数值,而是冠以一个名称并加上书名号(这些UUID可以在gatt_profile_uuid.h中查看到)
0x1800 - 0x26FF用作服务类通用唯一识别码
0x2700 - 0x27FF用于标识计量单位
0x2800 - 0x28FF用于区分属性类型
0x2900 - 0x29FF用作特性描述
0x2A00- 0x7FFF用于区分特性类型
UUID,就是用来唯一识别一个特征值的ID。
handle,就是对应的attribute的一个句柄。
具体细节详见:Generic Attribute Profile (GATT)
摘抄一部分源码供参考:
/**
* GATT Service UUIDs
*/
#define IMMEDIATE_ALERT_SERV_UUID 0x1802 // Immediate Alert
#define LINK_LOSS_SERV_UUID 0x1803 // Link Loss
#define TX_PWR_LEVEL_SERV_UUID 0x1804 // Tx Power
#define CURRENT_TIME_SERV_UUID 0x1805 // Current Time Service
#define REF_TIME_UPDATE_SERV_UUID 0x1806 // Reference Time Update Service
/**
* GATT Characteristic UUIDs
*/
#define ALERT_LEVEL_UUID 0x2A06 // Alert Level
#define TX_PWR_LEVEL_UUID 0x2A07 // Tx Power Level
#define DATE_TIME_UUID 0x2A08 // Date Time
#define DAY_OF_WEEK_UUID 0x2A09 // Day of Week
#define DAY_DATE_TIME_UUID 0x2A0A // Day Date Time
#define EXACT_TIME_256_UUID 0x2A0C // Exact Time 256
/**
* GATT Unit UUIDs
*/
#define GATT_UNITLESS_UUID 0x2700 // <Symbol>, <Expressed in terms of SI base units>
#define GATT_UNIT_LENGTH_METER_UUID 0x2701 // m, m
#define GATT_UNIT_MASS_KGRAM_UUID 0x2702 // kg, kg
所有对特征值的操作,都是通过对UUID 的搜索得到对应的handle之后,通过handle来操作特征值的。对于蓝牙通信来说,其都是通过一个个不同的UUID来标识区分不同的服务,区分不同的特性,甚至服务和特性之间的类别。
六、总结
参考:1)蓝牙技术联盟官方网站
2)Bluetooth开发者门户
3)Bluetooth Spec Core_V4.1