先说结论
蓝牙BLE广播包就31字节,塞什么、怎么塞,直接决定你的设备能不能被搜到、搜到后用户愿不愿意连。核心原则:名称要让人认出来、UUID要匹配手机App的预期、厂商数据要实用别浪费字节。 参数调错了最常见的结果就两种——要么手机根本搜不到,要么搜到了连不上。看完这篇,你自己能配出完整的广播包。
你的设备为什么搜不到?先搞懂广播包是什么
蓝牙BLE设备工作时分两个阶段:广播阶段(我在这里,谁来连我)和连接阶段(咱俩建立私密通道)。广播阶段设备不停往外发"自我介绍"包,手机蓝牙就在这个阶段扫到你。
广播包格式:长度(1字节) + 类型(1字节) + 数据(N字节)
每段数据前面的"长度"字段,指的是从类型字节到最后一个数据字节的总字节数,不是数据本身的字节数。新手最容易在这里搞混,写成实际数据长度,结果手机解析出错。
一个能用的最小广播包:
02 01 06 → 长度2,类型0x01(flags),值0x06
08 09 54 58 38 38 30 30 31 → 长度8,类型0x09(完整名称),数据"TX88001"
03 03 18 0D → 长度3,类型0x03(16bit UUID列表),数据0x180D(心率服务,可替换)
广播包里该填什么?
1. Flags(必须有,不填系统会报错)
Flags告诉手机你的设备是什么类型BLE设备,这个字段系统层要用,不填的话Android和iOS行为不一致。
// Flags字段(必填)
// bit1=1:LE General Discoverable Mode(可被发现)
// bit2=0:BR/EDR Not Supported(不支持经典蓝牙,纯BLE)
// 0x06 = 二进制 00000110,对应上面两个bit
uint8_t adv_flags[] = { 0x02, 0x01, 0x06 };
2. 设备名称(用户第一眼看到的就是它)
名称是用户判断"搜到的是不是我的设备"的唯一依据。蓝牙SIG标准里,类型0x09是完整名称,0x08是缩短名称(用于空间不够时替代)。
// 完整名称(TX88001,出厂默认,可定制)
uint8_t device_name[] = {
0x08, // 长度8字节
0x09, // 类型:Complete Local Name
'T', 'X', '8', '8', '0', '0', '1'
};
// 如果空间不够用缩短名称(只显示"TX8800",手机App仍能识别)
uint8_t short_name[] = {
0x06, // 长度6字节
0x08, // 类型:Shortened Local Name
'T', 'X', '8', '8', '0', '0'
};
实战经验:
- 名称控制在10个字符以内,超长手机端会截断
- 名称里带数字编号,方便多设备管理(比如KEY-01、KEY-02)
- 颖特新出货的蓝牙模块,出厂名称默认TX88001,支持出厂烧录自定义名称,量大免开模费
3. 服务UUID(决定App能不能认出你是哪种设备)
UUID是设备的"身份类型",手机App扫描时就是靠这个过滤设备的。比如心率带广播里带了0x180D心率服务UUID,手机上装的心率App扫到就认出来。
常用标准16bit UUID:
| UUID | 服务类型 | 常见应用 |
|---|---|---|
| 0x180D | 心率服务 | 心率带、心率手环 |
| 0x180F | 电池服务 | 所有电池供电设备 |
| 0x1816 | 体温服务 | 额温枪、体温贴 |
| 0x181A | 环境感知服务 | 温湿度传感器 |
| 0x181C | 人体存在检测 | PIR、人体感应 |
| 0x1810 | 血压服务 | 血压计 |
| 0x180A | 设备信息 | 所有设备可选 |
4. 外观类型(决定手机显示什么图标)
外观类型告诉手机你的设备外观分类,系统会根据这个显示对应图标。
| 外观值 | 设备类型 |
|---|---|
| 0x0080 | Generic Tag(通用标签/防丢器) |
| 0x00C0 | Generic Wearable(可穿戴设备) |
| 0x0440 | Heart Rate Sensor(心率传感器) |
| 0x03D0 | Thermometer(温度计) |
| 0x0300 | Generic Phone(手机) |
5. 厂商自定义数据(最有价值的部分)
类型0xFF是厂商自定义数据段,你可以往里填任何业务相关的信息——电池电量、固件版本、设备状态。手机App扫描时不需要建立连接就能读到,这是BLE广播最有价值的能力之一。
// 厂商自定义数据示例
// 字节结构:[长度][0xFF][厂商ID高字节][厂商ID低字节][业务数据1][业务数据2]
uint8_t mfg_data[] = {
0x05, // 总长度5字节(类型1字节 + 数据4字节)
0xFF, // 厂商自定义数据标识
0x59, 0x54, // 厂商ID:0x5954(可自定义,测试用0xFFFF)
0x64, // 电池电量:100%
0x01 // 设备状态:1=正常,0=异常
};
实际应用场景举例:
- 智能门锁:电量+门锁状态(已锁/已开),用户扫一下就知道要不要换电池
- 蓝牙Beacon:货架标签广播商品ID和价格,手机扫码直接出价格
- 防丢器:电量+信号强度+最后位置,手机App不用连上就能预览
广播参数:间隔、功率、频道怎么配?
数据格式对了只是第一步,物理参数不对照样搜不到或功耗爆炸。
| 参数 | 可调范围 | 实战建议 |
|---|---|---|
| 发射功率 | -40dBm ~ +10dBm | 室内设备一般-8~0dBm够用,调高不省钱反而费电 |
| 广播间隔 | 20ms ~ 10s | 防丢器/Beacon用100-200ms,普通设备500ms-1s |
| 广播频道 | Ch37/Ch38/Ch39 | 三个全开,别省 |
实测功耗参考(nRF52832):
| 广播间隔 | 平均广播电流 | CR2032续航估算 |
|---|---|---|
| 100ms | ~15μA | 6-12个月 |
| 500ms | ~3μA | 1.5-2年 |
| 1s | ~1.5μA | 2-3年 |
| 2s | ~0.8μA | 3年+ |
扫描响应包:多给你31字节
广播包只有31字节,不够用怎么办?手机主动扫描时会发一个scan request,设备可以回复scan response,这是额外的31字节。
// 扫描响应包里放什么?
// 1. 完整设备名称(广播包里用的是缩短名)
// 2. 设备序列号
// 3. OTA升级标识
// 4. 连接密码/Token
static uint8_t scan_rsp_data[] = {
0x0B, // 长度11
0x09, // 类型:Complete Local Name
'T', 'X', '8', '8', '0', '0', '1', '-', 'A', 'B', // 完整名称
};
完整配置代码(Nordic nRF52832)
#include "ble.h"
#include "nrf_sdh_ble.h"
#define APP_BLE_OBSERVER_PRIO 3
#define APP_BLE_CONN_CFG_TAG 1
// 完整广播数据
static uint8_t m_adv_data[] = {
0x02, 0x01, 0x06, // Flags: BLE通用发现
0x08, 0x09, 'T', 'X', '8', '8', '0', // 设备名TX88001(前半段)
0x00, 0x03, 0x03, 0x0D, 0x18, // 心率服务UUID 0x180D
0x05, 0xFF, 0x59, 0x54, 0x64, 0x01 // 厂商ID 0x5954 + 电量100% + 状态1
};
// 扫描响应数据
static uint8_t m_scan_rsp_data[] = {
0x0C, 0x09, 'T', 'X', '8', '8', '0', // 完整设备名
0x30, 0x30, 0x31, 0x2D, 0x50, 0x52, // TX88001-PR(带型号后缀)
};
// 广播参数
static ble_gap_adv_params_t m_adv_params = {
.type = BLE_GAP_ADV_TYPE_ADV_IND, // 可连接广播
.p_peer_addr = NULL, // 广播给所有人
.fp = BLE_GAP_ADV_FP_ANY, // 允许任何人扫描
.interval = 320, // 间隔=320×0.625ms=200ms
.timeout = 0, // 0=持续广播
};
// 启动广播
void advertising_init(void) {
ble_gap_adv_data_t adv_data = {
.adv_data.p_data = m_adv_data,
.adv_data.size = sizeof(m_adv_data),
.scan_rsp_data.p_data = m_scan_rsp_data,
.scan_rsp_data.size = sizeof(m_scan_rsp_data),
};
sd_ble_gap_adv_set_configure(&m_adv_params, &adv_data);
sd_ble_gap_adv_start(&m_adv_params, APP_BLE_CONN_CFG_TAG);
}
常见问题Q&A
Q1:广播包31字节怎么分配最合理?
我的经验分配方式:Flags 3字节 + 名称 8-12字节 + 服务UUID 3-6字节 + 厂商数据 5-6字节 = 19-27字节,留4-6字节给扩展。这样扫描响应包里就不用再塞名称了,全放完整信息和自定义数据。
Q2:广播间隔设了100ms,功耗好像比预期高很多?
确认一下发射功率。100ms间隔 + 0dBm功率平均电流约15μA,但如果功率设成+4dBm,平均电流会到25-30μA,续航直接打7折。先量一下实际发射电流,再确认参数。
Q3:手机搜到了但是连不上,是什么问题?
广播包本身没问题的情况下,连不上一般两个原因:①设备在广播包里声明的是不可连接广播类型(ADV_NONCONN_IND);②设备已经在和其他主机连接了,BLE从机同一时间只能连一个主机。检查广播类型参数。
Q4:厂商ID怎么选?
蓝牙SIG分配的正式ID可以免费申请(bluetooth.com/assigned-numbers/company-identifiers)。内部测试用0xFFFF,正式产品建议申请正式ID或者用固定自定义值(比如0x5954)。选ID的时候注意别和知名大厂冲突。
Q5:多设备同时广播会互相干扰吗?
BLE三个频道都是随机跳频,设备多了信道冲突是概率问题,10个以内设备基本感知不到。如果做50+设备mesh场景,用随机延迟广播能显著改善。颖特新部分模块内置广播随机跳频策略,批量部署时更稳定。
选型速查表
| 产品类型 | 推荐广播配置 | 典型模块型号 | 参考价格 |
|---|---|---|---|
| 防丢器 | 100ms间隔,厂商数据带电量 | TX88001/TX89001 | 8-15元 |
| 温湿度传感器 | 1s间隔,短名称+UUID | TX88032 | 18-25元 |
| 门锁 | 500ms间隔,厂商数据带状态 | TX88040 | 25-35元 |
| Beacon信标 | 100ms间隔,厂商数据带ID | TX88010 | 12-20元 |