泰凌微BLE实操(2):ATT自定义UUID并实现Notification数据传输

本文详细介绍了蓝牙低功耗(BLE)中的GATT(Generic Attribute Profile)服务的原理和配置,包括Attribute的类型、权限、UUID、Handle等关键概念。在泰凌微的SDK中,通过定义AttributeTable来实现服务,每个Attribute包含属性值、权限、回调函数等。实操部分展示了如何声明一个UUID为0x3F00的服务,并设置两个UUID,一个用于读取(0x3F01),一个用于写入(0x3F02)。此外,还涉及到ClientCharacteristicConfiguration的配置,用于控制通知和指示功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 理论

GATT(Generic Attribute Protocol)的实质是由多个Attribute构成,每个Attribute都具有一定的信息量,当多个不同种类的Attribute组合在一起就反映出一个基本的service。
在这里插入图片描述
Attribute的基本内容和属性如下:

(1)Attribute type:UUID

UUID用来区分每个Attribute类型,全长为16字节。在/stack/service/hids.h中定义了一些标准的UUID。

(2)Atribute Handle

多个Attrbute组成一个Atribute Table,每个Attribute都有一个Attribute Handle值,用来区分不同的Attribute。slave和master建立连接后,master通过Service Discovery过程解析读取到slave的Attribute Table,并根据Attribute Handle的值来对应每一个不同的Attribute。

(3)Attribute Value

每个Attribute都有对应的Attribute Value,用来作为request、response、notification和indication的数据。


为了实现slave端的GATT service,泰凌微的SDK设计了一个Attribute Table,该Table由多个基本的Attribute组成。

Attribute结构体

typedef struct attribute
{
  u16  attNum;
  u8   perm;
  u8   uuidLen;
  u32  attrLen;    //4 bytes aligned
  u8* uuid;
  u8* pAttrValue;
  att_readwrite_callback_t w;
  att_readwrite_callback_t r;
} attribute_t;

参数

(1)attNum

  • 表示当前Attribute Table中有效Attribute数目

    • 在BLE中Attribute Handle的值从0x0001开始,所以Attribute在数组中的下标号即Attribute Handle的值
  • 指定当前的service由哪几个Attribute构成

    • 每个service的第一个Attribute的UUID必须是GATT_UUID_PRIMARY_SERVICE(0x2800),这个AttributeattNum指定从当前Attribute开始往后数总共有attNumAttribute属于该service的组成部分
  • 除了第0项Attribute和每一个service首个Attribute外,其他所有的AttributeattNum的值都必须设为0

(2)perm

permission,指定当前AttributeClient访问的权限。权限如下,Attribute的权限可以为如下某个或组合:

#define ATT_PERMISSIONS_READ 0x01
#define ATT_PERMISSIONS_WRITE 0x02
#define ATT_PERMISSIONS_AUTHEN_READ 0x61
#define ATT_PERMISSIONS_AUTHEN_WRITE 0x62
#define ATT_PERMISSIONS_SECURE_CONN_READ 0xE1
#define ATT_PERMISSIONS_READ 0x01
#define ATT_PERMISSIONS_WRITE 0x02
#define ATT_PERMISSIONS_AUTHEN_READ 0x61
#define ATT_PERMISSIONS_AUTHEN_WRITE 0x62
#define ATT_PERMISSIONS_SECURE_CONN_READ 0xE1
  • SDK暂不支持授权读和授权写

(3)uuid、uuidlen

UUID分两种:BLE标准的2 bytes UUID和Telink私有的16 bytes UUID。通过uuiduuidLen可以同时描述这两种UUID

  • uuid是一个指向flash的u8类型的指针,uuidlen为指针开始连续多少个字节

(4)pAttrValue、attrLen

表示Attributevalue的指针及其长度,该指针涉及写操作,所以可能在RAM中,也可能在Flash中

(5)回调函数w

typedef int (*att_readwrite_callback_t)(u16 connHandle, void* p);
	p指针指向master写命令的具体数值,实际p指向一片内存,结构体为rf_packet_att_data_t

