4. 广播与扫描:广播类型、数据格式与扫描机制
广播与扫描,是 BLE 协议栈里最基础、也最容易踩坑的一环。我记得刚接触 BLE 时,总觉得广播不就是发个包嘛,有什么难的?结果第一次调设备发现手机死活搜不到,折腾了两天才明白——广播类型选错了。
这一节,咱们就把广播的四种类型、数据包的格式、以及主动/被动扫描的区别,一次性讲透。
4.1 广播类型:四种模式,各有各的用途
BLE 广播分为四种类型。说白了,就是告诉扫描者:你能不能连我?能不能扫我?
| 广播类型 | 可连接 | 可扫描 | 典型场景 |
|---|---|---|---|
| Connectable Undirected | 是 | 是 | 普通外设(如心率带、温湿度计) |
| Non-connectable Undirected | 否 | 否 | 纯广播(如信标、iBeacon) |
| Scannable Undirected | 否 | 是 | 需要扫描响应,但不建立连接 |
| Directed | 是 | 否 | 快速重连(已知目标设备) |
核心要点:广播类型在 ADV_IND、ADV_NONCONN_IND、ADV_SCAN_IND、ADV_DIRECT_IND 四个 PDU 类型中体现。芯片初始化时就要选对,否则后面改起来很麻烦。
4.1.1 Connectable Undirected(可连接无定向广播)
这是最常用的广播类型。设备每隔一个固定的间隔(比如 100ms)发一次广播包,任何扫描者都可以发起连接请求。
我在项目中做过一个智能门锁,用的就是这种模式。手机靠近门锁,扫描到广播后直接发起连接,体验很流畅。
我的习惯:广播间隔建议设在 100ms~500ms 之间。太短费电,太长连接响应慢。我一般取 200ms,兼顾功耗和响应速度。
4.1.2 Non-connectable Undirected(不可连接无定向广播)
这种广播只发数据,不接受连接。典型应用就是 iBeacon、Eddystone 这类信标设备。
你想想看,一个商场里的信标,它只需要告诉手机“我在哪”,根本不需要和手机建立连接。用 Non-connectable 模式,功耗更低,也更安全。
注意:Non-connectable 广播不能发送扫描响应(Scan Response)。如果你需要额外数据,必须全部塞进广播包本身。广播包最多 31 字节,自己算好空间。
4.1.3 Scannable Undirected(可扫描无定向广播)
这种模式有点特殊。它不可连接,但允许扫描者发送扫描请求,然后设备回复扫描响应。
我曾经在一个资产追踪项目里用过它。设备只广播位置信息,但手机可以主动请求更详细的数据(比如电池电量、设备名称),而不需要建立连接。这样既省电,又灵活。
4.1.4 Directed(定向广播)
定向广播只发给一个特定的设备。广播包中包含目标设备的地址,其他设备收到后会直接忽略。
这种模式主要用于快速重连。比如你的蓝牙耳机已经配对过手机,下次开机时用定向广播,手机能更快地找到它。
避坑指南:我曾经在定向广播里踩过一个坑——目标设备地址写错了,结果广播发出去没人理。调试了半天才发现是地址字节序搞反了。记住,BLE 地址是小端序,别搞混。
4.2 广播数据格式:AD Structure 与 AD Type
广播数据不是随便塞的。它有一套固定的格式,叫做 AD Structure(Advertising Data Structure)。
每个 AD Structure 由三部分组成:
- Length(1 字节):表示后面 AD Type + AD Data 的总长度
- AD Type(1 字节):标识数据类型(比如设备名、UUID、Tx Power 等)
- AD Data(N 字节):具体的数据内容
举个例子,一个包含设备名称的广播包,结构是这样的:
// 假设设备名称为 "BLE_DEV"
// 广播数据:0x08 0x09 'B' 'L' 'E' '_' 'D' 'E' 'V'
// 0x08 = Length(8 字节,包括 Type 和 Data)
// 0x09 = AD Type(Complete Local Name)
// 后面 7 字节 = "BLE_DEV"
常用的 AD Type 有这些:
| AD Type 值 | 含义 | 说明 |
|---|---|---|
| 0x01 | Flags | 广播模式、是否支持 LE 等 |
| 0x02/0x03 | 16-bit Service UUID | 不完全/完全列表 |
| 0x06/0x07 | 128-bit Service UUID | 不完全/完全列表 |
| 0x08/0x09 | Local Name | 短名称/完整名称 |
| 0x0A | Tx Power Level | 发射功率,用于距离估算 |
| 0xFF | Manufacturer Specific | 厂商自定义数据 |
我的经验:广播包只有 31 字节,要精打细算。我一般把 Flags 和 Service UUID 放在前面,因为扫描者最先解析这些。厂商自定义数据放最后,不够就砍。
4.3 主动扫描与被动扫描
扫描也分两种:主动扫描和被动扫描。很多初学者搞不清区别,其实很简单。
4.3.1 被动扫描
被动扫描就是扫描者只监听广播包,不发送任何请求。它只接收 ADV_IND、ADV_NONCONN_IND 等广播 PDU,不会去要扫描响应。
这种模式功耗低,但获取的信息有限。比如你只想知道周围有没有设备,不需要知道设备名字,用被动扫描就够了。
4.3.2 主动扫描
主动扫描会发送 SCAN_REQ(扫描请求),设备收到后回复 SCAN_RSP(扫描响应)。这样扫描者就能拿到更多数据,比如设备名称、额外的 Service UUID 等。
你想想看,手机 App 扫描周围设备时,通常都会显示设备名称。这个名称就是从扫描响应里拿到的。如果只用被动扫描,你只能看到广播包里的那 31 字节,很可能没有设备名。
关键区别:被动扫描只收不发,主动扫描会发 SCAN_REQ。主动扫描能获取扫描响应数据,但功耗更高,也会增加空中的碰撞概率。
4.3.3 扫描参数配置
扫描参数主要有两个:扫描窗口和扫描间隔。
- 扫描窗口:每次扫描持续的时间
- 扫描间隔:两次扫描开始之间的时间
扫描窗口必须小于等于扫描间隔。如果窗口等于间隔,就是连续扫描;如果窗口小于间隔,就是间歇扫描。
我建议:
- 需要快速发现设备时,用连续扫描(窗口=间隔)
- 需要省电时,用间歇扫描(窗口=间隔的 1/4 或 1/2)
注意:扫描窗口设得太小,可能会漏掉广播包。我曾经在一个项目中把扫描窗口设为 10ms,结果广播间隔 100ms 的设备经常搜不到。后来把窗口调到 50ms,问题就解决了。
4.4 广播与扫描的完整流程
下面这张图展示了广播与扫描的完整交互过程:
4.5 实战中的常见问题
最后,分享几个我在项目中遇到的实际问题,希望能帮你少走弯路。
4.5.1 广播包超长
广播包最多 31 字节,扫描响应也是 31 字节。如果你需要发送的数据超过 62 字节,就得用 Extended Advertising(BLE 5.0 以上支持)。
我曾经在一个传感器项目里,想把温度、湿度、气压、光照四个数据都塞进广播包,结果发现 31 字节根本不够。后来只好把部分数据放到扫描响应里,才勉强够用。
4.5.2 广播间隔与功耗
广播间隔越短,设备被发现得越快,但功耗也越高。我一般这样权衡:
- 需要快速连接:广播间隔 20ms~50ms(比如蓝牙遥控器)
- 普通场景:广播间隔 100ms~500ms(比如智能家居传感器)
- 低功耗场景:广播间隔 1s~5s(比如信标设备)
4.5.3 扫描窗口与丢包
扫描窗口太小,容易漏包。我建议扫描窗口至少设为广播间隔的 1.5 倍,这样能保证至少收到一个完整的广播包。
我的习惯:调试阶段用连续扫描(窗口=间隔),确保不丢包。产品阶段再根据功耗要求调整扫描参数。
好了,广播与扫描的核心内容就这些。记住广播类型的选择、数据格式的编排、以及扫描模式的差异,后面调协议栈时会省很多力气。