深入BLE低功耗蓝牙

蓝色蜻蜓

写在前面

最近在做蓝牙相关的工作,也终于有机会能由浅入深的把蓝牙的工作流程梳理一遍。梳理整个流程时,顺便写下这篇博客。

总的来说,蓝牙是工作在 2.4GHz ISM 频段上的一种无线通信协议。经典蓝牙和低功耗蓝牙分别有79个和40个信道可用,本文只关注低功耗蓝牙,仅涉及经典蓝牙中与BLE共同之处。

基础协议栈

蓝牙协议栈层级

如果只关注蓝牙在应用层的实现,往往只需要关注蓝牙的广播、服务即可,但是如果需要关注蓝牙的广播包是怎么发的?收发双方是如何跳频的,都需要更深入的了解 Conyroller 控制层中的 物理层、链路层。想要了解这两个层级,就需要先从蓝牙发送的数据空口包出发。

BLE 空口包结构

BLE空口包结构示意图

Preamble 前导码 通俗讲就是告诉接收方“我开始发数据了,请从这里开始接收”。在低功耗蓝牙BLE中,大部分情况下前导码都是 0x55 或 0xAA, 它是由 Access Address 最低位决定的,当最低位为 0 时,前导码是 0x55,当最低位是 1 时,前导码是 0xAA。0x55 和 0xAA 这两种前导码的设计是结合工程中的实际问题确定的,在实际的环境中可能会出现RF极性翻转等问题,导致接收到的数据解调后可能是 0x55 或 0xAA, 但是接收时通过 Access Address 最低位就能确定这个究竟是“真的”前导码,还是误触发的或者随机出现的。Preamble 的设计相当于设计两道门槛,只允许“大概率是真实数据包”的数据进入,把有限的资源分配给真正需要的数据。

Access Address 访问地址 在BLE中只保留两种,一个是广播通道,一个是数据通道。广播通道固定为 0x8E89BED6, 数据通道的 Access Address 是随机的,只要生成的不是特殊的即可(特殊的包括 全0、全1、与广播通道一致等)。在实际的接收方视角下,接收的数据可能是包含噪声的,所以需要在其中定位 Preamble 和 Access Address ,达到数据筛选的目的。同时,广播通道的访问地址是固定的,但是数据通道的访问地址是随机生成的,这就需要了解到在建立数据连接之前,这个访问地址已经通过 CONNECT_IND 类型的方式携带,这一步就告诉接收方,你连接后需要接收 Access Address 是 xxx 数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
PDU Header
└── PDU Type = CONNECT_IND / CONNECT_REQ

PDU Payload:
┌──────────────────────────────┐
│ InitA (6B) │
│ AdvA (6B) │
│ Access Address (4B) ← 在这里 │
│ CRC Init (3B) │
│ WinSize (1B) │
│ WinOffset (2B) │
│ Interval (2B) │
│ Latency (2B) │
│ Timeout (2B) │
│ Channel Map (5B) │
│ Hop Increment (5 bits) │
└──────────────────────────────┘

PDU Header

PDU Header 结构

接下来就是在开发中关注最多的PDU部分,这部分可以简单的拆解为 PDU HeaderPayload 两部分,其中 Header 有 2 字节,两字节的结构如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Advertising PDU Header (16 bits)

├── PDU Type (4 bits)
│ └── 指示 PDU 类型,例如 ADV_IND / SCAN_REQ / SCAN_RSP / CONNECT_IND

├── RFU / Reserved (1 bit)
│ └── 保留位,当前应为 0

├── ChSel (1 bit)
│ └── Channel Selection 标志,用于指示 Channel Selection Algorithm #2 相关能力

├── TxAdd (1 bit)
│ └── 发送方地址类型
│ ├── 0 = Public Address
│ └── 1 = Random Address

├── RxAdd (1 bit)
│ └── 接收方 / 目标地址类型
│ ├── 0 = Public Address
│ └── 1 = Random Address

├── Length (6 bits)
│ └── Payload 长度,范围 0~63 bytes

└── RFU / Reserved (2 bits)
└── 保留位,当前应为 0

| PDU Type | RFU | ChSel | TxAdd | RxAdd | Length | RFU |
|----------|-----|-------|-------|-------|--------|-----|
| 4 bits | 1 | 1 | 1 | 1 | 6 bits | 2 |

PDU Type

其中 PDU Type 决定了这整个PDU包是什么语义、进入什么状态机,下面是所有的 PDU Type 类型

分类 PDU Type 方向 语义 是否可连接 是否可扫描响应 状态机作用
广播 ADV_IND 广播 → 所有 可连接广播(通用设备发现) Advertising
广播 ADV_DIRECT_IND 广播 → 指定设备 定向连接广播 Advertising → Connect
广播 ADV_SCAN_IND 广播 → Scanner 可扫描广播 Advertising
广播 ADV_NONCONN_IND 广播 → 所有 不可连接广播(Beacon) Advertising

分类 PDU Type 方向 语义 作用
扫描 SCAN_REQ Scanner → Advertiser 请求更多广播信息 扫描交互
扫描 SCAN_RSP Advertiser → Scanner 补充广播数据 扫描交互响应

分类 PDU Type 方向 语义 核心作用
连接建立 CONNECT_IND / CONNECT_REQ Central → Peripheral(广告信道) 建立连接 切换到 Data Channel
连接建立 AUX_CONNECT_REQ (BLE 5) Central → Peripheral 扩展广播连接建立 扩展广告