int my_WriteCallback (void *p)
{
    rf_packet_att_data_t *pw = (rf_packet_att_data_t *)p;
    int len = pw->l2cap - 3;
    //add your code
    //valid data is pw->dat[0] ~ pw->dat[len-1]
    return 1;
}

调用时机:当slave收到的Attribute PDUAttribute Opcode

  • opcode = 0x12,Write Request
  • opcode = 0x52,Write Command
  • opcode = 0x18,Execute Write Request

Attribute可以没有回调函数,若不写则填空指针0。没有回调函数,则向pAttrValue指针所指向的区域写入master传来的值,长度为l2caplen-3

  • 如果pAttrValue指向flash 或 attrLen长度不够,master的写操作越界等情况需要自己写回调函数

(6)回调函数r

typedef int (*att_readwrite_callback_t)(u16 connHandle, void* p);
  • connHandle为master和slave之间的连接句柄,slave应用填BLS_CONN_HANDLE; master应该填BLM_CONN_HANDLE
  • 该回调函数同样是可选的,opcode如下:
    • opcode = 0x0A,Read Request
    • opcode = 0x0C,Read Blob Request

如果设置了回调函数,根据该函数的返回值决定是否回复Read Response/Read Blob Response。若为1则不回复,其他值则回复pAttrValue区域的长为attrLen的内容(没有设置回调函数也是这样)。

  • 也就是说,如果想修改内容,在回调函数中修改内存中的值后,返回一个非1值

对于每个service都有一个Client Characteristic Configuration属性,它的UUID为0x2902,该描述符是给任何支持通知或指示功能的特性额外增加的,允许GATT服务端配置特征值为NotificationIndication

  • 1:使能通知功能 2:使能指示功能 0:禁止通知和指示功能
  • 当APP端Enable listensing时值为0x01

2 实操

现在我们希望能够自己声明一个UUID位0x3F00的服务,并设置两个UUID:0x3F01和0x3F02,一个用来读一个用来写。注意每个声明的UUID的前面必须还要声明UUID为0x2803(my_characterUUID)的特性声明,它的属性值指针需要指示这个特性是Read/Write/Notify/Broadcast等属性权限、属性句柄(对端设备可通过属性句柄来访问该属性,起始值为1,然后逐个加1)、还有其16位的UUID。

1、添加Attribute
app_att.c中找到my_Attributes变量,实际上就是填写前面介绍的Attribute结构体的内容。

static const attribute_t my_Attributes[] = {

	{ATT_END_H - 1, 0,0,0,0,0},	// total num of attribute,ATT_END_H 为Attribute Handle的总个数
	/* 格式为:属性个数,权限,uuid长度,属性长度,uuid指针,属性值指针,写回调函数,读回调函数 */
	......在最下面添加自己的服务......
	////////////////////////////////////// APP /////////////////////////////////////////////////////
	{6,ATT_PERMISSIONS_READ,2,2,(u8*)(&my_primaryServiceUUID),(u8*)(&ServerDecl ), 0},                           //①
	{0,ATT_PERMISSIONS_READ,2,sizeof(my_AppDataIn),(u8*)(&my_characterUUID), (u8*)(my_AppDataIn), 0},            //②
	{0,ATT_PERMISSIONS_WRITE,2,sizeof(DataInVal),(u8*)(&my_appDataInUUID),(u8*)(DataInVal), inComingDataCB, 0},  //③
	{0,ATT_PERMISSIONS_READ,2,sizeof(my_AppDataOut),(u8*)(&my_characterUUID), (u8*)(my_AppDataOut), 0},          //④
	{0,ATT_PERMISSIONS_RDWR,2,sizeof(DataOutVal), (u8*)(&my_appDataOutUUID),(u8*)(&DataOutVal), 0},              //⑤
	{0,ATT_PERMISSIONS_RDWR,2,sizeof(DataCfgCCC),(u8*)(&clientCharacterCfgUUID), (u8*)(DataCfgCCC), cccUpdateCB},//⑥
};

下面来一行一行解释:
①首先需要使用UUID 0x2800来声明一个服务,服务的UUID自己指定,但不能和系统使用的重合

