Zephyr上开发 BLE(低功耗蓝牙)入门应用

在 Zephyr RTOS 上开发 BLE(低功耗蓝牙)应用,内容涵盖从基础概念到实战案例的完整路径。Zephyr 集成了功能丰富、高度可配置的蓝牙协议栈,并提供了从客户端(Peripheral) 到服务端(Central) 的全套支持。

🏗️ 第一部分:核心概念与开发准备

在学习具体代码前,理解 BLE 的核心架构至关重要。

  • GAP (Generic Access Profile):决定设备的角色和工作模式。在 Zephyr 中,最常用的角色是:

    • 外设 (Peripheral):通常是传感器、手表等资源受限设备,发送广播并等待连接。

    • 中心 (Central):通常是手机、网关等资源丰富的设备,扫描并发起连接。

  • GATT (Generic Attribute Profile) :定义了连接建立后的数据通信规范 ,核心概念是 Service(服务) 和 Characteristic(特征值)。

    • Service:功能的集合。

    • Characteristic:实际的数据容器,可被读写或通知。

  • 开发环境:

    • 硬件:推荐使用 Nordic nRF52840 DK 等官方支持良好的开发板。

    • 软件 :手机端可安装 nRF Connect App 进行调试;PC端可使用 west 命令行工具构建和烧录。

⚙️ 第二部分:Zephyr 项目配置

在 prj.conf 文件中启用必要的蓝牙 Kconfig 选项。

1. 作为服务端(Central)

kconfig

复制代码
# 启用蓝牙基础功能
CONFIG_BT=y

# 启用外设角色 (Peripheral)[reference:11]
CONFIG_BT_PERIPHERAL=y

# 根据需要启用 GATT 服务
CONFIG_BT_GATT_CLIENT=n  # 作为服务端,通常不启用客户端功能
CONFIG_BT_GATT_SERVER=y  # 作为服务端,启用服务器功能
2. 作为 客户端 (Peripheral)

kconfig

复制代码
# 启用蓝牙基础功能
CONFIG_BT=y

# 启用中心角色 (Central)[reference:12]
CONFIG_BT_CENTRAL=y

# 根据需要启用 GATT 客户端
CONFIG_BT_GATT_CLIENT=y  # 作为客户端,启用客户端功能
CONFIG_BT_GATT_SERVER=n  # 作为客户端,通常不启用服务器功能

注意 :CONFIG_BT_MAX_CONN 定义了最大连接数,在多连接场景下需要调整。


📡 第三部分:客户端开发实战

作为客户端,主要任务是构建并注册 GATT 服务 ,然后广播以等待中心设备连接。

1. 定义 GATT 服务

通常使用 BT_GATT_SERVICE_DEFINE 宏静态定义服务及其包含的特征值。

复制代码
// 1. 声明自定义服务 UUID (示例)
#define BT_UUID_MY_SERVICE_VAL \
    BT_UUID_128_ENCODE(0x12345678, 0x1234, 0x5678, 0x1234, 0x56789abcdef0)

// 2. 定义服务
static struct bt_gatt_attr my_attrs[] = {
    // 服务声明
    BT_GATT_PRIMARY_SERVICE(BT_UUID_DECLARE_128(BT_UUID_MY_SERVICE_VAL)),

    // 特征值声明:可读、可写、带通知
    BT_GATT_CHARACTERISTIC(BT_UUID_DECLARE_128(...), // 特征值 UUID
                           BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE | BT_GATT_CHRC_NOTIFY,
                           BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
                           my_read_cb,   // 读回调
                           my_write_cb,  // 写回调
                           NULL),        // 特征值数据
};
BT_GATT_SERVICE_DEFINE(my_svc, my_attrs);

提示 :BT_GATT_SERVICE_DEFINE 是静态声明方式,Zephyr 也提供了 bt_gatt_service_register API 用于动态注册。

2. 启动广播
复制代码
#include <zephyr/bluetooth/bluetooth.h>

void start_advertising(void)
{
    // 配置广播数据
    struct bt_le_adv_param *adv_param = BT_LE_ADV_PARAM(
        BT_LE_ADV_OPT_CONNECTABLE | BT_LE_ADV_OPT_USE_IDENTITY,
        BT_GAP_ADV_FAST_INT_MIN_2,
        BT_GAP_ADV_FAST_INT_MAX_2,
        NULL
    );

    // 配置广播数据包内容
    struct bt_data ad[] = {
        BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
        BT_DATA_BYTES(BT_DATA_UUID16_ALL, ...), // 可在此添加服务的 16-bit UUID
    };

    // 开始广播[reference:18]
    int err = bt_le_adv_start(adv_param, ad, ARRAY_SIZE(ad), NULL, 0);
    if (err) {
        printk("Advertising failed to start (err %d)\n", err);
    }
}
3. 实现回调函数

在读写回调函数中处理来自客户端的请求。例如,在写回调中更新状态,并在需要时通过 bt_gatt_notify 主动推送数据。

复制代码
static ssize_t my_write_cb(struct bt_conn *conn, const struct bt_gatt_attr *attr,
                           const void *buf, uint16_t len, uint16_t offset, uint8_t flags)
{
    // 处理写入的数据...
    // 如果需要,触发通知
    // bt_gatt_notify(conn, attr, data, len);
    return len;
}

🔍 第四部分:服务端开发实战

作为服务端,主要任务是扫描 、连接 、发现服务 和读写数据。

1. 启动扫描
复制代码
#include <zephyr/bluetooth/bluetooth.h>