分类 PDU Type 方向 语义 状态
数据通道 DATA PDU 双向 已建立连接的数据传输 Connection State

分类 PDU Type 方向 语义 说明
扩展广播 AUX_ADV_IND 广播 → 扫描器 BLE 5 扩展广播入口 Secondary Channel
扩展扫描 AUX_SCAN_REQ Scanner → Advertiser 扩展扫描请求 BLE 5
扩展扫描 AUX_SCAN_RSP Advertiser → Scanner 扩展扫描响应 BLE 5

RFU 预留

RFU 有 1 个字节,SIG将其暂时作为保留位置,等待后续扩展。

ChSel

ChSel Channel Section 表示是否支持 Channel Selection Algorithm #2 算法,当 ChSel 为 0 时表示不支持(默认支持 CSA #1),只有当其为 1 时才表示支持。

TxAdd 和 RxAdd

TxAdd 和 RxAdd 分别表示发送、接收方的地址类型,当为 0 时表示随机地址,当为 1 时表示公有地址。这只时一个标志位,表示公有还是随机。当表示公有时,这个地址是IEEE为厂家分配的,当表示随机时,这个地址是随机生成的。

Length

Payload Lenghth 表示后续 payload 的长度,目的是告诉接收方从我开始接收指定长度的数据。当广播时,payload 长度最大是 37 所以 Payload Length 是 0x25。按理说 6 bits 最大能够表示的 payload length 是 64 个字节,就是 0x40 ,但是在广播中只允许 payload 以最大 37 个字节的长度发送,这一点需要注意。

PDU Payload

PDU Payload 的结构取决于 PDU Type,下面列举几个不同PDU Type 下 Payload 的示例

ADV_IND 广播

1
2
3
4
5
6
7
ADV_IND Payload

├── AdvA (6 bytes)
│ └── Advertiser Address,广播设备地址

└── AdvData (0–31 bytes)
└── 广播数据,使用 AD Structure 组织

其中 AdvData 是由多个 AD Structure 组成,下面是其结构

1
2
3
4
5
AD Structure

├── Length (1 byte)
├── AD Type (1 byte)
└── AD Data (Length - 1 bytes)

比如一个广播的 AdvData 数据是

1
2
02 01 06
05 09 47 43 5F 31

那么其中 AD Structure 是

1
2
3
4
5
6
7
8
9
10
11
AD Structure 1

├── Length = 0x02
├── Type = 0x01 Flags
└── Data = 0x06

AD Structure 2

├── Length = 0x05
├── Type = 0x09 Complete Local Name
└── Data = 47 43 5F 31 → "GC_1"

SCAN_RSP

1
2
3
4
5
6
7
SCAN_RSP Payload

├── AdvA (6 bytes)
│ └── Advertiser Address

└── ScanRspData (0–31 bytes)
└── 扫描响应数据,结构和 AdvData 一样

使用 SCAN_RSP 可以让广播携带更多附加信息,相当于对 ADV_IND 的补充,比如在一个应用中需要过滤某服务ID为 xxx-xxx-xxxx 的设备,则可以通过这个将这部分数据广播出去。

CONNECT_IND

这是连接发起时重要的一次广播,他会携带,它的结构是

1
2
3
4
5
6
7
8
9
10
CONNECT_IND Payload

├── InitA (6 bytes)
│ └── Initiator Address,连接发起方地址,通常是 Central

├── AdvA (6 bytes)
│ └── Advertiser Address,被连接设备地址,通常是 Peripheral

└── LLData (22 bytes)
└── 后续 data channel 的连接参数

我们之前谈论的 Access Address 就在其 LLData 中,这次之后接收方就知道发送方每次发送的 Access Address。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
LLData (22 bytes)

├── Access Address (4 bytes)
│ └── 后续 data channel 使用的 AA

├── CRC Init (3 bytes)
│ └── 后续 data channel CRC 的初始值

├── WinSize (1 byte)
│ └── 首次连接事件接收窗口大小

├── WinOffset (2 bytes)
│ └── 首次连接事件的时间偏移

├── Interval (2 bytes)
│ └── Connection Interval,连接事件间隔

├── Latency (2 bytes)
│ └── Slave Latency,Peripheral 可跳过的连接事件数

├── Timeout (2 bytes)
│ └── Supervision Timeout,连接超时时间

├── Channel Map (5 bytes)
│ └── 37 个 data channel 的可用信道位图

└── Hop + SCA (1 byte)
├── Hop Increment (5 bits)
└── Sleep Clock Accuracy, SCA (3 bits)

CRC

CRC 这部分是 CRC24, 包含 PDU Header + PDU Payload,不包含 Preamble 和 Access Address。需要注意的是,在广播信道中 CRC 初始寄存器的值是 0x555555,再此基础上计算。

1
2
3
4
5
6
7
8
9
10
crc = CRCInit

对 PDU Header + PDU Payload 的每一个 byte:
从 bit0 到 bit7 依次处理

每处理一个 bit:
feedback = (crc bit0) XOR (input bit)
crc = crc >> 1
如果 feedback == 1:
crc = crc XOR 0xDA6000

以上就是BLE低功耗蓝牙的广播通道和数据通道的从基本数据流、格式。

专辑封面
仙儿
二手玫瑰

深入BLE低功耗蓝牙
https://www.wicos.me/jishu/11673/
作者
Wicos
发布于
2026年7月2日
许可协议