【从零开始】手写BLE协议栈(2-1)主动扫描(Active Scanning)

主动扫描:ADV_IND、SCAN_REQ 与 SCAN_RSP 的三包握手

本章示例代码:https://github.com/ixbwer/write-BLE-stack-from-scratch/tree/master/02_active_scan

前提知识

阅读本文需要具备以下基础:

  • 理解 BLE 广播帧结构(1-2 篇有详细讲解):1-2 篇中的广播者使用 ADV_NONCONN_IND(不可扫描),本文升级为 ADV_IND(可扫描)。你需要理解 PDU 格式——Header、AdvA 和 AdvData——以及广播使用哪三个信道(37/38/39)。
  • 理解 RADIO 状态机(1-1 有详细讲解):主动扫描涉及 RX→TX→RX 状态切换——先接收 ADV_IND,再发送 SCAN_REQ,再接收 SCAN_RSP,不理解状态机就看不懂这个切换过程。

本文只关注广播→扫描阶段的三包交互和两包之间有 150 µs 约束",不深入分析其成因,成因在 2-2 篇展开。


一、广播不是独白,是开放式提问

在 1-2 篇里,我们让 nRF52 每隔 100 ms 在信道 37/38/39 上发送一次 ADV_NONCONN_IND 包——一种不可连接、不可扫描的广播。它的作用就是一张名片:把设备名广而告之,不关心谁在听。但 ADV_NONCONN_IND 只能"贴名片",不允许扫描器反问。本章的广播者将升级为 ADV_IND(可连接可扫描广播),这样扫描器才有资格发出 SCAN_REQ。

但名片的正面空间有限——ADV_IND 的 AdvData 字段最多只有 31 字节。设想一台蓝牙温湿度传感器,它要在名片里放设备名(8 字节)、公司标识(4 字节)、电池电量和传感器数值(10 字节),31 字节很快就用完了。如果扫描方想知道这台传感器的固件版本?装不下。

BLE 规范给扫描器提供了一种"翻名片背面"的机制:主动扫描(Active Scanning)。流程是这样的——

扫描器收到 ADV_IND 后,向广播者发送一个 SCAN_REQ 包,意思是"我注意到你了,请把你的附加信息也给我"。广播者收到后,用 SCAN_RSP 作为回应,同样携带最多 31 字节的额外数据(ScanRspData)。两包加在一起,扫描器最多可获得 62 字节的设备信息。

这就是 ADV_IND → SCAN_REQ → SCAN_RSP 三包握手的全貌。

与之对应的,被动扫描(Passive Scanning) 是扫描器只收听 ADV_IND,对感兴趣的设备静默记录——什么也不回发。它的优点是扫描器自身对周围完全"无感知",不会暴露任何自身信息,在功耗敏感或隐私敏感的场景里常用。

image


二、角色分工:广播者(Advertiser)扫描器(Scanner)

三包握手有两个设备参与,但角色不对称:

广播者(Advertiser) 是发出 ADV_IND 的一方。它在 ADV_IND 的 Header 中把 PDU 类型设为 ADV_IND(而不是 ADV_NONCONN_IND),就等于贴出一块告示牌:"来者可问"。

BLE Core Spec(Vol 6, Part B, Section 2.3.1)把广播 PDU 类型分为四种,其中只有两种接受 SCAN_REQ:

PDU 类型 类型值 可连接 可扫描(接受 SCAN_REQ) 定向
ADV_IND 0b0000
ADV_DIRECT_IND 0b0001
ADV_NONCONN_IND 0b0010
ADV_SCAN_IND 0b0110

扫描器(Scanner) 是监听广播信道、接收 ADV_IND 的一方。扫描器收到广播包之后,第一件事是检查 PDU 类型。如果类型是 ADV_INDADV_SCAN_IND,说明对方允许被扫描,可以发出 SCAN_REQ。如果是 ADV_NONCONN_INDADV_DIRECT_IND(定向广播,针对特定设备),扫描器就算发出 SCAN_REQ 也不会收到回应——规范明确禁止广播者在这两种类型下回复 SCAN_RSP。

