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 广播与扫描的完整流程

下面这张图展示了广播与扫描的完整交互过程:

广播与扫描交互流程 广播与扫描交互流程 广播设备 扫描设备 ADV_IND / ADV_NONCONN_IND SCAN_REQ(主动扫描) SCAN_RSP(扫描响应) CONNECT_REQ(可连接广播) 流程说明 1. 广播设备按间隔发送广播包(ADV_IND 等) 2. 扫描设备被动接收,或主动发送 SCAN_REQ 获取更多数据 3. 如果是可连接广播,扫描设备可发送 CONNECT_REQ 建立连接

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 倍,这样能保证至少收到一个完整的广播包。

我的习惯:调试阶段用连续扫描(窗口=间隔),确保不丢包。产品阶段再根据功耗要求调整扫描参数。

好了,广播与扫描的核心内容就这些。记住广播类型的选择、数据格式的编排、以及扫描模式的差异,后面调协议栈时会省很多力气。