在 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_registerAPI 用于动态注册。
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>。
💎 总结与建议
-
从基础开始 :先运行
peripheral和central示例,理解 BLE 连接建立的基本流程。 -
善用工具 :使用 nRF Connect 手机 App 观察广播包、服务和数据交互,对理解 BLE 行为非常有帮助。
-
循序渐进 :从修改现有示例(如
peripheral_hr)开始,逐步添加自定义服务和特征值。 -
理解回调 :Zephyr BLE 是事件驱动的,
connected、discovered、notified等回调函数是应用逻辑的核心。 -
查阅文档:官方文档是最权威的资料,遇到问题时应首先查阅。
Zephyr 的 BLE 开发体验非常出色,其开放、完整的协议栈是你构建可靠蓝牙应用的坚实基础。希望这份指南能帮助你顺利起步。