信道 在三包交互中保持不变。ADV_IND、SCAN_REQ 和 SCAN_RSP 都在同一个广播信道(37、38 或 39 之一)上完成,不切换信道。这是 BLE 规范的硬性要求(Core Spec Vol 6, Part B, Section 4.4.2)——广播者在哪个信道被"问"到,就在哪个信道作答。
image

三、SCAN_REQ:扫描器的一张请帖

SCAN_REQ 是扫描器发向广播者的一个纯粹的"请求包"——它不携带任何数据,只是告诉广播者两件事:"我是谁(ScanA)""我想找你(AdvA)"

BLE Core Spec Vol 6, Part B, Section 2.3.2.1 规定了 SCAN_REQ 的 PDU 格式:
image

ScanA 是扫描器自身的蓝牙地址,广播者收到后可以用它来决定"要不要回应这台设备"(比如隐私保护场景下,广播者可能只回应白名单里的地址)。

AdvA 是从刚才收到的 ADV_IND 包中提取的广播者地址。广播者收到 SCAN_REQ 后,先核对 AdvA 是否等于自己的地址——如果不匹配,说明这条 SCAN_REQ 是发往别人的,广播者静默忽略,不回应。

SCAN_REQ 没有 AdvData、没有负载,格式固定,长度永远是 12 字节(只有链路层 PDU 头 + ScanA + AdvA)。它的意义就是一张请帖:拿着广播者的名字去敲门,同时亮出自己的身份。


四、SCAN_RSP:广播者的名片背面

广播者在收到合法的 SCAN_REQ(AdvA 匹配,且本设备当前允许被扫描)后,发出 SCAN_RSP 作为回应。SCAN_RSP 的格式(Core Spec Vol 6, Part B, Section 2.3.2.2)如下:

image

ScanRspData 的格式与 ADV_IND 中的 AdvData 完全相同——由一系列 AD Structure 串联构成:

image

示例:完整设备名"ThermoPro"
  09 09 54 68 65 72 6D 6F 50 72 6F
  │  │  └─ "T""h""e""r""m""o""P""r""o"
  │  └─ Type = 0x09(完整设备名)
  └─ Length = 9(Type + 8字节名称)

广播者通常把高频查询的信息(设备名、服务 UUID)放进 ADV_IND,把低频引用的信息(固件版本、制造商私有数据、配置序列号)放进 SCAN_RSP。这样既不占用固定广播包的宝贵空间,又允许真正"有需要的"扫描器按需索取。

两包合计,扫描器最多能拿到 62 字节的设备信息(ADV_IND 的 31 + SCAN_RSP 的 31)。


五、三包交互的精确时序

三包握手并不是"各方自己决定什么时候发"——每两包之间都有严格的时序约束:T_IFS(Inter-Frame Space,帧间间隔)= 150 µs ±2 µs

这个约束是双向的,谁是回包方、谁就负责这 150 µs 的计时

image

两个关键约束点:

  1. ADV_IND → SCAN_REQ:扫描器在收到 ADV_IND 的最后一个 bit 后,必须恰好在 150 µs 内让 SCAN_REQ 的第一个 bit 飞出天线。如果扫描器"慢了",广播者的 RX 接收窗口会超时关闭,这次扫描完全失败。

  2. SCAN_REQ → SCAN_RSP:广播者在收到 SCAN_REQ 的最后一个 bit 后,再用恰好 150 µs 把 SCAN_RSP 的第一个 bit 发出去。如果广播者"慢了",等待中的扫描器会超时放弃,SCAN_RSP 永远无法被接收。

image

注意"恰好"这两个字——T_IFS 不是"不超过 150 µs",而是精确等于 150 µs ±2 µs。2 µs 的容忍范围非常窄,用 CPU 软件计时根本无法满足,必须依赖硬件机制。这个问题的完整分析在 2-2 篇,nRF52 的硬件实现方案在 2-3 篇。

本系列教程同款硬件:👇
芯片: nRF 52832 开发板
工具: nRF 52840 BLE Dongle 蓝牙嗅探器
工具: 逻辑分析仪
工具: BPA low energy 蓝牙分析仪

posted @ 2026-03-28 10:04  ixbwer  阅读(6)  评论(0)    收藏  举报