目录
概述
bt_le_per_adv_set_data()
是 Zephyr RTOS 蓝牙协议栈中用于设置周期性广播数据的关键函数。它允许设备配置周期性广播的数据内容,这是蓝牙 5.0 及以上版本支持的高级特性。该函数是实现高效周期性广播系统的核心,通过合理设计数据结构和更新机制,可以构建从低功耗传感器网络到高质量音频广播等各种应用。
1 函数接口介绍
1.1 函数原型
int bt_le_per_adv_set_data(struct bt_le_ext_adv *adv,
const uint8_t *data,
uint8_t len);
参数说明
参数 | 类型 | 说明 |
---|---|---|
adv | struct bt_le_ext_adv* | 指向已创建的扩展广播实例的指针 |
data | const uint8_t* | 周期性广播数据缓冲区 |
len | uint8_t | 数据长度(最大 252 字节) |
返回值
返回值 | 说明 |
---|---|
0 | 设置成功 |
-EINVAL | 无效参数 |
-ENOMEM | 内存不足 |
-EIO | 底层 HCI 错误 |
1.2 功能详解
1) 配置周期性广播内容
定义周期性广播中传输的实际数据内容
数据格式遵循标准蓝牙 AD 结构(长度-类型-值)
2) 与主广播分离
周期性广播数据独立于主扩展广播数据
允许传输更大、更频繁更新的数据集
3) 数据格式要求
必须符合蓝牙 SIG 定义的 AD 数据结构
典型结构:
// 示例数据格式 uint8_t per_adv_data[] = { 0x02, BT_DATA_FLAGS, BT_LE_AD_NO_BREDR, // 广播标志 0x0A, BT_DATA_MANUFACTURER_DATA, 0xCD, 0x01, ... // 厂商自定义数据 0x10, BT_DATA_SVC_DATA16, 0xAA, 0xFE, ... // 服务数据 };
2 使用方法
2.1 创建流程
2.1.1 创建扩展广播实例
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/gap.h>
static struct bt_le_ext_adv *adv;
// 创建扩展广播实例
int create_adv_instance(void)
{
struct bt_le_adv_param adv_params = {
.id = BT_ID_DEFAULT,
.sid = 1,
.secondary_max_skip = 0,
.options = BT_LE_ADV_OPT_EXT_ADV |
BT_LE_ADV_OPT_PERIODIC,
.interval_min = BT_GAP_ADV_SLOW_INT_MIN,
.interval_max = BT_GAP_ADV_SLOW_INT_MAX,
};
return bt_le_ext_adv_create(&adv_params, NULL, &adv);
}
2.1.2 设置周期性广播数据
// 设置周期性广播数据
int set_periodic_data(void)
{
// 示例:温度传感器数据
uint8_t temp_data[] = {
0x02, BT_DATA_FLAGS, BT_LE_AD_NO_BREDR, // 广播标志
0x03, BT_DATA_TEMP_SERVICE, 0xAA, 0xBB, // 温度服务标识
0x05, BT_DATA_SENSOR_VALUE, 0x00, 0x00, 0x00, 0x00 // 4字节温度值
};
// 更新温度值(小端格式)
float temperature = 23.5f;
memcpy(&temp_data[8], &temperature, sizeof(float));
return bt_le_per_adv_set_data(adv, temp_data, sizeof(temp_data));
}
2.1.3 配置周期性广播参数
// 配置周期性广播参数
int config_periodic_adv(void)
{
struct bt_le_per_adv_param per_adv_params = {
.interval_min = BT_GAP_PER_ADV_SLOW_INT_MIN, // 1.28秒
.interval_max = BT_GAP_PER_ADV_SLOW_INT_MIN, // 固定间隔
.options = BT_LE_PER_ADV_OPT_USE_TX_POWER, // 包含发射功率
};
return bt_le_per_adv_set_param(adv, &per_adv_params);
}
2.1.4 启动广播
// 启动广播
int start_advertising(void)
{
int err;
// 启动扩展广播
struct bt_le_ext_adv_start_param ext_adv_params = {0};
if ((err = bt_le_ext_adv_start(adv, &ext_adv_params))) {
return err;
}
// 启动周期性广播
return bt_le_per_adv_start(adv);
}
2.2 主流程函数
void main(void)
{
bt_enable(NULL); // 初始化蓝牙
create_adv_instance();
set_periodic_data();
config_periodic_adv();
start_advertising();
while (1) {
k_sleep(K_SECONDS(30));
update_sensor_data(); // 定期更新广播数据
}
}
// 更新传感器数据
void update_sensor_data(void)
{
// 1. 停止周期性广播
bt_le_per_adv_stop(adv);
// 2. 更新数据
set_periodic_data();
// 3. 重新启动
bt_le_per_adv_start(adv);
}
2.3 关键配置 (prj.conf)
# 基础蓝牙配置
CONFIG_BT=y
CONFIG_BT_DEBUG_LOG=y
# 扩展广播和周期性广播
CONFIG_BT_EXT_ADV=y
CONFIG_BT_PER_ADV=y
# 增加缓冲区大小
CONFIG_BT_BUF_ACL_TX_SIZE=255
CONFIG_BT_BUF_ACL_RX_SIZE=255
# 支持的最大广播集
CONFIG_BT_EXT_ADV_MAX_ADV_SET=2
3 高级用法
3.1 大数据分片传输
#define CHUNK_SIZE 200
uint8_t large_data[1000]; // >252字节的数据
void send_large_data(void)
{
for (int i = 0; i < sizeof(large_data); i += CHUNK_SIZE) {
uint8_t chunk_len = MIN(CHUNK_SIZE, sizeof(large_data) - i);
uint8_t adv_data[2 + CHUNK_SIZE] = {
0x01, BT_DATA_SEQ_DATA, // 自定义分片标识
(i / CHUNK_SIZE) & 0xFF // 分片序号
};
memcpy(&adv_data[2], &large_data[i], chunk_len);
bt_le_per_adv_set_data(adv, adv_data, chunk_len + 2);
k_sleep(K_MSEC(50)); // 等待传输完成
}
}
3.2 动态数据更新
// 无中断更新数据
void update_data_seamlessly(void)
{
// 1. 准备新数据
uint8_t new_data[200] = {...};
// 2. 设置新数据
bt_le_per_adv_set_data(adv, new_data, sizeof(new_data));
// 注意:无需停止/重启广播,数据会自动更新
}
3.3 多广播集数据同步
struct bt_le_ext_adv *adv1, *adv2;
void sync_adv_data(void)
{
uint8_t shared_data[] = {...};
// 为多个广播集设置相同数据
bt_le_per_adv_set_data(adv1, shared_data, sizeof(shared_data));
bt_le_per_adv_set_data(adv2, shared_data, sizeof(shared_data)));
}
4 应用场景
4.1 LE Audio 广播
// 配置LE音频广播数据
int setup_le_audio_broadcast(void)
{
uint8_t audio_data[] = {
// 基础音频配置
0x06, BT_DATA_LE_AUDIO_BROADCAST,
0x01, 0x00, // 广播ID
0x02, // 音频编码: LC3
0x02, 0x01, // 采样率48kHz
// 附加信息
0x05, BT_DATA_LE_AUDIO_METADATA,
0x01, 0x02, 0x03, 0x04
};
return bt_le_per_adv_set_data(adv, audio_data, sizeof(audio_data));
}
4.2 电子货架标签系统
void update_esl_data(uint16_t product_id, uint32_t price)
{
uint8_t esl_data[] = {
0x01, 0x12, // 自定义ESL标识
(product_id >> 8) & 0xFF, product_id & 0xFF,
(price >> 24) & 0xFF, (price >> 16) & 0xFF,
(price >> 8) & 0xFF, price & 0xFF
};
bt_le_per_adv_set_data(adv, esl_data, sizeof(esl_data));
}
4.3 工业传感器网络
void broadcast_sensor_readings(float temp, float humidity, uint32_t pressure)
{
uint8_t sensor_data[15] = {
0x02, BT_DATA_MANUFACTURER_DATA, 0xCD, 0x01, // 厂商ID
};
// 小端格式打包数据
memcpy(&sensor_data[4], &temp, 4);
memcpy(&sensor_data[8], &humidity, 4);
memcpy(&sensor_data[12], &pressure, 4);
bt_le_per_adv_set_data(adv, sensor_data, sizeof(sensor_data));
}
5 性能优化技巧
5.1 数据压缩:
// 使用紧凑数据格式
uint8_t compressed_data[10] = {
0x01, 0xAB, // 数据类型标识
(int8_t)(temp * 2), // 1字节温度 (-128~127°C, 0.5°C精度)
(uint8_t)humidity, // 1字节湿度 (0-100%)
(pressure >> 16) & 0xFF, (pressure >> 8) & 0xFF, pressure & 0xFF // 3字节压力
};
5.2 增量更新:
// 仅发送变化的数据
if (fabs(temp - last_temp) > 0.1f) {
update_temp_in_adv_data(temp);
bt_le_per_adv_set_data(adv, current_data, data_len);
last_temp = temp;
}
5.3 自适应间隔:
// 根据数据变化率调整广播间隔
void adjust_broadcast_interval(float change_rate)
{
uint32_t new_interval = MAX(
BT_GAP_PER_ADV_FAST_INT_MIN,
MIN(
BT_GAP_PER_ADV_SLOW_INT_MAX,
1000 / change_rate // 基于变化率计算
)
);
struct bt_le_per_adv_param params = {
.interval_min = new_interval,
.interval_max = new_interval
};
bt_le_per_adv_set_param(adv, ¶ms);
}
6 常见问题和解决方案
1) 数据设置失败 (错误 -22/EINVAL)
检查数据长度:确保不超过 252 字节
验证数据结构:确认 AD 结构格式正确(长度值匹配后续数据)
确认广播实例状态:必须在启动周期性广播前设置数
2) 接收端无法解析数据
包含必要标识:确保数据中包含广播标志 (0x01)
添加设备名称:包含完整或简称设备名
使用标准数据类型:优先使用 SIG 定义的 AD 类型
3) 数据更新延迟
减少广播间隔:使用更短的间隔参数
优化更新流程:
// 高效更新流程 void efficient_update(uint8_t *new_data, uint8_t len) { // 无需停止广播即可更新数据 bt_le_per_adv_set_data(adv, new_data, len); // 强制刷新(某些控制器需要) bt_le_per_adv_stop(adv); k_sleep(K_MSEC(5)); bt_le_per_adv_start(adv); }