4. BLE 5.0 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. 读验证:读取特征值,应返回 1 字节数据(初始为 0x00,随后递增)。
  2. 写验证:向特征写入单字节(如 0xAB),串口日志应输出 Characteristic written: 0xab,随后立即收到一条通知,内容与写入值一致。
  3. 通知验证:在 CCCD 中写入 0x0001 使能通知。此后每 5 秒应自动收到一条递增的通知报文,串口同步输出 Notified: 0xXX。

判定标准:上述三项操作均能观察到预期行为,且串口日志与客户端接收数据一致,即判定案例通过。

GATT 服务与特征交互时序 BLE 客户端 (nRF Connect App) BLE 外设 (nRF52840 DK) GATT 服务 UUID: 0x1234 特征 UUID: 0x1235 读 | 写 | 通知 CCCD 0x0001 = 通知使能 值: 0x00~0xFF 连接 + 服务发现 读请求 读响应 (1字节) 写请求 (1字节) 写响应 CCCD 写 0x0001 通知 (1字节)