static void device_found(const bt_addr_le_t *addr, int8_t rssi, uint8_t type,
                         struct net_buf_simple *ad)
{
    // 1. 过滤设备 (例如,只关心特定服务或 RSSI 阈值)[reference:21][reference:22]
    // 2. 停止扫描[reference:23]
    bt_le_scan_stop();
    // 3. 发起连接[reference:24]
    bt_conn_le_create(addr, BT_CONN_LE_CREATE_CONN, BT_LE_CONN_PARAM_DEFAULT, &default_conn);
}

void start_scanning(void)
{
    struct bt_le_scan_param scan_param = {
        .type = BT_LE_SCAN_TYPE_PASSIVE, // 或 ACTIVE[reference:25]
        .options = BT_LE_SCAN_OPT_NONE,
        .interval = BT_GAP_SCAN_FAST_INTERVAL,
        .window = BT_GAP_SCAN_FAST_WINDOW,
    };

    int err = bt_le_scan_start(&scan_param, device_found); /* 注册扫描回调[reference:26] */
    if (err) {
        printk("Scanning failed to start (err %d)\n", err);
    }
}

注意 :samples/bluetooth/central 是一个非常好的入门示例。

2. 处理连接与发现服务

连接建立后,在 connected 回调中开始服务发现。

复制代码
static void connected(struct bt_conn *conn, uint8_t err)
{
    if (err) {
        // 处理连接失败
        return;
    }
    // 开始服务发现
    struct bt_gatt_discover_params *params = ...;
    params->uuid = BT_UUID_DECLARE_16(YOUR_SERVICE_UUID);
    params->func = discover_cb;
    params->start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE;
    params->end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE;
    params->type = BT_GATT_DISCOVER_PRIMARY;

    bt_gatt_discover(conn, params);
}
3. 处理发现结果与数据交互

在发现回调中,根据结果找到目标特征值句柄,然后进行读写或订阅通知。

复制代码
static uint8_t discover_cb(struct bt_conn *conn,
                           const struct bt_gatt_attr *attr,
                           struct bt_gatt_discover_params *params)
{
    if (!attr) {
        // 发现完成
        return BT_GATT_ITER_STOP;
    }

    // 根据 attr->uuid 判断是否为目标特征值
    // 保存特征值的句柄 (bt_gatt_attr_value_handle(attr))

    // 发起读、写或订阅操作
    // bt_gatt_read(conn, &read_params);
    // bt_gatt_write(conn, &write_params);
    // bt_gatt_subscribe(conn, &subscribe_params);

    return BT_GATT_ITER_CONTINUE;
}

🧪 第五部分:官方示例与学习路径

Zephyr 官方提供了大量示例,是学习和开发的最佳参考。建议按以下路径学习:

  • 入门 (基础角色):

    • 客户端 :samples/bluetooth/peripheral 或 samples/bluetooth/peripheral_hr。

    • 服务端 :samples/bluetooth/central。

  • 进阶 (特定功能):

    • 自定义服务 :samples/bluetooth/peripheral_esp。

    • 多连接 :samples/bluetooth/central_multilink。

    • 数据传输 :samples/bluetooth/peripheral_gatt_write。

    • 广播者/观察者 :samples/bluetooth/broadcaster 和 samples/bluetooth/observer。

    • 高级应用 :samples/bluetooth/ots_client 或 samples/bluetooth/central_hr。

提示 :所有示例的构建和烧录方式相同,即使用 west build -b <your_board> <path_to_sample>。


💎 总结与建议

  1. 从基础开始 :先运行 peripheral 和 central 示例,理解 BLE 连接建立的基本流程。

  2. 善用工具 :使用 nRF Connect 手机 App 观察广播包、服务和数据交互,对理解 BLE 行为非常有帮助。

  3. 循序渐进 :从修改现有示例(如 peripheral_hr)开始,逐步添加自定义服务和特征值。

  4. 理解回调 :Zephyr BLE 是事件驱动的,connected、discovered、notified 等回调函数是应用逻辑的核心。

  5. 查阅文档:官方文档是最权威的资料,遇到问题时应首先查阅。

Zephyr 的 BLE 开发体验非常出色,其开放、完整的协议栈是你构建可靠蓝牙应用的坚实基础。希望这份指南能帮助你顺利起步。

相关推荐
FPGA小徐5 分钟前
STM32五路气敏传感器阵列电路详解:从HHC1000分压到AD8606跟随器和ADC采样
stm32·单片机·嵌入式硬件
会员果汁1 小时前
函数调用和变长参数
c语言
Horn Still Sounds2 小时前
IMX6ULL裸机|I2C通信协议 + ADC模数转换学习笔记
arm开发·笔记·单片机·嵌入式硬件
智感子3 小时前
无人值守称重:地磅系统架构与流程
嵌入式硬件
新晨单片机设计4 小时前
S011C-基于STM32智能灯光控制系统(光强、声音、人体)【Proteus仿真+Keil程序+原理图】
stm32·嵌入式硬件·proteus·智能灯光控制系统
白山编程大哥4 小时前
Lua 中使用 C 语言的用户自定义类型——userdata
c语言·junit·lua
无敌贵点大王4 小时前
RTThread学习记录13——关于要使用一个外设,RTThread与cubemx到底要怎么配合?
c语言·stm32·学习·rtthread
phltxy5 小时前
C 语言动态内存管理:从申请空间到安全释放
c语言·开发语言·ui
数智顾问5 小时前
(90页PPT)IBM集团管理驾驶舱项目蓝图规划(附下载方式)
大数据·人工智能·物联网
上位机妹子6 小时前
C 语言 数组删除指定元素(快慢指针法)
c语言·数据结构·算法