admin管理员组文章数量:1550528
简介
本文介绍如何给nrf52840添加自定义的服务
环境
1、zephyr v3.5.99
2、nrf connect sdk v2.6.1
具体操作
1、通过添加宏定义的方式添加服务及内部特征。
//自定义蓝牙 GATT(Generic Attribute Profile)服务
BT_GATT_SERVICE_DEFINE(
// 这个宏定义了一个 GATT 服务,并将其命名为 custom_service。
// custom_service 是这个服务的标识符,将用于引用和操作该服务。
custom_service,
// 这个宏定义了一个主服务(Primary Service)。
// PRIMARY_SERVICE_UUID 是服务的 UUID,用于唯一标识该服务。
BT_GATT_PRIMARY_SERVICE(PRIMARY_SERVICE_UUID),
// 这个宏定义了一个特征(Characteristic)。
// INDICATE_CHAR_UUID 是特征的 UUID。
// BT_GATT_CHRC_READ | BT_GATT_CHRC_INDICATE 定义了这个特征的属性(可以读取和指示)。
// BT_GATT_PERM_READ 定义了读权限。
// indicate_char_callback 是读操作的回调函数。
// NULL 表示写操作没有回调函数。
// &indicate_flag 是特征的初始值或状态指针。
BT_GATT_CHARACTERISTIC(
INDICATE_CHAR_UUID,
BT_GATT_CHRC_READ | BT_GATT_CHRC_INDICATE,
BT_GATT_PERM_READ,
indicate_char_callback,
NULL,
&indicate_flag
),
// 这个宏定义了一个客户端特征配置(Client Characteristic Configuration, CCC)。
// indicate_char_ccc_cfg_changed 是 CCC 配置变化的回调函数。
// BT_GATT_PERM_READ | BT_GATT_PERM_WRITE 设置了 CCC 的读写权限。
BT_GATT_CCC(
indicate_char_ccc_cfg_changed,
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE
),
// WRITE_CHAR_UUID 是特征的 UUID。
// BT_GATT_CHRC_WRITE 定义了这个特征的属性(可以写入)。
// BT_GATT_PERM_WRITE 定义了写权限。
// NULL 表示没有读操作的回调函数。
// write_char_callback 是写操作的回调函数。
// NULL 表示没有初始值或状态指针。
BT_GATT_CHARACTERISTIC(
WRITE_CHAR_UUID,
BT_GATT_CHRC_WRITE,
BT_GATT_PERM_WRITE,
NULL,
write_char_callback,
NULL
),
// NOTIFY_CHAR_UUID: 这是通知特征(Notify Characteristic)的 UUID。
// BT_GATT_CHRC_NOTIFY: 这个属性表明该特征支持通知(Notify)。通知是指当特征的值发生变化时,服务器可以主动将新的值发送给已订阅该特征的客户端。
// BT_GATT_PERM_NONE: 这里没有设置任何权限,因为通知特征通常不需要直接的读写权限。
// NULL: 没有读操作的回调函数,因为该特征不支持读取。
// NULL: 没有写操作的回调函数,因为该特征不支持写入。
// NULL: 没有初始值或状态指针,因为该特征不需要初始化值。
BT_GATT_CHARACTERISTIC(
NOTIFY_CHAR_UUID,
BT_GATT_CHRC_NOTIFY,
BT_GATT_PERM_NONE,
NULL,
NULL,
NULL
),
// notify_char_ccc_cfg_changed: 这是一个客户端特征配置(CCC)变化的回调函数。它将在客户端启用或禁用通知时被调用。
// BT_GATT_PERM_READ | BT_GATT_PERM_WRITE: 设置了 CCC 的读写权限。客户端可以读取和写入这个配置,以启用或禁用通知。
BT_GATT_CCC(
notify_char_ccc_cfg_changed,
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE
)
);
2、配置回调函数
这些回调函数与上述代码对应
/**
* @description: 处理特征读取操作的回调函数
* @param: conn 蓝牙连接句柄
* attr GATT 属性结构
* buf 缓冲区,用于存储读取的数据
* len 缓冲区的长度
* offset 数据偏移量
* @return: ssize_t 读取的数据长度
*/
static ssize_t indicate_char_callback(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf,
uint16_t len, uint16_t offset)
{
// 获取存储在属性中的用户数据
const char *value = attr->user_data;
// 打印读取操作的相关信息
printk("Attribute read, handle: %u, conn: %p", attr->handle, (void *)conn);
// 使用 bt_gatt_attr_read 函数读取属性值并返回读取的数据长度
return bt_gatt_attr_read(conn, attr, buf, len, offset, value, sizeof(*value));
}
/**
* @description: 处理客户端特征配置(CCC)变化的回调函数
* @param: attr GATT 属性结构
* value 新的 CCC 配置值
* @return: 无
*/
static void indicate_char_ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
{
// 根据新的配置值设置指示功能的启用状态
indicate_enabled = (value == BT_GATT_CCC_INDICATE);
}
/**
* @description: 处理指示完成的回调函数
* @param: conn 蓝牙连接句柄
* params 指示参数结构
* err 指示操作的错误代码
* @return: 无
*/
static void indicate_cb(struct bt_conn *conn, struct bt_gatt_indicate_params *params, uint8_t err)
{
// 打印指示操作的结果
printk("Indication %s\n", err != 0U ? "fail" : "success");
}
/**
* @description: 处理特征写入操作的回调函数
* @param: conn 蓝牙连接句柄
* attr GATT 属性结构
* buf 缓冲区,包含写入的数据
* len 缓冲区的长度
* offset 数据偏移量
* flags 标志,指示写入操作的属性
* @return: ssize_t 写入的数据长度
*/
static ssize_t write_char_callback(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf,
uint16_t len, uint16_t offset, uint8_t flags)
{
// 打印写入操作的相关信息
printk("Attribute(write_char) write, handle: %u, conn: %p, len:%d\n", attr->handle, (void *)conn, len);
// 检查数据偏移量是否正确
if (offset != 0) {
printk("Write led: Incorrect data offset\n");
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
}
// 将写入的数据转换为字符串格式
char *value = (uint8_t *)buf;
printk("Write data: %.*s\n", len, value);
// 返回写入的数据长度
return len;
}
/**
* @description: 处理客户端特征配置(CCC)变化的回调函数
* @param: attr GATT 属性结构
* value 新的 CCC 配置值
* @return: 无
*/
static void notify_char_ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value)
{
// 根据新的配置值设置通知功能的启用状态
notify_enabled = (value == BT_GATT_CCC_NOTIFY);
}
添加常见蓝牙服务
static ssize_t manu_name_read_callback(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf,
uint16_t len, uint16_t offset)
{
// 获取存储在属性中的用户数据
const char *value = attr->user_data;
uint16_t data_len = strlen(value);
// 打印读取操作的相关信息
printk("Attribute(manu_name_read) read, handle: %u, conn: %p, data_len:%d, data:%.*s", attr->handle, (void *)conn, data_len, data_len, attr->user_data);
// 使用 bt_gatt_attr_read 函数读取属性值并返回读取的数据长度
return bt_gatt_attr_read(conn, attr, buf, len, offset, value, data_len);
}
static ssize_t version_read_callback(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf,
uint16_t len, uint16_t offset)
{
// 获取存储在属性中的用户数据
const char *value = attr->user_data;
uint16_t data_len = strlen(value);
// 打印读取操作的相关信息
printk("Attribute(version_read) read, handle: %u, conn: %p, data_len:%d, data:%.*s", attr->handle, (void *)conn, data_len, data_len, attr->user_data);
// 使用 bt_gatt_attr_read 函数读取属性值并返回读取的数据长度
return bt_gatt_attr_read(conn, attr, buf, len, offset, value, data_len);
}
//设备信息服务(Device Information Service)UUID: 0x180A
BT_GATT_SERVICE_DEFINE(
dis_svc,
BT_GATT_PRIMARY_SERVICE(BT_UUID_DIS),
BT_GATT_CHARACTERISTIC(BT_UUID_DIS_MANUFACTURER_NAME,
BT_GATT_CHRC_READ, BT_GATT_PERM_READ,
manu_name_read_callback, NULL, "Zephyr Manufacturer"),
BT_GATT_CHARACTERISTIC(BT_UUID_DIS_FIRMWARE_REVISION,
BT_GATT_CHRC_READ, BT_GATT_PERM_READ,
version_read_callback, NULL, "1.0.1"),
);
如何发送数据
示例代码
int indicate_send(char* value, int len)
{
if(!indicate_enabled)
{
return EACCES;
}
indicate_param.attr = &custom_service.attrs[2];
indicate_param.func = indicate_cb;
indicate_param.data = value;
indicate_param.len = len;
indicate_param.destroy = NULL;
return bt_gatt_indicate(NULL, &indicate_param);
}
int notify_send(char* value, int len)
{
if(!notify_enabled)
{
return EACCES;
}
return bt_gatt_notify(NULL, &custom_service.attrs[7], value, len);
}
解释
在 Zephyr 的 Bluetooth GATT(Generic Attribute Profile)中,一个特征(Characteristic)通常需要两个 BT_GATT_ATTRIBUTE
来定义。这两个 BT_GATT_ATTRIBUTE
分别承担不同的职责,以便完整地描述和操作特征。
#define BT_GATT_CHARACTERISTIC(_uuid, _props, _perm, _read, _write, _user_data) \
BT_GATT_ATTRIBUTE(BT_UUID_GATT_CHRC, BT_GATT_PERM_READ, \
bt_gatt_attr_read_chrc, NULL, \
((struct bt_gatt_chrc[]) { \
BT_GATT_CHRC_INIT(_uuid, 0U, _props), \
})), \
BT_GATT_ATTRIBUTE(_uuid, _perm, _read, _write, _user_data)
第一个 BT_GATT_ATTRIBUTE
这个 BT_GATT_ATTRIBUTE
定义了 GATT 特征声明(Characteristic Declaration),它包含了以下信息:
- UUID:
BT_UUID_GATT_CHRC
,这是一个固定的 UUID,用于标识这是一个特征声明。 - 权限:
BT_GATT_PERM_READ
,表示这个特征声明是可读的。 - 读取回调:
bt_gatt_attr_read_chrc
,这是一个读取回调函数,用于处理特征声明的读取请求。 - 用户数据:
((struct bt_gatt_chrc[]) { BT_GATT_CHRC_INIT(_uuid, 0U, _props), })
,这是一个包含特征属性(如 UUID 和属性标志)的结构体数组。
第二个 BT_GATT_ATTRIBUTE
这个 BT_GATT_ATTRIBUTE
定义了实际的特征值(Characteristic Value),它包含了以下信息:
- UUID:特征的 UUID(由
_uuid
参数指定)。 - 权限:特征值的权限(由
_perm
参数指定),如读、写、通知等。 - 读取回调:特征值的读取回调函数(由
_read
参数指定),用于处理读取请求。 - 写入回调:特征值的写入回调函数(由
_write
参数指定),用于处理写入请求。 - 用户数据:与特征值相关的用户数据(由
_user_data
参数指定)。
为什么需要两个 BT_GATT_ATTRIBUTE
- 特征声明:第一个
BT_GATT_ATTRIBUTE
定义了特征声明,它告诉客户端这是一个特征,并描述了特征的属性(如读、写、通知等)。这使客户端知道如何与特征交互。 - 特征值:第二个
BT_GATT_ATTRIBUTE
定义了实际的特征值,它包含了特征的数据和操作回调。客户端可以通过特征声明了解到特征值的位置,然后对其进行读写操作。
版权声明:本文标题:Nordic添加蓝牙服务 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://m.elefans.com/xitong/1727246578a1104698.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论