Zephyr OS蓝牙广播(Advertising)功能实现
目录
概述
1 Advertising功能介绍
1.1 实现原理
1.2 广播类型
1.3 广播数据格式
1.4 优化建议
1.5 常见问题和解决方法
2 Nordic 蓝牙广播(Advertising)功能实现
2.1 环境准备与SDK基础
2.2 广播功能实现
2.3 广播优化与最佳实践
3 实际应用案例
3.1 iBeacon实现
3.2 环境传感器广播
3.3 功耗测量优化
3.4 广播性能优化技巧
4 常见问题解决
4.1 广播不可见
4.2 数据更新失败
4.3 高功耗问题
概述
本文主要介绍Zephyr RTOS蓝牙广播(Advertising)功能实现,Zephyr RTOS为Nordic芯片提供了强大的蓝牙支持,使开发者能够高效实现BLE功能。下面我将详细介绍在Zephyr OS上实现蓝牙广播的完整流程。
1 Advertising功能介绍
蓝牙广播是低功耗蓝牙(BLE)中设备向外发送信息的主要方式,适用于信标、传感器数据广播、设备发现等场景。下面我将详细介绍蓝牙广播的实现原理和具体方法。
1.1 实现原理
蓝牙广播的核心机制是:
设备周期性地在三个广播信道(37, 38, 39)上发送广播包
广播包包含设备信息、服务标识和自定义数据
附近设备通过扫描接收这些广播信息
设备可以设置广播参数(间隔、类型、数据等)
1.2 广播类型
广播类型 | 值 | 描述 | 适用场景 |
---|---|---|---|
ADV_IND | 0 | 可连接的非定向广播 | 通用设备发现 |
ADV_DIRECT_IND | 1 | 可连接的定向广播 | 快速重连 |
ADV_SCAN_IND | 2 | 可扫描的非定向广播 | 广播数据 |
ADV_NONCONN_IND | 3 | 不可连接的非定向广播 | 信标设备 |
ADV_DIRECT_IND_LOW | 4 | 低占空比的定向广播 | 节能设备 |
1.3 广播数据格式
1)合理组织AD结构:
// 推荐的广播数据结构
[Flags][Service UUID][Device Name][Tx Power][Manufacturer Data]
2)控制数据长度
广播数据:最大31字节扫描响应数据:最大31字节优先放置重要信息在广播数据中
1.4 优化建议
1)功耗优化
适当增加广播间隔(100ms-1s)
使用不可连接广播类型(ADV_NONCONN_IND)
在不需要时停止广播
2) 数据压缩
使用紧凑的数据格式
避免重复信息
使用自定义二进制格式替代文本
3)安全考虑:
避免广播敏感信息
使用随机设备地址
实现广播数据加密(如Eddystone-EID)
1.5 常见问题和解决方法
1)广播数据被截断
检查数据长度是否超过31字节
优先放置重要数据在开头
使用扫描响应数据补充信息
2) 设备无法被发现
确认使用了正确的广播类型
检查广播信道是否被干扰
验证广播间隔设置是否合理
3) 功耗过高
增加广播间隔
减少广播数据量
使用更节能的广播类型
4)兼容性问题:
避免使用BLE 5.0特有功能与旧设备通信
包含基本的BLE 4.0兼容数据
提供多种广播数据格式
2 Nordic 蓝牙广播(Advertising)功能实现
2.1 环境准备与SDK基础
1) 开发环境
硬件:nRF52系列开发板(如nRF52832/nRF52840)
软件:
nRF Connect SDK(最新版本)
Segger Embedded Studio 或 VSCode + nRF Connect插件
nRF Command Line Tools
2) SDK关键组件
SoftDevice:蓝牙协议栈
BLE Advertising Module:广播功能核心模块
BLE Services:GAP和GATT服务
2.2 广播功能实现
1)基础广播实现
#include <zephyr/kernel.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/gap.h>/* 广播参数配置 */
static const struct bt_le_adv_param *adv_param = BT_LE_ADV_PARAM(BT_LE_ADV_OPT_CONNECTABLE | BT_LE_ADV_OPT_USE_NAME, // 可连接且包含设备名800, // 最小广播间隔: 800*0.625ms=500ms1600, // 最大广播间隔: 1600*0.625ms=1000msNULL); // 对等设备地址(定向广播使用)/* 广播数据结构 */
static const struct bt_data ad[] = {BT_DATA_BYTES(BT_DATA_FLAGS, BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR),BT_DATA(BT_DATA_NAME_COMPLETE, CONFIG_BT_DEVICE_NAME, sizeof(CONFIG_BT_DEVICE_NAME) - 1),
};/* 扫描响应数据 */
static const struct bt_data sd[] = {BT_DATA(BT_DATA_UUID16_ALL, BT_UUID_16_ENCODE(BT_UUID_DIS_VAL)),
};void main(void)
{int err;/* 初始化蓝牙协议栈 */err = bt_enable(NULL);if (err) {printk("Bluetooth init failed (err %d)\n", err);return;}printk("Bluetooth initialized\n");/* 启动广播 */err = bt_le_adv_start(adv_param, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));if (err) {printk("Advertising failed to start (err %d)\n", err);return;}printk("Advertising successfully started\n");/* 主循环 */while (1) {k_sleep(K_SECONDS(10));// 在此添加广播更新逻辑}
}
2)高级广播功能
- 2.1) 制造商特定数据广播
/* 制造商特定数据结构 */
#define MANUFACTURER_ID 0x0059 // Nordic的厂商IDstatic uint8_t mfg_data[5] = {0x01, 0x02, 0x03, 0x04, 0x05}; // 自定义数据static struct bt_data custom_ad[] = {BT_DATA_BYTES(BT_DATA_FLAGS, BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR),BT_DATA(BT_DATA_MANUFACTURER_DATA, mfg_data, sizeof(mfg_data)),
};/* 更新广播数据 */
void update_advertising_data(void)
{// 动态更新制造商数据mfg_data[0] = (k_uptime_get_32() >> 24) & 0xFF;mfg_data[1] = (k_uptime_get_32() >> 16) & 0xFF;// 停止当前广播bt_le_adv_stop();// 使用新数据重新启动广播bt_le_adv_start(adv_param, custom_ad, ARRAY_SIZE(custom_ad), NULL, 0);
}
-2.2) 扩展广播 (BLE 5.0+)
#if defined(CONFIG_BT_EXT_ADV)
/* 扩展广播参数 */
static const struct bt_le_adv_param ext_adv_param = {.id = 0,.sid = 0,.secondary_max_skip = 0,.options = BT_LE_ADV_OPT_EXT_ADV | BT_LE_ADV_OPT_USE_NAME |BT_LE_ADV_OPT_CONNECTABLE,.interval_min = BT_GAP_ADV_FAST_INT_MIN_2, // 30ms.interval_max = BT_GAP_ADV_FAST_INT_MAX_2, // 50ms.peer = NULL,
};/* 配置扩展广播 */
void start_extended_advertising(void)
{struct bt_le_ext_adv *ext_adv;int err;/* 创建扩展广播实例 */err = bt_le_ext_adv_create(&ext_adv_param, NULL, &ext_adv);if (err) {printk("Failed to create extended advertising set (err %d)\n", err);return;}/* 设置广播数据 */err = bt_le_ext_adv_set_data(ext_adv, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));if (err) {printk("Failed to set advertising data (err %d)\n", err);return;}/* 启动扩展广播 */err = bt_le_ext_adv_start(ext_adv, BT_LE_EXT_ADV_START_DEFAULT);if (err) {printk("Failed to start extended advertising (err %d)\n", err);return;}printk("Extended advertising started\n");
}
#endif /* CONFIG_BT_EXT_ADV */
3. 动态广播间隔调整
/* 动态调整广播间隔 */
void adjust_advertising_interval(uint16_t min_interval, uint16_t max_interval)
{// 创建新的广播参数const struct bt_le_adv_param *new_param = BT_LE_ADV_PARAM(BT_LE_ADV_OPT_CONNECTABLE | BT_LE_ADV_OPT_USE_NAME,min_interval,max_interval,NULL);// 停止当前广播bt_le_adv_stop();// 使用新参数启动广播bt_le_adv_start(new_param, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));printk("Advertising interval adjusted to %d-%d ms\n",min_interval * 5 / 8, max_interval * 5 / 8);
}/* 在低功耗模式下延长广播间隔 */
void enter_low_power_mode(void)
{// 设置长间隔:2000-4000 msadjust_advertising_interval(3200, 6400);
}/* 在活跃模式下缩短广播间隔 */
void enter_active_mode(void)
{// 设置短间隔:100-200 msadjust_advertising_interval(160, 320);
}
2.3 广播优化与最佳实践
1) 广播数据压缩技巧
/* 紧凑广播数据结构 */
static const struct bt_data compressed_ad[] = {// 标志位 (1字节)BT_DATA_BYTES(BT_DATA_FLAGS, BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR),// 短名称 (4字节)BT_DATA(BT_DATA_NAME_SHORTENED, "Zeph", 4),// 服务UUID (2字节)BT_DATA_BYTES(BT_DATA_UUID16_SOME, BT_UUID_16_ENCODE(BT_UUID_HRS_VAL)),// 制造商数据 (5字节: 厂商ID + 3字节自定义)BT_DATA(BT_DATA_MANUFACTURER_DATA, (uint8_t[]){0x59, 0x00, 0x01, 0x02, 0x03}, 5),// 发射功率 (2字节)BT_DATA_BYTES(BT_DATA_TX_POWER, 0xF4) // -12 dBm
};
2) 广播功耗优化
/* 优化广播参数 */
static const struct bt_le_adv_param low_power_adv_param = BT_LE_ADV_PARAM(BT_LE_ADV_OPT_CONNECTABLE | BT_LE_ADV_OPT_LOW_POWER,1600, // 1000ms3200, // 2000msNULL);/* 降低发射功率 */
void reduce_tx_power(void)
{int err;// 设置-20dBm的发射功率err = bt_le_set_tx_power(BT_LE_TX_POWER_N4); // -20dBmif (err) {printk("Failed to set TX power (err %d)\n", err);} else {printk("TX power reduced to -20dBm\n");}
}/* 配置广播睡眠模式 */
void configure_advertising_scheduler(void)
{// 仅在白天活动 (示例)k_work_schedule(&adv_work, K_HOURS(6)); // 6AM开始广播k_work_schedule(&sleep_work, K_HOURS(18)); // 6PM停止广播
}static void start_adv_work(struct k_work *work)
{bt_le_adv_start(adv_param, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));printk("Advertising started\n");
}static void stop_adv_work(struct k_work *work)
{bt_le_adv_stop();printk("Advertising stopped\n");
}K_WORK_DELAYABLE_DEFINE(adv_work, start_adv_work);
K_WORK_DELAYABLE_DEFINE(sleep_work, stop_adv_work);
3 实际应用案例
3.1 iBeacon实现
#include <zephyr/sys/byteorder.h>/* iBeacon数据格式 */
void configure_ibeacon(void)
{const struct bt_data ibeacon_ad[] = {BT_DATA_BYTES(BT_DATA_FLAGS, BT_LE_AD_NO_BREDR),BT_DATA_BYTES(BT_DATA_MANUFACTURER_DATA,0x4c, 0x00, // Apple ID0x02, 0x15, // iBeacon类型// UUID0xE2, 0xC5, 0x6D, 0xB5, 0xDF, 0xFB, 0x48, 0xD2,0xB0, 0x60, 0xD0, 0xF5, 0xA7, 0x10, 0x96, 0xE0,// Major0x00, 0x01,// Minor0x00, 0x02,// Tx Power0xC5)};// 使用不可连接广播const struct bt_le_adv_param *ibeacon_param = BT_LE_ADV_PARAM(BT_LE_ADV_OPT_NONE, 160, // 100ms320, // 200msNULL);// 启动iBeacon广播bt_le_adv_start(ibeacon_param, ibeacon_ad, ARRAY_SIZE(ibeacon_ad), NULL, 0);printk("iBeacon advertising started\n");
}
3.2 环境传感器广播
/* 传感器数据结构 */
struct sensor_data {int16_t temperature; // 温度 (0.01°C)uint16_t humidity; // 湿度 (0.01%)uint8_t battery; // 电池电量 (0-100%)
};/* 更新传感器广播数据 */
void update_sensor_advertising(struct sensor_data *data)
{uint8_t mfg_data[7];// 厂商ID (Nordic)mfg_data[0] = 0x59;mfg_data[1] = 0x00;// 温度 (小端序)sys_put_le16(data->temperature, &mfg_data[2]);// 湿度 (小端序)sys_put_le16(data->humidity, &mfg_data[4]);// 电池电量mfg_data[6] = data->battery;// 更新广播数据const struct bt_data sensor_ad[] = {BT_DATA_BYTES(BT_DATA_FLAGS, BT_LE_AD_NO_BREDR),BT_DATA(BT_DATA_MANUFACTURER_DATA, mfg_data, sizeof(mfg_data))};// 停止并重新启动广播以更新数据bt_le_adv_stop();bt_le_adv_start(adv_param, sensor_ad, ARRAY_SIZE(sensor_ad), NULL, 0);printk("Sensor data updated: T=%.2fC, H=%.2f%%, Bat=%d%%\n",data->temperature / 100.0, data->humidity / 100.0, data->battery);
}
3.3 功耗测量优化
/* 功耗测量代码片段 */
void measure_power_consumption(void)
{uint32_t start_time = k_uptime_get_32();uint32_t start_cycles = k_cycle_get_32();// 执行广播操作bt_le_adv_start(/* 参数 */);k_sleep(K_MSEC(5000));bt_le_adv_stop();uint32_t duration_ms = k_uptime_get_32() - start_time;uint64_t total_cycles = k_cycle_get_32() - start_cycles;// 计算平均电流(需要硬件测量校准)float avg_current = (total_cycles * 1000.0) / (duration_ms * sys_clock_hw_cycles_per_sec());printk("Advertising power: %.2f mA\n", avg_current);
}
3.4 广播性能优化技巧
数据压缩:使用自定义二进制格式而非文本
广播间隔:根据应用场景动态调整间隔
发现阶段:短间隔(100-200ms)
稳定状态:长间隔(1-2秒)
广播信道:优先使用信道37(最少干扰)
数据分片:对大型数据使用扫描响应
广播时间窗口:仅在需要时广播
4 常见问题解决
4.1 广播不可见
// 在代码中添加验证
if (!bt_le_adv_is_enabled()) {printk("Advertising not enabled!\n");
}// 检查广播参数
if (adv_param->options & BT_LE_ADV_OPT_SCANNABLE) {printk("Broadcast is scannable\n");
}
4.2 数据更新失败
void safe_update_advertising(void)
{static struct k_mutex adv_mutex;k_mutex_lock(&adv_mutex, K_FOREVER);bt_le_adv_stop();// 更新数据bt_le_adv_start(/* 新参数 */);k_mutex_unlock(&adv_mutex);
}
4.3 高功耗问题
动态调整广播间隔
使用-20dBm到-12dBm的发射功率
实现广播睡眠计划
// 1. 降低发射功率
bt_le_set_tx_power(BT_LE_TX_POWER_N8); // -8dBm// 2. 增加广播间隔
adjust_advertising_interval(3200, 6400); // 2-4秒// 3. 使用不可连接广播
const struct bt_le_adv_param *non_conn_param = BT_LE_ADV_PARAM(BT_LE_ADV_OPT_NONE, 1600, 3200, NULL);