#define GATT_UUID_PRIMARY_SERVICE        0x2800     //!< Primary Service
#define SERVER_SERV_UUID                 0x3F00     //自定义的服务UUID
static const u16 my_primaryServiceUUID = GATT_UUID_PRIMARY_SERVICE;
static const u16 ServerDecl = SERVER_SERV_UUID;

②使用UUID 0x2803声明Service下的Characteristic

#define GATT_UUID_CHARACTER 0x2803     //!< Characteristic
static const u16 my_characterUUID = GATT_UUID_CHARACTER;
#define APP_DATA_IN_UUID  0X3F01
static const u8 my_AppDataIn[5] = {
	CHAR_PROP_WRITE,
	U16_LO(APP_DATA_IN_CD_H), U16_HI(APP_DATA_IN_CD_H),
	U16_LO(APP_DATA_IN_UUID), U16_HI(APP_DATA_IN_UUID)
};
  • CHAR_PROP_WRITE代表该属性是用来写的,即客户端可以写数据到该Attribute
  • APP_DATA_IN_CD_HAttribute Handle,它从1开始递增,在app_att.h中的ATT_HANDLE变量中相应增加我们这边添加的6行Attribute的Handle定义

③声明刚刚声明的CharacteristicCharacteristic Value

static const u16 my_appDataInUUID = APP_DATA_IN_UUID;
/* 如果没有声明后面的回调函数,则客户端写的数据就默认写到数组中 */
static uint8_t DataInVal[SIMPLESTREAMSERVER_DATAINOUT_LEN] = {0};

typedef struct{
	u8	type;       /* Data Channel Type */
	u8  rf_len;     /* Data Channel Length */
	u16	l2cap;      /* L2CAP报文的长度 */
	u16	chanid;     /* Channel ID */
	u8	att;        /* Attribute Opcode */
	u8	hl;         /* Attribute handle低字节 */
	u8	hh;         /* Attribute handle高字节 */
	u8	dat[200];   /* 该值根据自己设置的实际的MTU修改 */
}rf_packet_att_data_t;

/* 写回调函数 */
int inComingDataCB(u16 connHandle, void* p)
{
	rf_packet_att_data_t *pw = (rf_packet_att_data_t *)p;
	/* L2CAP报文长度减去Attribute Opcode和Attribute Handle的长度即实际传输的数据的长度 */
    int len = pw->l2cap - 3;
    //数据保存在pw->dat[0] ~ pw->dat[len-1]中
    
    return 1;
}

通过Notification向指定的UUID传输消息的报文格式如下:
在这里插入图片描述
这就对应了上面结构体中的内容。

④⑤声明客户端可读的AttributeChracteristicChracteristic Value,与②③类似

#define APP_DATA_OUT_UUID  0X3F02
static const u16 my_appDataOutUUID = APP_DATA_OUT_UUID;
static uint8_t DataOutVal[SIMPLESTREAMSERVER_DATAINOUT_LEN] = {0};
/* 通知的形式为Notification */
static const u8 my_AppDataOut[5] = {
	CHAR_PROP_NOTIFY,
	U16_LO(APP_DATA_OUT_CD_H), U16_HI(APP_DATA_OUT_CD_H),
	U16_LO(APP_DATA_OUT_UUID), U16_HI(APP_DATA_OUT_UUID)
};

⑥我们还希望能够实时获取客户端Client Characteristic Configuration的使能状态

#define GATT_UUID_CLIENT_CHAR_CFG 0x2902     //!< Client Characteristic Configuration
static const u16 clientCharacterCfgUUID = GATT_UUID_CLIENT_CHAR_CFG;
/* 没有声明回调函数就写到这里 */
static u8 DataCfgCCC[3] = {0,0,0};
/*  客户端的写回调函数 */
int cccUpdateCB(u16 connHandle, void* p)
{
	rf_packet_att_data_t *pw = (rf_packet_att_data_t *)p;
    int res = (pw->dat[1] << 8) | pw->dat[0];
    //------------------
	if(res == 0x0001)
	{
		/* ready */
	}
    return 1;
}

对于Client Characteristic Configuration的属性值如下:默认为0x0000,Notification使能时为0x0001。
在这里插入图片描述
资料:Bluetooth Core Specification V5.0

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

tilblackout

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值