文章目录
- [BLE 协议栈深入:从物理层到应用层的系统性拆解](#BLE 协议栈深入:从物理层到应用层的系统性拆解)
-
- [一、为什么 BLE 协议栈要分层](#一、为什么 BLE 协议栈要分层)
- [二、BLE 协议栈整体架构](#二、BLE 协议栈整体架构)
-
- [2.1 两大架构实现方案](#2.1 两大架构实现方案)
- [三、Controller 层详解](#三、Controller 层详解)
-
- [3.1 PHY 物理层](#3.1 PHY 物理层)
- [3.2 Link Layer 链路层------BLE 的核心引擎](#3.2 Link Layer 链路层——BLE 的核心引擎)
-
- [3.2.1 五种核心状态及转换](#3.2.1 五种核心状态及转换)
- [3.2.2 Link Layer 数据包结构](#3.2.2 Link Layer 数据包结构)
- [3.2.3 Link Layer 的关键职责清单](#3.2.3 Link Layer 的关键职责清单)
- [四、HCI 主机控制器接口](#四、HCI 主机控制器接口)
-
- [4.1 HCI 的三种数据包类型](#4.1 HCI 的三种数据包类型)
- [4.2 常见 HCI LE 命令示例](#4.2 常见 HCI LE 命令示例)
- [4.3 HCI 的物理传输方式](#4.3 HCI 的物理传输方式)
- [4.4 HCI 的调试价值](#4.4 HCI 的调试价值)
- [五、Host 层详解](#五、Host 层详解)
-
- [5.1 L2CAP --- 逻辑链路控制与适配协议](#5.1 L2CAP — 逻辑链路控制与适配协议)
- [5.2 ATT --- 属性协议(GATT 的底层传输)](#5.2 ATT — 属性协议(GATT 的底层传输))
-
- [5.2.1 属性(Attribute)的定义](#5.2.1 属性(Attribute)的定义)
- [5.2.2 六种 ATT 操作原语](#5.2.2 六种 ATT 操作原语)
- [5.3 SMP --- 安全管理器协议](#5.3 SMP — 安全管理器协议)
- [5.4 GAP --- 通用访问配置文件](#5.4 GAP — 通用访问配置文件)
- [5.5 GATT --- 通用属性配置文件](#5.5 GATT — 通用属性配置文件)
- 六、与经典蓝牙协议栈的结构对比
- 七、数据流实战:一个温度值是怎么发出去的
-
- [7.1 发送端:逐层封包](#7.1 发送端:逐层封包)
- [7.2 接收端:逐层解包](#7.2 接收端:逐层解包)
- 八、阶段检查点
- 九、动手实验
-
- [实验 1:用 nRF Connect 抓取并解析广播包](#实验 1:用 nRF Connect 抓取并解析广播包)
- [实验 2:观察 HCI 日志](#实验 2:观察 HCI 日志)
-
- [方案 A:Android HCI Snoop Log(无需额外硬件)](#方案 A:Android HCI Snoop Log(无需额外硬件))
- [方案 B:Wireshark + nRF52840 Dongle 空口抓包(推荐进阶)](#方案 B:Wireshark + nRF52840 Dongle 空口抓包(推荐进阶))
- [实验 3:对比 BLE 和经典蓝牙的 L2CAP](#实验 3:对比 BLE 和经典蓝牙的 L2CAP)
- 十、推荐学习资源
BLE 协议栈深入:从物理层到应用层的系统性拆解
一、为什么 BLE 协议栈要分层
在深入每一层之前,先回答一个根本问题:协议栈为什么要分层?
蓝牙协议栈本质上是一套"信封嵌套系统"------你的应用数据(比如一个温度值 0x1A)从应用层出发,每经过一层就被包裹一层帧头/帧尾,最终变成一个满足 BLE 空中接口规范的数据包发射出去。接收端则逐层剥离,还原出原始数据。
这种分层设计带来三个核心好处:
| 好处 | 说明 |
|---|---|
| 关注点分离 | 每层只负责自己的事,PHY 管射频、LL 管时序、GATT 管数据组织 |
| 可替换性 | Controller 和 Host 可以来自不同厂商,只要 HCI 接口一致就能协同工作 |
| 调试友好 | 出问题时逐层排查,HCI 日志是 Host/Controller 的天然分界线 |
与 TCP/IP 协议栈类比:BLE 的 PHY+LL 对应物理层+数据链路层,L2CAP 对应传输层,ATT/GATT 对应应用层协议。但 BLE 多了一个关键差异------安全(SMP)内嵌在协议栈中,而 TCP/IP 的安全(TLS)是后来叠加的。
二、BLE 协议栈整体架构
BLE 协议栈分为三大模块:Controller(控制器) 、HCI(主机控制器接口) 、Host(主机)。
#mermaid-svg-XH2XyIl7volPto1N{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-XH2XyIl7volPto1N .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-XH2XyIl7volPto1N .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-XH2XyIl7volPto1N .error-icon{fill:#552222;}#mermaid-svg-XH2XyIl7volPto1N .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-XH2XyIl7volPto1N .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-XH2XyIl7volPto1N .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-XH2XyIl7volPto1N .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-XH2XyIl7volPto1N .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-XH2XyIl7volPto1N .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-XH2XyIl7volPto1N .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-XH2XyIl7volPto1N .marker{fill:#333333;stroke:#333333;}#mermaid-svg-XH2XyIl7volPto1N .marker.cross{stroke:#333333;}#mermaid-svg-XH2XyIl7volPto1N svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-XH2XyIl7volPto1N p{margin:0;}#mermaid-svg-XH2XyIl7volPto1N .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-XH2XyIl7volPto1N .cluster-label text{fill:#333;}#mermaid-svg-XH2XyIl7volPto1N .cluster-label span{color:#333;}#mermaid-svg-XH2XyIl7volPto1N .cluster-label span p{background-color:transparent;}#mermaid-svg-XH2XyIl7volPto1N .label text,#mermaid-svg-XH2XyIl7volPto1N span{fill:#333;color:#333;}#mermaid-svg-XH2XyIl7volPto1N .node rect,#mermaid-svg-XH2XyIl7volPto1N .node circle,#mermaid-svg-XH2XyIl7volPto1N .node ellipse,#mermaid-svg-XH2XyIl7volPto1N .node polygon,#mermaid-svg-XH2XyIl7volPto1N .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-XH2XyIl7volPto1N .rough-node .label text,#mermaid-svg-XH2XyIl7volPto1N .node .label text,#mermaid-svg-XH2XyIl7volPto1N .image-shape .label,#mermaid-svg-XH2XyIl7volPto1N .icon-shape .label{text-anchor:middle;}#mermaid-svg-XH2XyIl7volPto1N .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-XH2XyIl7volPto1N .rough-node .label,#mermaid-svg-XH2XyIl7volPto1N .node .label,#mermaid-svg-XH2XyIl7volPto1N .image-shape .label,#mermaid-svg-XH2XyIl7volPto1N .icon-shape .label{text-align:center;}#mermaid-svg-XH2XyIl7volPto1N .node.clickable{cursor:pointer;}#mermaid-svg-XH2XyIl7volPto1N .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-XH2XyIl7volPto1N .arrowheadPath{fill:#333333;}#mermaid-svg-XH2XyIl7volPto1N .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-XH2XyIl7volPto1N .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-XH2XyIl7volPto1N .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XH2XyIl7volPto1N .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-XH2XyIl7volPto1N .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XH2XyIl7volPto1N .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-XH2XyIl7volPto1N .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-XH2XyIl7volPto1N .cluster text{fill:#333;}#mermaid-svg-XH2XyIl7volPto1N .cluster span{color:#333;}#mermaid-svg-XH2XyIl7volPto1N div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-XH2XyIl7volPto1N .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-XH2XyIl7volPto1N rect.text{fill:none;stroke-width:0;}#mermaid-svg-XH2XyIl7volPto1N .icon-shape,#mermaid-svg-XH2XyIl7volPto1N .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XH2XyIl7volPto1N .icon-shape p,#mermaid-svg-XH2XyIl7volPto1N .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-XH2XyIl7volPto1N .icon-shape rect,#mermaid-svg-XH2XyIl7volPto1N .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XH2XyIl7volPto1N .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-XH2XyIl7volPto1N .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-XH2XyIl7volPto1N :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Controller 层(控制器层)
接口层
Host 层(主机层)
应用层 Application
控制
安全
用户应用代码
GAP
通用访问配置文件
广播/扫描/连接管理
GATT
通用属性配置文件
服务/特征/描述符
SMP
安全管理器协议
配对/加密/密钥
ATT
属性协议
读/写/通知/指示
L2CAP
逻辑链路控制与适配协议
信道复用/分段重组
HCI
Host Controller Interface
标准化命令/事件/数据接口
Link Layer 链路层
状态机/连接管理/加密
Baseband 基带
时隙/跳频/时序
PHY 物理层
射频收发/GFSK调制
2.1 两大架构实现方案
BLE 协议栈在硬件上有两种部署方式,理解这一点对后续开发选型至关重要:
| 方案 | 架构 | 特点 | 典型芯片 |
|---|---|---|---|
| 单芯片(SoC) | Host + Controller 集成在同一 MCU | 体积小、功耗最低、成本最低 | Nordic nRF52840、TI CC2640R2F |
| 双芯片(NCP) | Controller 独立芯片 + 外部 Host MCU | 灵活度高,Host 可跑 Linux/Android | CSR8510 + Linux BlueZ |
在单芯片方案中,HCI 层退化为内部函数调用和消息队列,不经过物理总线。在双芯片方案中,HCI 通过 UART/USB/SPI 传输,命令和数据格式严格遵循 Bluetooth Core Specification 定义。
三、Controller 层详解
Controller 是 BLE 协议栈的"硬件核心",直接管理射频链路和底层连接控制。它包含两个子层:**PHY(物理层)**和 Link Layer(链路层)。
3.1 PHY 物理层
| 学习要点 | 详情 | 与经典蓝牙对比 |
|---|---|---|
| 调制方式 | GFSK(高斯频移键控),与 BR 相同 | 经典蓝牙有 EDR 的 π/4-DQPSK / 8DPSK |
| 频偏容限 | ±150 KHz(比 BR 的 ±75 KHz 宽松一倍) | 允许使用低成本晶体振荡器 |
| 发射功率 | -20 ~ +20 dBm(5.0 后放宽) | 经典蓝牙 Class 1 为 +20 dBm |
| 接收灵敏度 | 规范要求 > -70 dBm,实际芯片 -90 ~ -100 dBm | 取决于芯片设计 |
为什么 BLE 不用 EDR 高阶调制? 这是一个有意的设计取舍。π/4-DQPSK 和 8DPSK 虽然能提高速率(2/3 Mbps),但对射频前端的线性度要求更高,直接推高功耗和芯片成本。BLE 的设计目标是"纽扣电池供电数年",因此放弃了速率换取功耗和成本优势。
3.2 Link Layer 链路层------BLE 的核心引擎
Link Layer 是整个 BLE 协议栈最重要、最复杂的一层。 它负责管理射频状态、数据包收发、连接时序、加密等几乎所有底层操作。Nordic 的 BLE 协议栈能同时支持 20 路连接,核心能力就在 LL 层。
3.2.1 五种核心状态及转换
#mermaid-svg-24qHF0vCRPJ3pQrE{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-24qHF0vCRPJ3pQrE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-24qHF0vCRPJ3pQrE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-24qHF0vCRPJ3pQrE .error-icon{fill:#552222;}#mermaid-svg-24qHF0vCRPJ3pQrE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-24qHF0vCRPJ3pQrE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-24qHF0vCRPJ3pQrE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-24qHF0vCRPJ3pQrE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-24qHF0vCRPJ3pQrE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-24qHF0vCRPJ3pQrE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-24qHF0vCRPJ3pQrE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-24qHF0vCRPJ3pQrE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-24qHF0vCRPJ3pQrE .marker.cross{stroke:#333333;}#mermaid-svg-24qHF0vCRPJ3pQrE svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-24qHF0vCRPJ3pQrE p{margin:0;}#mermaid-svg-24qHF0vCRPJ3pQrE defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-24qHF0vCRPJ3pQrE g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-24qHF0vCRPJ3pQrE g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-24qHF0vCRPJ3pQrE g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-24qHF0vCRPJ3pQrE g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-24qHF0vCRPJ3pQrE g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-24qHF0vCRPJ3pQrE .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-24qHF0vCRPJ3pQrE .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-24qHF0vCRPJ3pQrE .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-24qHF0vCRPJ3pQrE .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-24qHF0vCRPJ3pQrE .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-24qHF0vCRPJ3pQrE .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-24qHF0vCRPJ3pQrE .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-24qHF0vCRPJ3pQrE .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-24qHF0vCRPJ3pQrE .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-24qHF0vCRPJ3pQrE .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-24qHF0vCRPJ3pQrE .edgeLabel .label text{fill:#333;}#mermaid-svg-24qHF0vCRPJ3pQrE .label div .edgeLabel{color:#333;}#mermaid-svg-24qHF0vCRPJ3pQrE .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-24qHF0vCRPJ3pQrE .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-24qHF0vCRPJ3pQrE .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-24qHF0vCRPJ3pQrE .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-24qHF0vCRPJ3pQrE .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-24qHF0vCRPJ3pQrE .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-24qHF0vCRPJ3pQrE .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-24qHF0vCRPJ3pQrE #statediagram-barbEnd{fill:#333333;}#mermaid-svg-24qHF0vCRPJ3pQrE .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-24qHF0vCRPJ3pQrE .cluster-label,#mermaid-svg-24qHF0vCRPJ3pQrE .nodeLabel{color:#131300;}#mermaid-svg-24qHF0vCRPJ3pQrE .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-24qHF0vCRPJ3pQrE .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-24qHF0vCRPJ3pQrE .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-24qHF0vCRPJ3pQrE .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-24qHF0vCRPJ3pQrE .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-24qHF0vCRPJ3pQrE .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-24qHF0vCRPJ3pQrE .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-24qHF0vCRPJ3pQrE .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-24qHF0vCRPJ3pQrE .note-edge{stroke-dasharray:5;}#mermaid-svg-24qHF0vCRPJ3pQrE .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-24qHF0vCRPJ3pQrE .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-24qHF0vCRPJ3pQrE .statediagram-note text{fill:black;}#mermaid-svg-24qHF0vCRPJ3pQrE .statediagram-note .nodeLabel{color:black;}#mermaid-svg-24qHF0vCRPJ3pQrE .statediagram .edgeLabel{color:red;}#mermaid-svg-24qHF0vCRPJ3pQrE #dependencyStart,#mermaid-svg-24qHF0vCRPJ3pQrE #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-24qHF0vCRPJ3pQrE .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-24qHF0vCRPJ3pQrE :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Standby Connected 上电初始化
Host 命令启动广播
Host 命令启动扫描
Host 命令发起连接
收到 CONNECT_IND
广播超时 / Host 命令停止
发现目标设备
扫描超时 / Host 命令停止
连接建立成功
连接超时 / Host 命令取消
连接断开
Advertising 发送广播包后
等待下一个广播事件
SendADV
ListenResponse
Scanning 主动扫描模式
收到 Scan Response
ListenADV
SendScanReq
Initiating
Idle
不发送也不接收\n功耗最低(μA级)
Master_or_Slave
按 Connection Interval\n周期性收发数据
各状态详解:
| 状态 | 核心动作 | 功耗特征 | 进入条件 | 退出条件 |
|---|---|---|---|---|
| Standby | 空闲等待 | 最低(μA 级) | 上电默认态 / 任何状态完成操作后 | Host 命令触发 |
| Advertising | 周期性在 37/38/39 信道发送广播包 | 取决于广播间隔(20ms ~ 10.24s) | Host 命令启动 | 收到 CONNECT_IND → Connected;超时 → Standby |
| Scanning | 周期性监听广播信道 | 取决于扫描窗口/间隔比 | Host 命令启动 | 发现目标 → Initiating;超时 → Standby |
| Initiating | 向目标设备发送 CONNECT_IND | 短时高功耗 | 扫描发现目标 | 连接成功 → Connected;超时 → Standby |
| Connected | 按 Connection Interval 在数据信道收发 | 取决于连接间隔和负载 | Advertising 或 Initiating 完成连接 | 连接断开 → Standby |
动手练习:不看任何参考资料,徒手画出五种状态及全部转换路径,标注每个转换的触发事件。这是面试和排障的高频考点。
3.2.2 Link Layer 数据包结构
BLE 的空中数据包(Air Packet)由 LL 层构建,结构如下:
┌──────────┬────────────────┬─────────────────────────────┬──────────┐
│ Preamble │ Access Address │ Protocol Data Unit │ CRC │
│ (1 byte) │ (4 bytes) │ │ (3 bytes)│
│ │ │ ┌────────┬────────────────┐ │ │
│ │ │ │ Header │ Payload │ │ │
│ │ │ │(2 byte)│ (0-255 byte) │ │ │
│ │ │ └────────┴────────────────┘ │ │
└──────────┴────────────────┴─────────────────────────────┴──────────┘
| 字段 | 长度 | 作用 |
|---|---|---|
| Preamble | 1 byte | 前导码(0xAA 或 0x55),用于接收端时钟同步 |
| Access Address | 4 bytes | 广播包固定 0x8E89BED6;数据包为随机生成的连接标识 |
| LL Header | 2 bytes | 包含 PDU Type(数据包类型)、Length(载荷长度)等控制信息 |
| Payload | 0--255 bytes | 实际数据或控制指令 |
| CRC | 3 bytes | 24 位循环冗余校验,保证数据完整性 |
关键理解 :广播包的 Access Address 是固定值 0x8E89BED6,意味着"发给所有人"。连接建立后,双方会协商一个随机的 Access Address,只属于这对连接------这就是 BLE 实现"一对一私密通信"的基础。
3.2.3 Link Layer 的关键职责清单
| 职责 | 说明 |
|---|---|
| 信道选择 | 广播用 37/38/39 固定信道;连接后用自适应跳频(AFH)在 37 个数据信道间切换 |
| 数据包识别 | 通过 Access Address 判断数据包是否属于自己 |
| 时序管理 | 控制广播间隔、扫描窗口、连接间隔等所有时间参数 |
| 数据完整性 | CRC-24 校验,检测传输错误 |
| ACK 与重传 | 连接状态下的确认应答和自动重传机制 |
| 链路加密 | LL 层提供 AES-CCM 加密和认证(密钥由上层 SMP 协商生成) |
| 连接管理 | 连接参数更新、PHY 更新、数据长度扩展等控制流程 |
四、HCI 主机控制器接口
HCI 是 Host 和 Controller 之间的标准化通信接口,它的存在使得不同厂商的 Host 和 Controller 可以互换使用。
4.1 HCI 的三种数据包类型
| 包类型 | 方向 | 标识 | 说明 |
|---|---|---|---|
| Command | Host → Controller | 0x01 | 主机向控制器发送命令(如"启动广播""建立连接") |
| Event | Controller → Host | 0x04 | 控制器向主机报告状态(如"连接成功""数据到达") |
| ACL Data | 双向 | 0x02 | 用户数据在 Host 和 Controller 之间传输 |
4.2 常见 HCI LE 命令示例
| HCI 命令 | 功能 | 使用场景 |
|---|---|---|
LE Set Advertising Parameters |
设置广播间隔、类型、信道 | 配置外设广播行为 |
LE Set Advertising Data |
设置广播载荷内容 | 填入设备名、服务 UUID 等 |
LE Set Scan Parameters |
设置扫描窗口、间隔、类型 | 配置主机扫描行为 |
LE Create Connection |
向指定设备发起连接请求 | 建立 BLE 连接 |
LE Read White List Size |
读取白名单容量 | 设备过滤 |
LE Set Data Length |
设置数据长度扩展参数 | 优化吞吐量(DLE) |
4.3 HCI 的物理传输方式
| 传输方式 | 特点 | 典型场景 |
|---|---|---|
| UART | 成本低,最常用 | 嵌入式双芯片方案 |
| USB | 高速,即插即用 | PC 端蓝牙适配器 |
| SPI | 高速,但线序较多 | 部分嵌入式设计 |
| SDIO | 高速,常用于 combo 芯片 | Wi-Fi + BT 组合模块 |
4.4 HCI 的调试价值
HCI 日志是排障的第一手资料。 当你遇到 BLE 问题时,第一反应应该是:
"问题出在 Controller 还是 Host?"
判断方法很简单------看 HCI 边界:
- HCI 命令正确,但 Controller 返回异常 → 问题在 Controller(射频/时序/硬件)
- HCI 命令本身就有问题 → 问题在 Host(应用层/协议栈逻辑)
五、Host 层详解
Host 层是 BLE 协议栈的"智慧中枢",运行在设备的主处理器上,承载协议栈中大部分逻辑处理与决策功能。它由 5 个协议/配置文件组成:
5.1 L2CAP --- 逻辑链路控制与适配协议
L2CAP 是 Host 层的"交通调度员",位于 LL 层之上,为上层协议提供数据封装和多路复用服务。
| 功能 | BLE 中的实现 | 与经典蓝牙对比 |
|---|---|---|
| 信道复用 | CID(Channel ID)分配:ATT=0x0004,SMP=0x0006 | 经典蓝牙用 PSM |
| 分段重组 | 将上层大数据包分割为 LL 层可承载的小包 | 经典蓝牙最大 65535 字节 |
| 流控 | Basic Mode + LE Credit Based Flow Control | 经典蓝牙有 Basic/ERTM/Streaming 三种 |
| MTU | 默认 23 字节,最大 517 字节 | 经典蓝牙默认 672 字节 |
信道标识符(CID)速查:
| CID 值 | 分配给 |
|---|---|
| 0x0001 | L2CAP 信令通道 |
| 0x0004 | ATT(属性协议) |
| 0x0005 | LE L2CAP Connection Oriented Channel |
| 0x0006 | SMP(安全管理器协议) |
| 0x0007 | SMP over BR/EDR |
实际开发中你很少直接操作 L2CAP------协议栈会帮你处理分段/重组和信道分配。但理解它的存在很重要,因为当你在 Wireshark 中看到 L2CAP 层的分段信息时,需要知道它是怎么回事来的。
5.2 ATT --- 属性协议(GATT 的底层传输)
ATT 是 BLE 数据交互的"传输管道",定义了 Client 和 Server 之间传递属性信息的 6 种基本操作原语。
5.2.1 属性(Attribute)的定义
每个属性是一个四元组:
Attribute = Handle (16-bit) + Type (UUID) + Value (变长) + Permissions
| 字段 | 说明 |
|---|---|
| Handle | 16 位句柄,是该属性在属性表中的唯一索引(类似数组下标) |
| Type | UUID,标识该属性的类型(如 0x2A37 = Heart Rate Measurement) |
| Value | 实际数据(如心率值 72) |
| Permissions | 读/写/加密等访问权限 |
5.2.2 六种 ATT 操作原语
| 操作 | 方向 | 确认 | 说明 |
|---|---|---|---|
| Read | Client → Server | 有 Response | 读取属性值 |
| Write | Client → Server | 有 Response | 写入属性值(可靠) |
| Write Without Response | Client → Server | 无 Response | 写入属性值(快速,不可靠) |
| Notification | Server → Client | 无 Confirmation | 服务器主动推送(高效) |
| Indication | Server → Client | 有 Confirmation | 服务器主动推送(可靠) |
| Read Multiple | Client → Server | 有 Response | 批量读取多个属性 |
ATT 与 GATT 的关系------这是初学者最容易混淆的概念:
ATT 是传输管道(类似 HTTP 协议)
→ 定义了"怎么传"(读/写/通知等操作原语)
GATT 是上层数据组织方式(类似 RESTful API)
→ 定义了"传什么"(Service/Characteristic/Descriptor 的层级结构)
没有 GATT,ATT 依然能工作,但对端设备不知道你的属性是什么意思------是温度?是电量?还是一个控制指令?GATT 通过标准化的 Service/Characteristic 结构解决了这个互操作性问题。
5.3 SMP --- 安全管理器协议
SMP 负责 BLE 连接的加密和安全,定义配对和密钥分发协议。它的主要功能包括:
| 功能 | 说明 |
|---|---|
| 配对 | 两台设备建立信任关系的过程 |
| 加密 | 使用 AES-CCM 对链路层数据进行加密 |
| 密钥管理 | 生成、分发和存储 LTK、IRK、CSRK 等密钥 |
| 隐私保护 | 支持 RPA(可解析私有地址)防止设备追踪 |
SMP 的详细内容将在第四阶段(安全机制) 中系统展开。这里只需要记住:SMP 位于 Host 层,通过 L2CAP 的 CID 0x0006 信道传输,最终为 Link Layer 提供加密密钥。
5.4 GAP --- 通用访问配置文件
GAP 是 Host 层中最接近应用层的一层,负责设备的对外交互行为:
| 功能 | 说明 |
|---|---|
| 设备发现 | 广播和扫描的流程定义 |
| 连接建立 | 从广播到连接的完整流程管理 |
| 安全等级 | 定义 4 个安全等级(Level 1--4) |
| 设备角色 | 定义 Peripheral/Central/Broadcaster/Observer 四种角色 |
GAP 的详细内容将在第三阶段(GAP 与 GATT) 中系统展开。
5.5 GATT --- 通用属性配置文件
GATT 建立在 ATT 之上,用层级结构组织属性数据:
Profile(配置文件)
└── Service(服务) ← 例: Heart Rate Service (0x180D)
├── Characteristic(特征) ← 例: Heart Rate Measurement (0x2A37)
│ └── Descriptor(描述符)← 例: CCCD (0x2902)
└── Characteristic
└── Descriptor
GATT 是 BLE 开发中最常打交道的协议层,实际工作中 80% 的问题出在这一层。详细内容将在第三阶段系统展开。
六、与经典蓝牙协议栈的结构对比
对于有经典蓝牙基础的开发者,以下对比表能帮你快速建立 BLE 的知识映射:
| 经典蓝牙独有 | BLE 独有 | 共有但实现不同 |
|---|---|---|
| SDP(服务发现) | GATT(属性模型) | L2CAP |
| RFCOMM(串口仿真) | ATT(属性协议) | SMP / 安全 |
| SCO/eSCO(同步语音链路) | --- | HCI |
| Inquiry / Page 流程 | Advertising / Scanning | --- |
| LMP(链路管理协议) | LL Control PDU | --- |
| A2DP / HFP / PBAP | Health Thermometer / Battery 等 | GAP |
核心差异总结:
- 服务发现机制不同:经典蓝牙用 SDP(基于查询-响应),BLE 用 GATT(基于属性浏览)。GATT 更轻量,适合资源受限设备。
- 连接建立方式不同:经典蓝牙走 Inquiry → Page → 连接(耗时数秒),BLE 走 Advertising → CONNECT_IND(耗时数毫秒)。
- 没有语音链路:BLE 4.x 没有 SCO/eSCO 等价物,直到 BLE 5.2 引入 LE Audio(CIS/BIS)才补齐音频能力。
- 协议栈更精简:BLE 去掉了 RFCOMM、SDP、LMP 等经典蓝牙独有的协议层,整体栈深度更浅,实现更简单。
七、数据流实战:一个温度值是怎么发出去的
理解协议栈最好的方式,是跟踪一个真实数据的传输过程。假设设备 A(Peripheral)要把当前温度 25°C(十六进制 0x19)发送给设备 B(Central)。
7.1 发送端:逐层封包
#mermaid-svg-F4RJIDVmRSju8SVZ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-F4RJIDVmRSju8SVZ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-F4RJIDVmRSju8SVZ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-F4RJIDVmRSju8SVZ .error-icon{fill:#552222;}#mermaid-svg-F4RJIDVmRSju8SVZ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-F4RJIDVmRSju8SVZ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-F4RJIDVmRSju8SVZ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-F4RJIDVmRSju8SVZ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-F4RJIDVmRSju8SVZ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-F4RJIDVmRSju8SVZ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-F4RJIDVmRSju8SVZ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-F4RJIDVmRSju8SVZ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-F4RJIDVmRSju8SVZ .marker.cross{stroke:#333333;}#mermaid-svg-F4RJIDVmRSju8SVZ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-F4RJIDVmRSju8SVZ p{margin:0;}#mermaid-svg-F4RJIDVmRSju8SVZ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-F4RJIDVmRSju8SVZ .cluster-label text{fill:#333;}#mermaid-svg-F4RJIDVmRSju8SVZ .cluster-label span{color:#333;}#mermaid-svg-F4RJIDVmRSju8SVZ .cluster-label span p{background-color:transparent;}#mermaid-svg-F4RJIDVmRSju8SVZ .label text,#mermaid-svg-F4RJIDVmRSju8SVZ span{fill:#333;color:#333;}#mermaid-svg-F4RJIDVmRSju8SVZ .node rect,#mermaid-svg-F4RJIDVmRSju8SVZ .node circle,#mermaid-svg-F4RJIDVmRSju8SVZ .node ellipse,#mermaid-svg-F4RJIDVmRSju8SVZ .node polygon,#mermaid-svg-F4RJIDVmRSju8SVZ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-F4RJIDVmRSju8SVZ .rough-node .label text,#mermaid-svg-F4RJIDVmRSju8SVZ .node .label text,#mermaid-svg-F4RJIDVmRSju8SVZ .image-shape .label,#mermaid-svg-F4RJIDVmRSju8SVZ .icon-shape .label{text-anchor:middle;}#mermaid-svg-F4RJIDVmRSju8SVZ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-F4RJIDVmRSju8SVZ .rough-node .label,#mermaid-svg-F4RJIDVmRSju8SVZ .node .label,#mermaid-svg-F4RJIDVmRSju8SVZ .image-shape .label,#mermaid-svg-F4RJIDVmRSju8SVZ .icon-shape .label{text-align:center;}#mermaid-svg-F4RJIDVmRSju8SVZ .node.clickable{cursor:pointer;}#mermaid-svg-F4RJIDVmRSju8SVZ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-F4RJIDVmRSju8SVZ .arrowheadPath{fill:#333333;}#mermaid-svg-F4RJIDVmRSju8SVZ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-F4RJIDVmRSju8SVZ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-F4RJIDVmRSju8SVZ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-F4RJIDVmRSju8SVZ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-F4RJIDVmRSju8SVZ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-F4RJIDVmRSju8SVZ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-F4RJIDVmRSju8SVZ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-F4RJIDVmRSju8SVZ .cluster text{fill:#333;}#mermaid-svg-F4RJIDVmRSju8SVZ .cluster span{color:#333;}#mermaid-svg-F4RJIDVmRSju8SVZ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-F4RJIDVmRSju8SVZ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-F4RJIDVmRSju8SVZ rect.text{fill:none;stroke-width:0;}#mermaid-svg-F4RJIDVmRSju8SVZ .icon-shape,#mermaid-svg-F4RJIDVmRSju8SVZ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-F4RJIDVmRSju8SVZ .icon-shape p,#mermaid-svg-F4RJIDVmRSju8SVZ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-F4RJIDVmRSju8SVZ .icon-shape rect,#mermaid-svg-F4RJIDVmRSju8SVZ .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-F4RJIDVmRSju8SVZ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-F4RJIDVmRSju8SVZ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-F4RJIDVmRSju8SVZ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 调用 API
bt_gatt_notify()
组织为 Characteristic
Handle=0x0015, UUID=0x2A6E
选择操作类型
Notification Opcode=0x1B
数据包: 1B 15 00 19
添加 L2CAP Header
Length=0x0004, CID=0x0004
数据包: 04 00 04 00 1B 15 00 19
添加 LL Header + Access Address
- CRC-24
GFSK 调制
在数据信道上发射
应用层
温度值 = 0x19
GATT
ATT
L2CAP
LL
PHY
空中射频信号
7.2 接收端:逐层解包
接收端的流程完全对称:
| 层 | 操作 |
|---|---|
| PHY | GFSK 解调,还原比特流 |
| LL | 校验 Access Address(是否是发给我的)→ 校验 CRC(数据是否完整)→ 剥离 LL Header |
| L2CAP | 根据 CID=0x0004 知道载荷属于 ATT → 重组分段(如有)→ 剥离 L2CAP Header |
| ATT | 根据 Opcode=0x1B 识别为 Notification → 提取 Handle=0x0015 和 Value=0x19 |
| GATT | 根据 Handle=0x0015 找到 Temperature Measurement Characteristic → 解析值 0x19 = 25°C |
| 应用层 | 收到通知回调:on_temperature_changed(25) |
这个流程理解透了,BLE 开发中遇到的大部分问题都能快速定位------因为你知道数据在哪一层出了问题。
八、阶段检查点
- 能画出 BLE 协议栈 Host / Controller / HCI 三层架构图,并说出每层包含的协议
- 能画出 Link Layer 五种状态的完整转换图,标注每种转换的触发条件
- 能解释 Access Address 在广播包和数据包中的不同作用
- 能解释 ATT 与 GATT 的关系(类比 TCP 与 HTTP / 管道与数据组织)
- 能列出 BLE 协议栈与经典蓝牙协议栈的至少 5 个结构性差异
- 能跟踪一个数据值从应用层到射频发射的完整封包流程
- 知道 HCI 日志的用途,以及如何使用它来定位 Host vs Controller 问题
九、动手实验
实验 1:用 nRF Connect 抓取并解析广播包
工具:nRF Connect for Mobile(Android / iOS 免费)
目标:直观看到 BLE 广播数据的 TLV 结构
步骤:
- 在手机上安装 nRF Connect(应用商店搜索 "nRF Connect for Mobile",开发者为 Nordic Semiconductor ASA)

nRF Connect 扫描到设备后显示设备名、MAC 地址、RSSI 信号强度
-
打开 App,点击右上角 SCAN 按钮开始扫描
-
在设备列表中找到任意一个 BLE 设备(如智能手环、空气净化器)
-
点击设备卡片展开,查看 Raw advertising data(原始广播数据)
-
手动按 TLV 格式解析十六进制数据:
示例原始数据:02 01 06 09 08 4D 79 44 65 76 69 63 65
解析:
02 01 06
├── Length = 0x02 (2字节)
├── Type = 0x01 (Flags)
└── Data = 0x06 (LE General Discoverable + BR/EDR Not Supported)09 08 4D 79 44 65 76 69 63 65
├── Length = 0x09 (9字节)
├── Type = 0x08 (Shortened Local Name)
└── Data = "MyDevice" (ASCII)
预期收获:理解广播数据不是黑盒,而是由 Length-Type-Value 三元组组成的结构化数据。
实验 2:观察 HCI 日志
工具:Android 手机(需开发者模式)或 Wireshark + nRF Sniffer
目标:看到 Host 和 Controller 之间的真实 HCI 命令交互
方案 A:Android HCI Snoop Log(无需额外硬件)
- 进入手机 开发者选项 → 开启 启用蓝牙 HCI 信息收集日志

Android 开发者选项中的蓝牙 HCI 日志开关位置
- 打开蓝牙,用 nRF Connect 扫描并连接一个 BLE 设备
- 操作完成后关闭日志
- 将生成的
btsnoop_hci.log文件传输到电脑 - 用 Wireshark 打开该文件

Wireshark 加载 HCI Snoop Log 后可以看到完整的命令/事件交互
- 在 Wireshark 过滤器中输入以下过滤表达式,分别观察:
bthci_cmd:只看 HCI 命令bthci_evt:只看 HCI 事件btle:只看 BLE 链路层数据包
你应该能看到的关键 HCI 命令:
| 命令 | 说明 |
|---|---|
LE Set Scan Parameters |
设置扫描窗口/间隔 |
LE Set Scan Enable |
启动扫描 |
LE Advertising Report |
扫描到的广播设备报告 |
LE Create Connection |
向目标设备发起连接 |
LE Connection Complete |
连接建立完成事件 |
方案 B:Wireshark + nRF52840 Dongle 空口抓包(推荐进阶)
这是专业 BLE 开发者的标准抓包方案,能捕获空中的原始射频数据包。
硬件准备:
- nRF52840 USB Dongle(约 ¥100)
- 安装 nRF Sniffer 固件(从 Nordic GitHub 下载)
软件配置:
- 安装 Wireshark(≥ 3.2 版本)
- 安装 nRF Util 工具及 Sniffer 插件
- 将 Dongle 插入 USB,Wireshark 的捕获接口列表中会出现 "nRF Sniffer for Bluetooth LE"

Wireshark 捕获界面中显示的 nRF Sniffer for Bluetooth LE 接口
- 双击该接口开始捕获,你会看到周围所有 BLE 设备的空中数据包
常用 Wireshark 显示过滤器:
# 只看广播包
btle.adv_header.type == 0x00
# 按设备地址过滤
btle.advertising_address == aa:bb:cc:dd:ee:ff
# 只看连接建立过程
btle.control_opcode == 0x05
# 只看 ATT 层数据
btatt

Wireshark 过滤 BTLE 协议后的数据包列表,可以看到 ADV_IND 等广播类型
预期收获:亲眼看到 LL 层的 Access Address、LL Header、PDU 结构,以及 HCI 命令的实际格式。这是理解协议栈最直接的方式。
实验 3:对比 BLE 和经典蓝牙的 L2CAP
工具 :Core Spec Vol 3, Part A(免费下载)
目标:理解 BLE L2CAP 与经典蓝牙 L2CAP 的关键差异
阅读要点:
- 翻到 Section 3(L2CAP Channel)
- 对比经典蓝牙的 3 种操作模式(Basic / ERTM / Streaming)与 BLE 的简化实现
- 重点关注:
- 经典蓝牙的 PSM(Protocol/Service Multiplexer) 在 BLE 中被 CID(Channel ID) 替代
- BLE 的 L2CAP MTU 默认只有 23 字节(经典蓝牙默认 672 字节)
- BLE 5.2 引入的 CoC(Connection Oriented Channels) 提供了类似经典蓝牙 ERTM 的可靠传输
十、推荐学习资源
| 类型 | 资源 | 链接 |
|---|---|---|
| 官方规范 | Bluetooth Core Spec v5.4 Vol 6 Part B(Link Layer) | bluetooth.com/specifications |
| 官方博客 | 深入浅出低功耗蓝牙协议栈 | nordicsemi.cn/blog |
| 培训课程 | Nordic BLE 基础课程第一课 | nordicsemi.cn 中文课程 |
| 芯片文档 | TI BLE5-Stack Link Layer 指南 | software-dl.ti.com |
| 芯片文档 | Silicon Labs BLE 架构说明 | docs.silabs.com |
| 工具 | Wireshark BLE 显示过滤器参考 | wireshark.org/docs/dfref/b/btle.html |
| 工具 | Nordic nRF Sniffer 使用指南 | docs.nordicsemi.com |