BLE 蓝牙低功耗
BLE 蓝牙低功耗
一、概述
BLE(Bluetooth Low Energy,蓝牙低功耗,又称 Bluetooth Smart)是工作在 2.4GHz ISM 频段的低功耗无线通信技术。与经典蓝牙(BR/EDR)相比,BLE 功耗极低(纽扣电池可用数年)、连接快、成本低,广泛用于可穿戴设备、传感器、智能家居。
一句话核心:BLE 通过广播(无连接)+ 连接(GATT 属性)两种模式工作:设备先周期性广播自己的服务,手机/网关扫描到后建立连接,通过属性表(GATT)读写服务和数据。
二、BLE vs 经典蓝牙
| 维度 | 经典蓝牙(BR/EDR) | BLE |
|---|---|---|
| 频段 | 2.4GHz | 2.4GHz |
| 连接模式 | 点对点持续连接 | 广播 + 连接 |
| 功耗 | 较高 | 极低 |
| 数据速率 | 1~3 Mbps | 1~2 Mbps |
| 延迟 | 高(秒级连接) | 低(几 ms 连接) |
| 协议栈 | 复杂 | 精简 |
| 典型场景 | 音频(耳机/音箱) | 传感/穿戴/IoT |
关键理解:BLE 不是为了快,而是为了省电——靠"大部分时间睡眠,短时间工作"实现极低功耗。
三、物理层(2.4GHz)
- 频段:2.4 GHz(2400~2483.5 MHz),40 个信道;
- 信道划分:3 个广播信道(37/38/39)+ 37 个数据信道;
- 调制:GFSK;
- 跳频:连接后数据信道按序列跳频,抗干扰。
广播信道(3): ch37 ch38 ch39 ← 广播/发现/连接用
数据信道(37): ch0~ch36(去掉3个广播信道)数值演算(信道频点):数据信道间隔 2MHz,广播信道刻意错开数据信道位置、落在 2402/2426/2480 MHz,避免与其他 2.4GHz 设备(WiFi 常见 1/6/11 信道)持续冲突:
数据信道 ch k: 频率 = 2404 + 2k MHz (k = 0~36)
例: ch0 = 2404 MHz, ch36 = 2404+72 = 2476 MHz
广播信道:
ch37 = 2402 MHz, ch38 = 2426 MHz, ch39 = 2480 MHz
(分别避开 WiFi 1/6/11 主信道, 提升发现成功率)关键理解:广播只在 3 个专用信道发(扫描设备轮询这 3 个信道即可发现);连接后跳频到数据信道传输。
四、两种工作模式
4.1 广播模式(Advertising,无连接)
设备周期性发送广播包(含设备名、服务 UUID、厂商数据),任何扫描者都能收到,无需配对/连接:
广播设备 扫描设备(手机/网关)
每隔 adv_interval 发广播包 ──────► 扫描 3 个广播信道
(默认 20ms~10.24s)- 适合:Beacon、设备发现、广播传感器数据(无需连接);
- 广播间隔越长越省电。
数值演算(广播功耗):设每次广播发射 1ms、发射电流 20mA、睡眠 5μA,对比不同广播间隔:
广播间隔 = 500ms:
占空比 = 1ms / 500ms = 0.2%
平均电流 ≈ 0.2%×20mA + 99.8%×5μA ≈ 40μA + 5μA ≈ 45μA
广播间隔 = 20ms (最短):
占空比 = 1ms / 20ms = 5%
平均电流 ≈ 5%×20mA + 95%×5μA ≈ 1000μA + 4.75μA ≈ 1mA
(比 500ms 间隔高出约 22 倍!)
纽扣电池 220mAh:
500ms 间隔: 220mAh/45μA ≈ 4889h ≈ 204 天
20ms 间隔: 220mAh/1mA ≈ 220h ≈ 9 天关键理解:广播间隔是纯广播场景的省电主开关——间隔从 20ms 拉到 500ms,待机时间可从 9 天提升到 200 天。但间隔太长会导致手机扫描发现变慢、时延变高,需在"省电"与"可被发现性/实时性"间权衡。
4.2 连接模式(Connected)
设备扫描到广播后发起连接请求,建立点对点连接,通过 GATT 交换数据:
广播设备(外设/Slave) 扫描/连接方(中央/Central)
广播 ───────────────────────► 扫描到
◄── 连接请求 ──────────────
建立连接(约定连接间隔)
按连接间隔交换数据(双发)五、协议栈分层
┌─────────────────────────────┐
│ 应用(App) │
├─────────────────────────────┤
│ GATT / GAP (Profile 层) │ ← 数据组织(属性) / 角色管理
├─────────────────────────────┤
│ ATT / SM / L2CAP │ ← 属性协议 / 安全 / 逻辑信道
├─────────────────────────────┤
│ Link Layer │ ← 链路层(状态机/跳频)
├─────────────────────────────┤
│ Physical │ ← 2.4GHz GFSK
└─────────────────────────────┘| 层 | 作用 |
|---|---|
| GAP | 通用访问配置:角色(外设/中央)、广播、连接参数 |
| GATT | 通用属性配置:定义服务/特征/描述符如何组织数据 |
| ATT | 属性协议:客户端读/写服务端属性 |
| Link Layer | 链路层:状态机(待机/广播/扫描/连接)、跳频、加密 |
六、GATT 数据模型(核心)
BLE 连接后,数据通过 GATT 属性表组织,三层结构:
┌─ Service(服务)─────────────┐
│ ┌─ Characteristic(特征)─┐ │
│ │ 属性(Value) │ │
│ │ 描述符(Descriptor) │ │
│ └─────────────────────────┘ │
└───────────────────────────────┘| 概念 | 说明 | 例子 |
|---|---|---|
| Service(服务) | 逻辑功能分组,有唯一 UUID | 心率服务 0x180D |
| Characteristic(特征) | 具体数据,含 Value + 权限 | 心率测量 0x2A37 |
| Descriptor(描述符) | 特征的附加信息/配置 | 客户端特征配置(CCCD) 0x2902 |
数值演算(属性表实例):心率计外设的 GATT 属性表(含句柄,中央通过句柄访问):
句柄 类型(UUID) 权限 值
────── ────────────────────── ────────────────── ────────────
0x0001 主服务(0x2800) Read 0x180D(心率服务)
0x0002 特征声明(0x2803) Read 0x0A 0x2A37(Notify+心率测量)
0x0003 特征值(0x2A37) Notify [当前心率 0x48=72bpm]
0x0004 客户端特征配置(0x2902) Read/Write 0x0000(未使能)
0x0001(使能 Notify)
0x0005 特征声明(0x2803) Read 0x02 0x2A29(Read+制造商名)
0x0006 特征值(0x2A29) Read "Acme"中央要收心率数据,需先写 0x0004(CCCD) 为 0x0001 使能 Notify,之后外设每次测量都通过 0x0003 主动推送。
读写操作:
- Read:中央设备读特征值;
- Write:中央设备写特征值(或写 CCCD 使能通知);
- Notify/Indicate:外设主动推送数据给中央(外设改变时发通知,最省电,常用于传感器数据)。
标准服务 UUID:心率 0x180D、电池 0x180F、设备信息 0x180A、温度 0x1809。
易错点:CCCD(0x2902)默认是
0x0000(未使能),外设不会主动发 Notify——手机必须先写0x0001使能,这是"能连接但收不到通知"最常见的原因。CCCD 是描述符(Descriptor),属于它所在特征,不单独成服务。
七、连接参数与功耗
连接后,功耗由连接参数决定:
| 参数 | 说明 | 影响 |
|---|---|---|
| 连接间隔 | 两个连接事件的时间(7.5ms~4s) | 越大越省电、延迟越高 |
| 从机延迟 | 外设可跳过的连接事件数 | 越大越省电 |
| 监督超时 | 连接多久无数据判超时断开 | 需 > 连接间隔×延迟 |
数值演算(功耗):设连接间隔 30ms、每连接事件传输 1 包、发射电流 20mA、睡眠电流 5μA:
每次传输: 30ms 中约 1ms 发射 → 占空比 = 1/30 ≈ 3.3%
平均电流 ≈ 3.3% × 20mA + 97% × 5μA ≈ 660μA + 5μA ≈ 665μA
若每天工作 5 分钟(其余睡眠):
日平均 ≈ (5min×665μA + 1435min×5μA)/1440 ≈ (3.3mAh+119mAh)/1440 ≈ 85μA
纽扣电池 220mAh → 可用约 220mAh/85μA ≈ 2590 小时 ≈ 108 天(含工作时间)关键理解:连接间隔 + 从机延迟是省电关键。传感器类设备尽量拉大连接间隔、用 Notify 推送而非频繁 Poll,功耗可显著降低。
数值演算(监督超时约束):监督超时表示"连接在多久无有效事件后判为断开"。若从机延迟让外设跳过 N 次连接事件,最坏情况下两次真实收发可间隔 连接间隔×(N+1),因此必须满足:
监督超时 > 连接间隔 × (从机延迟 + 1)
例: 连接间隔 = 30ms, 从机延迟 = 4:
最坏事件间隔 = 30ms × (4+1) = 150ms
监督超时必须 > 150ms → 设 200ms 或更大才安全
(若误设监督超时 = 100ms < 150ms, 外设因跳过4个事件
超过100ms无收发 → 被误判超时断开)
反向利用: 从机延迟 = 4 时, 实际省电等效于把连接间隔拉到 150ms,
但又能保证在"需要时"提前醒来收发 → 既省电又可控。易错点:监督超时必须大于 连接间隔×(从机延迟+1),否则外设合法地跳过连接事件时会被误判掉线——这是"连接参数没问题却频繁断开"的常见根因。
八、数据传输的完整流程
三~七节分别讲了物理层、广播/连接、协议栈、GATT、连接参数,本节把它们串起来,完整演示一次数据从"外设发到手机 / 手机发到外设"的全过程。
BLE 与 CAN 类似是"广播发现 + 点对点连接":先靠广播让手机发现设备,再建立连接,之后通过 GATT 属性操作在连接事件中交换数据。数据只在"连接事件"里传输,而连接事件只在连接间隔的整倍数时刻发生——这是理解 BLE 传输时序的关键。
8.1 连接建立:从广播到连上
以心率计外设、手机(中央)为例,建立一条连接:
| 步骤 | 动作 | 说明 |
|---|---|---|
| ① | 外设广播 | 外设按广播间隔(如 500ms)在 3 个广播信道发广播包(含设备名、服务 UUID) |
| ② | 手机扫描 | 手机轮询 3 个广播信道,收到广播包,识别到心率服务 0x180D |
| ③ | 发连接请求 | 手机向外设发 CONNECT_IND,携带期望的连接间隔等参数 |
| ④ | 建立连接 | 外设接受后双方进入"连接态",从此在约定的连接间隔上收发 |
| ⑤ | 服务发现 | 手机发起 GATT 发现,读外设的属性表,定位心率服务/特征 |
| ⑥ | 交换数据 | 双方按连接事件在数据信道跳频传输(见 8.2) |
外设(广播/从机) 手机(扫描/中央)
周期广播 ─────────────────────────────► 扫描到 0x180D
◄──────────── 连接请求 ───────────────
建立连接(约定连接间隔=30ms)
服务发现(GATT Discover) ─────────────►
◄──────── 返回属性表 ─────────────────
连接事件中按连接间隔交换数据 ◄────────►8.2 连接事件:数据到底怎么传
连接后,双方每个连接间隔唤醒一次,在一个"连接事件"里双向收发。设连接间隔 = 30ms:
时间轴 ──┬───────────┬───────────┬───────────┬──────
连接事件 │ 事件#1 │ 事件#2 │ 事件#3 │ ...
▼ ▼ ▼
[中央发→外设] [中央发→外设] [中央发→外设] ← 每个事件中央先发(可空)
[外设发→中央] [外设发→中央] [外设发→中央] ← 然后外设发(可空)
└─ 30ms ─┘└─ 30ms ─┘└─ 30ms ─┘
例: 中央在事件#2 读心率特征:
事件#2: 中央发 Read(0x2A37) → 外设回数据(0x48=72bpm)| 参与者 | 在每个连接事件的动作 |
|---|---|
| 中央(Master) | 在每个连接事件先发(可发 Write/Read 请求,也可只发空包维持连接) |
| 外设(Slave) | 在中央发完后再发(可回读数据、发 Notify,也可只回空包) |
数据通路(GATT 操作)——连接事件里传输的是各种 ATT 协议包:
| 操作 | 方向 | 用途 |
|---|---|---|
| Read | 中央→外设 | 读某个特征值 |
| Write | 中央→外设 | 写特征值(如写 CCCD 使能通知) |
| Notify | 外设→中央 | 外设主动推送数据(无需中央请求) |
| Indicate | 外设→中央 | 类似 Notify,但中央需回 ACK 确认(更可靠) |
关键理解:Notify 不是"随时可发",只能在连接事件里发;但外设可以主动在某个连接事件里发 Notify(无需中央先请求),这正是它省电的原因——外设只在需要上报时、在既定连接事件里"顺便"带上数据。
8.3 Notify 数据的完整传输流程(外设 → 手机)
这是传感器类 BLE 最常见的场景。以心率值经 Notify 上报为例:
| 步骤 | 参与者 | 动作 |
|---|---|---|
| ① | 手机(中央) | 先 Write 0x2902(CCCD) = 0x0001,使能心率特征的 Notify |
| ② | 外设 | 周期读心率传感器,得到 hr |
| ③ | 外设 | 在下一个连接事件里,向手机发 Notify(特征 0x2A37,数据 hr) |
| ④ | 手机 | 收到 Notify,触发回调,应用显示心率 |
| ⑤ | 重复 | 外设每隔测量间隔重复 ②~④(连接事件持续维持连接) |
外设 手机
读传感器得 hr=72 Write CCCD=0x0001 (使能Notify)
│ ◄──── 确认
▼
连接事件: Notify(0x2A37, 72) ───────────► 收到, 触发回调 → 显示 72bpm
下一个测量: Notify(0x2A37, 75) ──────────► 显示 75bpm
... (按连接间隔, 外设"顺便"带上数据, 无需手机请求)易错点:Notify 数据是"外设主动推",但必须先由中央 Write CCCD 使能(CCCD 默认
0x0000)。若没使能,外设即使发送 Notify 手机也不会收到/处理——这是"连上了但收不到数据"最常见的原因。
8.4 读特征值的完整流程(手机 → 外设)
以手机主动读一次心率(Read)为例:
| 步骤 | 参与者 | 动作 |
|---|---|---|
| ① | 手机 | 在连接事件里发 Read(特征 0x2A37) |
| ② | 外设 | 收到 Read 请求,读当前心率值 |
| ③ | 外设 | 在同一连接事件回 Read Response(数据 0x48=72) |
| ④ | 手机 | 收到响应,应用显示 72bpm |
手机 外设
Read(0x2A37) ──────────────────────────► 收到请求, 读传感器
◄────────── Read Response(0x48) 返回 72bpm8.5 如何获取其他设备的数据?两种方式
方式一:主动 Read(拉模式,Poll)——手机需要时"去问":
手机: Read(特征) ──► 外设: 立刻回读数据
优点: 按需读取, 不浪费带宽; 缺点: 需频繁 Poll, 中央要一直唤醒方式二:Notify 上报(推模式,Push)——更省电,传感器常用:
外设: 数据变化时在连接事件主动 Notify ──► 手机: 被动收
优点: 外设只在变化时上报, 中央无需轮询 → 双方都省电
缺点: 实时性受连接间隔影响关键理解:传感器类场景首选 Notify 推送(方式二)——外设按测量间隔在既定连接事件里"顺便"带数据,中央不轮询,功耗最低。这与 CAN 的"主动上报更常用"同理:一次上报、省去多次轮询往返。Poll(Read)仅适合低频、非连续的按需读取(如用户点按钮才读一次)。
小结:本节用"连接建立 → 连接事件 → Notify 推送 / Read 读取"把 BLE 的双向数据传输完整串了起来:Notify 演示外设→手机的推方向,Read 演示手机→外设的拉方向,两者都在连接事件中按既定时序完成。
九、常见坑与排查
| 问题 | 可能原因 | 排查 |
|---|---|---|
| 手机搜不到设备 | 广播间隔太长、广播数据错 | 检查广播数据/间隔,确认开启广播 |
| 连上了但读不到数据 | UUID 或 GATT 属性错 | 核对服务/特征 UUID 和权限 |
| Notify 收不到 | 中央未使能 CCCD(0x2902) | 需写 CCCD 使能通知 |
| 功耗异常高 | 连接间隔太短、频繁连接 | 拉大连接间隔/从机延迟 |
| 频繁断开 | 监督超时 < 连接间隔 | 监督超时须 > 连接间隔×从机延迟 |
| 距离近就不稳 | 天线匹配/频偏 | 查射频匹配、信道选择 |