4. BLE GATT服务与特征值定义及注册
任务说明
本案例基于 Nordic nRF52840 DK 开发板与 Zephyr RTOS,实现一个 BLE 5.0 外设。该外设暴露一个自定义 GATT 服务(UUID: 0x1234),服务中包含一个可读写的特征(UUID: 0x1235),并支持通过 CCCD(客户端特征配置描述符)使能通知。当外设端调用写入接口修改特征值时,将自动向已使能通知的客户端发送通知报文。
实现方案
使用 Zephyr 的 BT_GATT_SERVICE 宏静态定义服务与特征。特征属性设置为可读、可写、支持通知。通过回调函数处理读写请求,并在写入时触发通知发送。工程基于 nrf52840dk_nrf52840 板级定义,使用 Kconfig 最小化配置。
代码实现
/* main.c */
#include <zephyr/kernel.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/sys/printk.h>
/* 自定义服务与特征 UUID */
#define BT_UUID_CUSTOM_SERVICE_VAL \
BT_UUID_128_ENCODE(0x00001234, 0x0000, 0x1000, 0x8000, 0x00805f9b34fb)
#define BT_UUID_CUSTOM_CHAR_VAL \
BT_UUID_128_ENCODE(0x00001235, 0x0000, 0x1000, 0x8000, 0x00805f9b34fb)
#define BT_UUID_CUSTOM_SERVICE BT_UUID_DECLARE_128(BT_UUID_CUSTOM_SERVICE_VAL)
#define BT_UUID_CUSTOM_CHAR BT_UUID_DECLARE_128(BT_UUID_CUSTOM_CHAR_VAL)
/* 特征值缓冲区 */
static uint8_t char_value = 0x00;
/* 特征写入回调 */
static ssize_t on_write(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
const void *buf, uint16_t len,
uint16_t offset, uint8_t flags)
{
if (len != 1) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
}
if (offset != 0) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
}
char_value = *(uint8_t *)buf;
printk("Characteristic written: 0x%02x\n", char_value);
/* 通知所有已使能 CCCD 的客户端 */
bt_gatt_notify(NULL, &attrs[1], &char_value, sizeof(char_value));
return len;
}
/* 特征读取回调 */
static ssize_t on_read(struct bt_conn *conn,
const struct bt_gatt_attr *attr,
void *buf, uint16_t len, uint16_t offset)
{
return bt_gatt_attr_read(conn, attr, buf, len, offset,
&char_value, sizeof(char_value));
}
/* GATT 属性表 */
BT_GATT_SERVICE_DEFINE(custom_svc,
BT_GATT_PRIMARY_SERVICE(BT_UUID_CUSTOM_SERVICE),
BT_GATT_CHARACTERISTIC(BT_UUID_CUSTOM_CHAR,
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE |
BT_GATT_CHRC_NOTIFY,
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
on_read, on_write, &char_value),
BT_GATT_CCC(NULL, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
);
/* 广播参数 */
static const struct bt_data ad[] = {
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
BT_DATA_BYTES(BT_DATA_UUID128_ALL, BT_UUID_CUSTOM_SERVICE_VAL),
};
static void connected(struct bt_conn *conn, uint8_t err)
{
if (err) {
printk("Connection failed (err %u)\n", err);
} else {
printk("Connected\n");
}
}
static void disconnected(struct bt_conn *conn, uint8_t reason)
{
printk("Disconnected (reason %u)\n", reason);
}
BT_CONN_CB_DEFINE(conn_callbacks) = {
.connected = connected,
.disconnected = disconnected,
};
int main(void)
{
int err;
err = bt_enable(NULL);
if (err) {
printk("Bluetooth init failed (err %d)\n", err);
return err;
}
printk("Bluetooth initialized\n");
err = bt_le_adv_start(BT_LE_ADV_CONN, ad, ARRAY_SIZE(ad), NULL, 0);
if (err) {
printk("Advertising failed (err %d)\n", err);
return err;
}
printk("Advertising started\n");
/* 周期性修改特征值并触发通知(演示用) */
while (1) {
k_sleep(K_SECONDS(5));
char_value++;
bt_gatt_notify(NULL, &attrs[1], &char_value, sizeof(char_value));
printk("Notified: 0x%02x\n", char_value);
}
return 0;
}
工程配置
# prj.conf
CONFIG_BT=y
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_GATT_CLIENT=n
CONFIG_BT_DEVICE_NAME="Custom_GATT_Server"
CONFIG_BT_DEVICE_APPEARANCE=0
构建与烧录
west build -b nrf52840dk_nrf52840 .
west flash
验证方法
使用 nRF Connect for Desktop 或手机 nRF Connect App 扫描并连接名为 Custom_GATT_Server 的设备。连接后应看到 UUID 0x1234 的服务,其下包含 UUID 0x1235 的特征和 CCCD。
- 读验证:读取特征值,应返回 1 字节数据(初始为
0x00,随后递增)。 - 写验证:向特征写入单字节(如
0xAB),串口日志应输出Characteristic written: 0xab,随后立即收到一条通知,内容与写入值一致。 - 通知验证:在 CCCD 中写入
0x0001使能通知。此后每 5 秒应自动收到一条递增的通知报文,串口同步输出Notified: 0xXX。
判定标准:上述三项操作均能观察到预期行为,且串口日志与客户端接收数据一致,即判定案例通过。