第五阶段:开发实战(详细版)
文章目录
- 第五阶段:开发实战(详细版)
- [1 开发实战的目标与能力地图](#1 开发实战的目标与能力地图)
- [2 开发平台选择与对比](#2 开发平台选择与对比)
-
- [2.1 主流平台对比](#2.1 主流平台对比)
- [2.2 选型建议](#2.2 选型建议)
- [3 开发环境搭建](#3 开发环境搭建)
-
- [3.1 以 Nordic nRF Connect SDK 为例](#3.1 以 Nordic nRF Connect SDK 为例)
- [3.2 以 ESP-IDF 为例](#3.2 以 ESP-IDF 为例)
- [3.3 环境搭建检查清单](#3.3 环境搭建检查清单)
- [4 Peripheral 开发全流程](#4 Peripheral 开发全流程)
-
- [4.1 Peripheral 开发总流程图](#4.1 Peripheral 开发总流程图)
- [4.2 关键步骤与代码示意](#4.2 关键步骤与代码示意)
- [4.3 连接管理与事件处理](#4.3 连接管理与事件处理)
- [5.5 Central 开发全流程](#5.5 Central 开发全流程)
-
- [5.1 Central 开发总流程图](#5.1 Central 开发总流程图)
- [5.2 Android BLE Central 关键流程](#5.2 Android BLE Central 关键流程)
- [5.3 Android BLE 回调状态机](#5.3 Android BLE 回调状态机)
- [5.4 Central 开发注意事项](#5.4 Central 开发注意事项)
- [6 广播数据构造与解析实战](#6 广播数据构造与解析实战)
-
- [6.1 广播数据 AD Structure 格式](#6.1 广播数据 AD Structure 格式)
- [6.2 常用 AD Type 速查](#6.2 常用 AD Type 速查)
- [6.3 手工解析示例](#6.3 手工解析示例)
- [7 GATT Service 设计实战](#7 GATT Service 设计实战)
-
- [7.1 一个自定义 LED 控制 Service 设计](#7.1 一个自定义 LED 控制 Service 设计)
- [7.2 属性设计表](#7.2 属性设计表)
- [7.3 设计检查清单](#7.3 设计检查清单)
- [8 连接参数与功耗优化](#8 连接参数与功耗优化)
-
- [8.1 连接参数三要素](#8.1 连接参数三要素)
- [8.2 参数与功耗/延迟的权衡](#8.2 参数与功耗/延迟的权衡)
- [8.3 推荐参数组合](#8.3 推荐参数组合)
- [8.4 吞吐量估算](#8.4 吞吐量估算)
- [9 调试工具链](#9 调试工具链)
-
- [9.1 工具对比](#9.1 工具对比)
- [9.2 抓包分析流程](#9.2 抓包分析流程)
- [9.3 Wireshark 常用过滤](#9.3 Wireshark 常用过滤)
- [10 完整双向通信项目实战](#10 完整双向通信项目实战)
- [10.1 项目目标](#10.1 项目目标)
- [10.2 项目分解](#10.2 项目分解)
- [10.3 验收标准](#10.3 验收标准)
- [11 功耗测量与优化](#11 功耗测量与优化)
-
- [11.1 功耗测量方法](#11.1 功耗测量方法)
- [11.2 功耗优化检查表](#11.2 功耗优化检查表)
- [11.3 功耗与性能权衡图](#11.3 功耗与性能权衡图)
- [12 常见开发问题与排障](#12 常见开发问题与排障)
-
- [12.1 常见问题表](#12.1 常见问题表)
- [12.2 排障决策树](#12.2 排障决策树)
- [13 阶段检查点](#13 阶段检查点)
- [14 动手实验](#14 动手实验)
-
-
- [5.14.1 推荐实验顺序](#5.14.1 推荐实验顺序)
-
- [15 面试与工程高频问题](#15 面试与工程高频问题)
1 开发实战的目标与能力地图
第五阶段不再停留在"看懂协议",而是要求你能从零做出一个可用的 BLE 产品原型。能力地图如下:
#mermaid-svg-CtuiQihh5Z8ZD15r{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-CtuiQihh5Z8ZD15r .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-CtuiQihh5Z8ZD15r .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-CtuiQihh5Z8ZD15r .error-icon{fill:#552222;}#mermaid-svg-CtuiQihh5Z8ZD15r .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-CtuiQihh5Z8ZD15r .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-CtuiQihh5Z8ZD15r .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-CtuiQihh5Z8ZD15r .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-CtuiQihh5Z8ZD15r .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-CtuiQihh5Z8ZD15r .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-CtuiQihh5Z8ZD15r .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-CtuiQihh5Z8ZD15r .marker{fill:#333333;stroke:#333333;}#mermaid-svg-CtuiQihh5Z8ZD15r .marker.cross{stroke:#333333;}#mermaid-svg-CtuiQihh5Z8ZD15r svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-CtuiQihh5Z8ZD15r p{margin:0;}#mermaid-svg-CtuiQihh5Z8ZD15r .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-CtuiQihh5Z8ZD15r .cluster-label text{fill:#333;}#mermaid-svg-CtuiQihh5Z8ZD15r .cluster-label span{color:#333;}#mermaid-svg-CtuiQihh5Z8ZD15r .cluster-label span p{background-color:transparent;}#mermaid-svg-CtuiQihh5Z8ZD15r .label text,#mermaid-svg-CtuiQihh5Z8ZD15r span{fill:#333;color:#333;}#mermaid-svg-CtuiQihh5Z8ZD15r .node rect,#mermaid-svg-CtuiQihh5Z8ZD15r .node circle,#mermaid-svg-CtuiQihh5Z8ZD15r .node ellipse,#mermaid-svg-CtuiQihh5Z8ZD15r .node polygon,#mermaid-svg-CtuiQihh5Z8ZD15r .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-CtuiQihh5Z8ZD15r .rough-node .label text,#mermaid-svg-CtuiQihh5Z8ZD15r .node .label text,#mermaid-svg-CtuiQihh5Z8ZD15r .image-shape .label,#mermaid-svg-CtuiQihh5Z8ZD15r .icon-shape .label{text-anchor:middle;}#mermaid-svg-CtuiQihh5Z8ZD15r .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-CtuiQihh5Z8ZD15r .rough-node .label,#mermaid-svg-CtuiQihh5Z8ZD15r .node .label,#mermaid-svg-CtuiQihh5Z8ZD15r .image-shape .label,#mermaid-svg-CtuiQihh5Z8ZD15r .icon-shape .label{text-align:center;}#mermaid-svg-CtuiQihh5Z8ZD15r .node.clickable{cursor:pointer;}#mermaid-svg-CtuiQihh5Z8ZD15r .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-CtuiQihh5Z8ZD15r .arrowheadPath{fill:#333333;}#mermaid-svg-CtuiQihh5Z8ZD15r .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-CtuiQihh5Z8ZD15r .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-CtuiQihh5Z8ZD15r .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CtuiQihh5Z8ZD15r .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-CtuiQihh5Z8ZD15r .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CtuiQihh5Z8ZD15r .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-CtuiQihh5Z8ZD15r .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-CtuiQihh5Z8ZD15r .cluster text{fill:#333;}#mermaid-svg-CtuiQihh5Z8ZD15r .cluster span{color:#333;}#mermaid-svg-CtuiQihh5Z8ZD15r 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-CtuiQihh5Z8ZD15r .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-CtuiQihh5Z8ZD15r rect.text{fill:none;stroke-width:0;}#mermaid-svg-CtuiQihh5Z8ZD15r .icon-shape,#mermaid-svg-CtuiQihh5Z8ZD15r .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CtuiQihh5Z8ZD15r .icon-shape p,#mermaid-svg-CtuiQihh5Z8ZD15r .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-CtuiQihh5Z8ZD15r .icon-shape .label rect,#mermaid-svg-CtuiQihh5Z8ZD15r .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CtuiQihh5Z8ZD15r .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-CtuiQihh5Z8ZD15r .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-CtuiQihh5Z8ZD15r :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 调试与优化
Wireshark + Sniffer 抓包
HCI 日志分析
功耗测量与优化
吞吐量测试
Central 能力
扫描过滤与设备发现
连接与 MTU 协商
服务发现与读写
CCCD 订阅与通知接收
绑定与重连
Peripheral 能力
广播配置与数据构造
GATT Service/Characteristic 定义
连接管理与参数协商
Notify/Indicate 数据推送
配对绑定与权限控制
| 能力 | 验收标准 |
|---|---|
| Peripheral | 能独立完成广播、GATT 服务、连接管理、通知推送 |
| Central | 能完成扫描、连接、服务发现、读写、订阅、绑定重连 |
| 调试 | 能用 Sniffer 抓包并定位 ATT/SMP/LL 层问题 |
| 优化 | 能测量功耗并解释连接参数对功耗的影响 |
| 联调 | 能完成 Peripheral + Central 双向通信项目 |
2 开发平台选择与对比
2.1 主流平台对比
| 平台 | SDK | 典型芯片 | 优势 | 学习曲线 | 适合人群 |
|---|---|---|---|---|---|
| Nordic | nRF Connect SDK (Zephyr) | nRF52832/840, nRF5340 | 生态最完善、文档详细、社区活跃 | ★★★ | 想深入协议栈和产品化 |
| Espressif | ESP-IDF / Arduino | ESP32/ESP32-C3/S3 | 价格低、Wi-Fi 共存、中文资料多 | ★★ | 快速原型、IoT 项目 |
| Silicon Labs | Gecko SDK | EFR32BG22/BG24 | 低功耗优秀、多连接支持好 | ★★★ | 低功耗产品 |
| TI | SimpleLink SDK | CC2640R2F/CC2652 | 工业级稳定、多角色支持 | ★★★★ | 工业/多连接场景 |
| STM32WB | STM32Cube | STM32WB55 | 与 STM32 生态统一 | ★★★ | 已有 STM32 基础 |
| Android | Android BLE API | --- | Central 开发必学 | ★★ | 手机端开发 |
| iOS | Core Bluetooth | --- | Central 开发必学(限制较多) | ★★★ | 苹果生态 |
2.2 选型建议
#mermaid-svg-KPcqeHixI6BEuZci{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-KPcqeHixI6BEuZci .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-KPcqeHixI6BEuZci .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-KPcqeHixI6BEuZci .error-icon{fill:#552222;}#mermaid-svg-KPcqeHixI6BEuZci .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-KPcqeHixI6BEuZci .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-KPcqeHixI6BEuZci .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-KPcqeHixI6BEuZci .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-KPcqeHixI6BEuZci .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-KPcqeHixI6BEuZci .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-KPcqeHixI6BEuZci .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-KPcqeHixI6BEuZci .marker{fill:#333333;stroke:#333333;}#mermaid-svg-KPcqeHixI6BEuZci .marker.cross{stroke:#333333;}#mermaid-svg-KPcqeHixI6BEuZci svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-KPcqeHixI6BEuZci p{margin:0;}#mermaid-svg-KPcqeHixI6BEuZci .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-KPcqeHixI6BEuZci .cluster-label text{fill:#333;}#mermaid-svg-KPcqeHixI6BEuZci .cluster-label span{color:#333;}#mermaid-svg-KPcqeHixI6BEuZci .cluster-label span p{background-color:transparent;}#mermaid-svg-KPcqeHixI6BEuZci .label text,#mermaid-svg-KPcqeHixI6BEuZci span{fill:#333;color:#333;}#mermaid-svg-KPcqeHixI6BEuZci .node rect,#mermaid-svg-KPcqeHixI6BEuZci .node circle,#mermaid-svg-KPcqeHixI6BEuZci .node ellipse,#mermaid-svg-KPcqeHixI6BEuZci .node polygon,#mermaid-svg-KPcqeHixI6BEuZci .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-KPcqeHixI6BEuZci .rough-node .label text,#mermaid-svg-KPcqeHixI6BEuZci .node .label text,#mermaid-svg-KPcqeHixI6BEuZci .image-shape .label,#mermaid-svg-KPcqeHixI6BEuZci .icon-shape .label{text-anchor:middle;}#mermaid-svg-KPcqeHixI6BEuZci .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-KPcqeHixI6BEuZci .rough-node .label,#mermaid-svg-KPcqeHixI6BEuZci .node .label,#mermaid-svg-KPcqeHixI6BEuZci .image-shape .label,#mermaid-svg-KPcqeHixI6BEuZci .icon-shape .label{text-align:center;}#mermaid-svg-KPcqeHixI6BEuZci .node.clickable{cursor:pointer;}#mermaid-svg-KPcqeHixI6BEuZci .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-KPcqeHixI6BEuZci .arrowheadPath{fill:#333333;}#mermaid-svg-KPcqeHixI6BEuZci .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-KPcqeHixI6BEuZci .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-KPcqeHixI6BEuZci .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KPcqeHixI6BEuZci .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-KPcqeHixI6BEuZci .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KPcqeHixI6BEuZci .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-KPcqeHixI6BEuZci .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-KPcqeHixI6BEuZci .cluster text{fill:#333;}#mermaid-svg-KPcqeHixI6BEuZci .cluster span{color:#333;}#mermaid-svg-KPcqeHixI6BEuZci 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-KPcqeHixI6BEuZci .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-KPcqeHixI6BEuZci rect.text{fill:none;stroke-width:0;}#mermaid-svg-KPcqeHixI6BEuZci .icon-shape,#mermaid-svg-KPcqeHixI6BEuZci .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KPcqeHixI6BEuZci .icon-shape p,#mermaid-svg-KPcqeHixI6BEuZci .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-KPcqeHixI6BEuZci .icon-shape .label rect,#mermaid-svg-KPcqeHixI6BEuZci .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KPcqeHixI6BEuZci .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-KPcqeHixI6BEuZci .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-KPcqeHixI6BEuZci :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
是
否
是
否
Android
iOS
你的目标是什么?
想深入协议栈
做产品化?
✅ Nordic nRF52/53
想快速做出原型?
✅ ESP32
低功耗优先?
✅ Silicon Labs EFR32
TI / STM32WB
需要手机端 Central?
✅ Android BLE API
✅ Core Bluetooth
建议组合 :一块 Nordic nRF52 开发板 (Peripheral)+ Android 手机 (Central)+ nRF Sniffer(抓包)。这套组合资料最全,遇到问题最容易搜到答案。
3 开发环境搭建
3.1 以 Nordic nRF Connect SDK 为例
| 步骤 | 操作 | 验证方式 |
|---|---|---|
| 1 | 安装 nRF Connect for Desktop | 能打开工具 |
| 2 | 安装 Toolchain Manager,下载 SDK | 版本号显示正常 |
| 3 | 安装 VS Code + nRF Connect 扩展 | 扩展识别 SDK |
| 4 | 编译 bluetooth/peripheral_hr 示例 |
编译无报错 |
| 5 | 烧录到开发板 | 串口打印启动日志 |
| 6 | 手机 nRF Connect 扫描到设备 | 能看到广播和 GATT 服务 |
3.2 以 ESP-IDF 为例
| 步骤 | 操作 | 验证方式 |
|---|---|---|
| 1 | 安装 ESP-IDF(install.sh / Windows Installer) |
idf.py --version 正常 |
| 2 | 设置环境变量 | idf.py set-target esp32 成功 |
| 3 | 进入 examples/bluetooth/bluedroid/ble/gatt_server |
目录存在 |
| 4 | idf.py build |
编译通过 |
| 5 | idf.py -p COMx flash monitor |
串口看到广播启动日志 |
| 6 | 手机 nRF Connect 扫描连接 | 能读写示例特征值 |
3.3 环境搭建检查清单
- SDK 能编译官方示例
- 开发板能被烧录工具识别
- 串口日志能正常输出
- 手机能扫描到示例设备
- nRF Connect 能连接并看到 GATT 服务
- Wireshark + Sniffer 能抓到广播包
4 Peripheral 开发全流程
4.1 Peripheral 开发总流程图
#mermaid-svg-iogeKV8jwZKZqKv1{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-iogeKV8jwZKZqKv1 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-iogeKV8jwZKZqKv1 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-iogeKV8jwZKZqKv1 .error-icon{fill:#552222;}#mermaid-svg-iogeKV8jwZKZqKv1 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-iogeKV8jwZKZqKv1 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-iogeKV8jwZKZqKv1 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-iogeKV8jwZKZqKv1 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-iogeKV8jwZKZqKv1 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-iogeKV8jwZKZqKv1 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-iogeKV8jwZKZqKv1 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-iogeKV8jwZKZqKv1 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-iogeKV8jwZKZqKv1 .marker.cross{stroke:#333333;}#mermaid-svg-iogeKV8jwZKZqKv1 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-iogeKV8jwZKZqKv1 p{margin:0;}#mermaid-svg-iogeKV8jwZKZqKv1 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-iogeKV8jwZKZqKv1 .cluster-label text{fill:#333;}#mermaid-svg-iogeKV8jwZKZqKv1 .cluster-label span{color:#333;}#mermaid-svg-iogeKV8jwZKZqKv1 .cluster-label span p{background-color:transparent;}#mermaid-svg-iogeKV8jwZKZqKv1 .label text,#mermaid-svg-iogeKV8jwZKZqKv1 span{fill:#333;color:#333;}#mermaid-svg-iogeKV8jwZKZqKv1 .node rect,#mermaid-svg-iogeKV8jwZKZqKv1 .node circle,#mermaid-svg-iogeKV8jwZKZqKv1 .node ellipse,#mermaid-svg-iogeKV8jwZKZqKv1 .node polygon,#mermaid-svg-iogeKV8jwZKZqKv1 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-iogeKV8jwZKZqKv1 .rough-node .label text,#mermaid-svg-iogeKV8jwZKZqKv1 .node .label text,#mermaid-svg-iogeKV8jwZKZqKv1 .image-shape .label,#mermaid-svg-iogeKV8jwZKZqKv1 .icon-shape .label{text-anchor:middle;}#mermaid-svg-iogeKV8jwZKZqKv1 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-iogeKV8jwZKZqKv1 .rough-node .label,#mermaid-svg-iogeKV8jwZKZqKv1 .node .label,#mermaid-svg-iogeKV8jwZKZqKv1 .image-shape .label,#mermaid-svg-iogeKV8jwZKZqKv1 .icon-shape .label{text-align:center;}#mermaid-svg-iogeKV8jwZKZqKv1 .node.clickable{cursor:pointer;}#mermaid-svg-iogeKV8jwZKZqKv1 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-iogeKV8jwZKZqKv1 .arrowheadPath{fill:#333333;}#mermaid-svg-iogeKV8jwZKZqKv1 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-iogeKV8jwZKZqKv1 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-iogeKV8jwZKZqKv1 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-iogeKV8jwZKZqKv1 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-iogeKV8jwZKZqKv1 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-iogeKV8jwZKZqKv1 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-iogeKV8jwZKZqKv1 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-iogeKV8jwZKZqKv1 .cluster text{fill:#333;}#mermaid-svg-iogeKV8jwZKZqKv1 .cluster span{color:#333;}#mermaid-svg-iogeKV8jwZKZqKv1 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-iogeKV8jwZKZqKv1 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-iogeKV8jwZKZqKv1 rect.text{fill:none;stroke-width:0;}#mermaid-svg-iogeKV8jwZKZqKv1 .icon-shape,#mermaid-svg-iogeKV8jwZKZqKv1 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-iogeKV8jwZKZqKv1 .icon-shape p,#mermaid-svg-iogeKV8jwZKZqKv1 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-iogeKV8jwZKZqKv1 .icon-shape .label rect,#mermaid-svg-iogeKV8jwZKZqKv1 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-iogeKV8jwZKZqKv1 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-iogeKV8jwZKZqKv1 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-iogeKV8jwZKZqKv1 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 初始化协议栈
配置广播参数
构造广播数据
注册 GATT Service
启动广播
等待连接
连接建立
参数协商 / MTU 协商
处理 GATT 读写
Notify/Indicate 推送
断开 / 重连
4.2 关键步骤与代码示意
步骤 1:初始化与广播参数
c
// ESP-IDF 风格示意
esp_ble_adv_params_t adv_params = {
.adv_int_min = 0x20, // 20ms
.adv_int_max = 0x40, // 40ms
.adv_type = ADV_TYPE_IND,
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
.channel_map = ADV_CHNL_ALL,
.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
};
esp_ble_gap_start_advertising(&adv_params);
步骤 2:构造广播数据
c
uint8_t adv_data[] = {
0x02, 0x01, 0x06, // Flags: LE General Discoverable, BR/EDR Not Supported
0x05, 0x09, 'M', 'y', 'B', 'L', // Complete Local Name: "MyBL"
0x03, 0x03, 0x0D, 0x18, // Complete 16-bit UUID: Heart Rate 0x180D
};
esp_ble_gap_config_adv_data_raw(adv_data, sizeof(adv_data));
步骤 3:注册 GATT Service(示意)
c
// 定义 Service 和 Characteristic
static const uint16_t SERVICE_UUID = 0x180D;
static const uint16_t CHAR_UUID = 0x2A37;
// 创建 Service
gatts_svc = gatts_create_service(SERVICE_UUID);
// 添加 Characteristic:Read + Notify
gatts_char = gatts_add_characteristic(gatts_svc, CHAR_UUID,
PROP_READ | PROP_NOTIFY,
PERM_READ);
// 添加 CCCD Descriptor
gatts_add_descriptor(gatts_char, CCCD_UUID, PERM_READ | PERM_WRITE);
// 启动 Service
gatts_start_service(gatts_svc);
4.3 连接管理与事件处理
| 事件 | 处理动作 | 注意点 |
|---|---|---|
| 连接建立 | 保存 conn_handle,停止广播 | 可配置"连接后是否继续广播" |
| 参数更新 | 接受或拒绝 Central 请求 | 检查 Supervision Timeout 约束 |
| MTU 协商 | 记录协商后的 MTU | 影响单包有效载荷 |
| CCCD 写入 | 记录订阅状态 | 决定是否推送 Notify |
| 断开 | 清理状态,重新广播 | 注意绑定信息保留 |
#mermaid-svg-cTDNkcD1GMSaLtTl{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-cTDNkcD1GMSaLtTl .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-cTDNkcD1GMSaLtTl .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-cTDNkcD1GMSaLtTl .error-icon{fill:#552222;}#mermaid-svg-cTDNkcD1GMSaLtTl .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-cTDNkcD1GMSaLtTl .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-cTDNkcD1GMSaLtTl .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-cTDNkcD1GMSaLtTl .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-cTDNkcD1GMSaLtTl .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-cTDNkcD1GMSaLtTl .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-cTDNkcD1GMSaLtTl .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-cTDNkcD1GMSaLtTl .marker{fill:#333333;stroke:#333333;}#mermaid-svg-cTDNkcD1GMSaLtTl .marker.cross{stroke:#333333;}#mermaid-svg-cTDNkcD1GMSaLtTl svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-cTDNkcD1GMSaLtTl p{margin:0;}#mermaid-svg-cTDNkcD1GMSaLtTl defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-cTDNkcD1GMSaLtTl g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-cTDNkcD1GMSaLtTl g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-cTDNkcD1GMSaLtTl g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-cTDNkcD1GMSaLtTl g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-cTDNkcD1GMSaLtTl g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-cTDNkcD1GMSaLtTl .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-cTDNkcD1GMSaLtTl .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-cTDNkcD1GMSaLtTl .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-cTDNkcD1GMSaLtTl .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-cTDNkcD1GMSaLtTl .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-cTDNkcD1GMSaLtTl .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-cTDNkcD1GMSaLtTl .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-cTDNkcD1GMSaLtTl .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cTDNkcD1GMSaLtTl .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-cTDNkcD1GMSaLtTl .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cTDNkcD1GMSaLtTl .edgeLabel .label text{fill:#333;}#mermaid-svg-cTDNkcD1GMSaLtTl .label div .edgeLabel{color:#333;}#mermaid-svg-cTDNkcD1GMSaLtTl .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-cTDNkcD1GMSaLtTl .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-cTDNkcD1GMSaLtTl .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-cTDNkcD1GMSaLtTl .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-cTDNkcD1GMSaLtTl .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-cTDNkcD1GMSaLtTl .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-cTDNkcD1GMSaLtTl .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-cTDNkcD1GMSaLtTl #statediagram-barbEnd{fill:#333333;}#mermaid-svg-cTDNkcD1GMSaLtTl .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-cTDNkcD1GMSaLtTl .cluster-label,#mermaid-svg-cTDNkcD1GMSaLtTl .nodeLabel{color:#131300;}#mermaid-svg-cTDNkcD1GMSaLtTl .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-cTDNkcD1GMSaLtTl .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-cTDNkcD1GMSaLtTl .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-cTDNkcD1GMSaLtTl .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-cTDNkcD1GMSaLtTl .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-cTDNkcD1GMSaLtTl .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-cTDNkcD1GMSaLtTl .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-cTDNkcD1GMSaLtTl .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-cTDNkcD1GMSaLtTl .note-edge{stroke-dasharray:5;}#mermaid-svg-cTDNkcD1GMSaLtTl .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-cTDNkcD1GMSaLtTl .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-cTDNkcD1GMSaLtTl .statediagram-note text{fill:black;}#mermaid-svg-cTDNkcD1GMSaLtTl .statediagram-note .nodeLabel{color:black;}#mermaid-svg-cTDNkcD1GMSaLtTl .statediagram .edgeLabel{color:red;}#mermaid-svg-cTDNkcD1GMSaLtTl #dependencyStart,#mermaid-svg-cTDNkcD1GMSaLtTl #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-cTDNkcD1GMSaLtTl .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-cTDNkcD1GMSaLtTl :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 连接建立
读写/通知/参数更新
断开连接
主动停止
Advertising
Connected
5.5 Central 开发全流程
5.1 Central 开发总流程图
#mermaid-svg-NcxMrpjQk6cyXPmm{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-NcxMrpjQk6cyXPmm .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-NcxMrpjQk6cyXPmm .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-NcxMrpjQk6cyXPmm .error-icon{fill:#552222;}#mermaid-svg-NcxMrpjQk6cyXPmm .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NcxMrpjQk6cyXPmm .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-NcxMrpjQk6cyXPmm .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NcxMrpjQk6cyXPmm .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NcxMrpjQk6cyXPmm .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-NcxMrpjQk6cyXPmm .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NcxMrpjQk6cyXPmm .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NcxMrpjQk6cyXPmm .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NcxMrpjQk6cyXPmm .marker.cross{stroke:#333333;}#mermaid-svg-NcxMrpjQk6cyXPmm svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NcxMrpjQk6cyXPmm p{margin:0;}#mermaid-svg-NcxMrpjQk6cyXPmm .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-NcxMrpjQk6cyXPmm .cluster-label text{fill:#333;}#mermaid-svg-NcxMrpjQk6cyXPmm .cluster-label span{color:#333;}#mermaid-svg-NcxMrpjQk6cyXPmm .cluster-label span p{background-color:transparent;}#mermaid-svg-NcxMrpjQk6cyXPmm .label text,#mermaid-svg-NcxMrpjQk6cyXPmm span{fill:#333;color:#333;}#mermaid-svg-NcxMrpjQk6cyXPmm .node rect,#mermaid-svg-NcxMrpjQk6cyXPmm .node circle,#mermaid-svg-NcxMrpjQk6cyXPmm .node ellipse,#mermaid-svg-NcxMrpjQk6cyXPmm .node polygon,#mermaid-svg-NcxMrpjQk6cyXPmm .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-NcxMrpjQk6cyXPmm .rough-node .label text,#mermaid-svg-NcxMrpjQk6cyXPmm .node .label text,#mermaid-svg-NcxMrpjQk6cyXPmm .image-shape .label,#mermaid-svg-NcxMrpjQk6cyXPmm .icon-shape .label{text-anchor:middle;}#mermaid-svg-NcxMrpjQk6cyXPmm .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-NcxMrpjQk6cyXPmm .rough-node .label,#mermaid-svg-NcxMrpjQk6cyXPmm .node .label,#mermaid-svg-NcxMrpjQk6cyXPmm .image-shape .label,#mermaid-svg-NcxMrpjQk6cyXPmm .icon-shape .label{text-align:center;}#mermaid-svg-NcxMrpjQk6cyXPmm .node.clickable{cursor:pointer;}#mermaid-svg-NcxMrpjQk6cyXPmm .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-NcxMrpjQk6cyXPmm .arrowheadPath{fill:#333333;}#mermaid-svg-NcxMrpjQk6cyXPmm .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-NcxMrpjQk6cyXPmm .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-NcxMrpjQk6cyXPmm .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NcxMrpjQk6cyXPmm .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-NcxMrpjQk6cyXPmm .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NcxMrpjQk6cyXPmm .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-NcxMrpjQk6cyXPmm .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-NcxMrpjQk6cyXPmm .cluster text{fill:#333;}#mermaid-svg-NcxMrpjQk6cyXPmm .cluster span{color:#333;}#mermaid-svg-NcxMrpjQk6cyXPmm 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-NcxMrpjQk6cyXPmm .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-NcxMrpjQk6cyXPmm rect.text{fill:none;stroke-width:0;}#mermaid-svg-NcxMrpjQk6cyXPmm .icon-shape,#mermaid-svg-NcxMrpjQk6cyXPmm .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NcxMrpjQk6cyXPmm .icon-shape p,#mermaid-svg-NcxMrpjQk6cyXPmm .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-NcxMrpjQk6cyXPmm .icon-shape .label rect,#mermaid-svg-NcxMrpjQk6cyXPmm .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NcxMrpjQk6cyXPmm .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-NcxMrpjQk6cyXPmm .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-NcxMrpjQk6cyXPmm :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 初始化 BLE Adapter
扫描(可带过滤)
发现目标设备
发起连接
连接成功
MTU 协商
服务发现
查找 Characteristic
读写特征值
写 CCCD 订阅通知
接收 Notify
断开 / 重连
5.2 Android BLE Central 关键流程
kotlin
// 1. 扫描
val scanner = bluetoothAdapter.bluetoothLeScanner
val settings = ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.build()
val filter = ScanFilter.Builder()
.setServiceUuid(ParcelUuid.fromString("0000180D-0000-1000-8000-00805F9B34FB"))
.build()
scanner.startScan(listOf(filter), settings, scanCallback)
// 2. 连接
val gatt = device.connectGatt(context, false, gattCallback, BluetoothDevice.TRANSPORT_LE)
// 3. 连接成功后请求 MTU
gatt.requestMtu(247)
// 4. 服务发现
gatt.discoverServices()
// 5. 写 CCCD 订阅通知
val cccd = characteristic.getDescriptor(CCCD_UUID)
gatt.setCharacteristicNotification(characteristic, true)
cccd.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
gatt.writeDescriptor(cccd)
5.3 Android BLE 回调状态机
#mermaid-svg-DK0FmXurD9diXYZM{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-DK0FmXurD9diXYZM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-DK0FmXurD9diXYZM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-DK0FmXurD9diXYZM .error-icon{fill:#552222;}#mermaid-svg-DK0FmXurD9diXYZM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-DK0FmXurD9diXYZM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-DK0FmXurD9diXYZM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-DK0FmXurD9diXYZM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-DK0FmXurD9diXYZM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-DK0FmXurD9diXYZM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-DK0FmXurD9diXYZM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-DK0FmXurD9diXYZM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-DK0FmXurD9diXYZM .marker.cross{stroke:#333333;}#mermaid-svg-DK0FmXurD9diXYZM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-DK0FmXurD9diXYZM p{margin:0;}#mermaid-svg-DK0FmXurD9diXYZM defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-DK0FmXurD9diXYZM g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-DK0FmXurD9diXYZM g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-DK0FmXurD9diXYZM g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-DK0FmXurD9diXYZM g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-DK0FmXurD9diXYZM g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-DK0FmXurD9diXYZM .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-DK0FmXurD9diXYZM .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-DK0FmXurD9diXYZM .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-DK0FmXurD9diXYZM .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-DK0FmXurD9diXYZM .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-DK0FmXurD9diXYZM .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-DK0FmXurD9diXYZM .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-DK0FmXurD9diXYZM .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-DK0FmXurD9diXYZM .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-DK0FmXurD9diXYZM .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-DK0FmXurD9diXYZM .edgeLabel .label text{fill:#333;}#mermaid-svg-DK0FmXurD9diXYZM .label div .edgeLabel{color:#333;}#mermaid-svg-DK0FmXurD9diXYZM .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-DK0FmXurD9diXYZM .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-DK0FmXurD9diXYZM .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-DK0FmXurD9diXYZM .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-DK0FmXurD9diXYZM .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-DK0FmXurD9diXYZM .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-DK0FmXurD9diXYZM .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-DK0FmXurD9diXYZM #statediagram-barbEnd{fill:#333333;}#mermaid-svg-DK0FmXurD9diXYZM .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-DK0FmXurD9diXYZM .cluster-label,#mermaid-svg-DK0FmXurD9diXYZM .nodeLabel{color:#131300;}#mermaid-svg-DK0FmXurD9diXYZM .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-DK0FmXurD9diXYZM .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-DK0FmXurD9diXYZM .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-DK0FmXurD9diXYZM .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-DK0FmXurD9diXYZM .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-DK0FmXurD9diXYZM .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-DK0FmXurD9diXYZM .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-DK0FmXurD9diXYZM .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-DK0FmXurD9diXYZM .note-edge{stroke-dasharray:5;}#mermaid-svg-DK0FmXurD9diXYZM .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-DK0FmXurD9diXYZM .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-DK0FmXurD9diXYZM .statediagram-note text{fill:black;}#mermaid-svg-DK0FmXurD9diXYZM .statediagram-note .nodeLabel{color:black;}#mermaid-svg-DK0FmXurD9diXYZM .statediagram .edgeLabel{color:red;}#mermaid-svg-DK0FmXurD9diXYZM #dependencyStart,#mermaid-svg-DK0FmXurD9diXYZM #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-DK0FmXurD9diXYZM .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-DK0FmXurD9diXYZM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} connectGatt()
onConnectionStateChange(CONNECTED)
discoverServices()
onServicesDiscovered()
读写/通知
断开
连接失败
Disconnected
Connecting
Connected
Discovering
Ready
5.4 Central 开发注意事项
| 注意点 | 说明 |
|---|---|
| 串行化 GATT 操作 | Android 同一时间只能有一个 GATT 操作,需排队 |
| 连接间隔与超时 | 重连策略要考虑 Supervision Timeout |
| 服务发现缓存 | Android 可能缓存服务,需 refresh() |
| 通知订阅 | 必须先写 CCCD,再能收到 Notify |
| 权限 | Android 12+ 需要 BLUETOOTH_SCAN/BLUETOOTH_CONNECT |
6 广播数据构造与解析实战
6.1 广播数据 AD Structure 格式
text
+--------+--------+---------+--------+---------+---
| Length | AD Type| Data | Length | AD Type | ...
| 1 byte | 1 byte | N bytes | 1 byte | 1 byte | ...
+--------+--------+---------+--------+---------+---
6.2 常用 AD Type 速查
| AD Type | 值 | 说明 |
|---|---|---|
| Flags | 0x01 | 可发现性、BR/EDR 支持标志 |
| Incomplete 16-bit UUID | 0x02 | 部分 16-bit 服务 UUID |
| Complete 16-bit UUID | 0x03 | 完整 16-bit 服务 UUID |
| Complete Local Name | 0x09 | 完整设备名 |
| TX Power Level | 0x0A | 发射功率 |
| Manufacturer Specific | 0xFF | 厂商自定义数据 |
6.3 手工解析示例
广播原始数据:
text
02 01 06 05 09 4D 79 42 4C 03 03 0D 18
| 字段 | 长度 | 类型 | 数据 | 含义 |
|---|---|---|---|---|
02 01 06 |
2 | 0x01 | 0x06 | Flags:LE General Discoverable + BR/EDR Not Supported |
05 09 4D 79 42 4C |
5 | 0x09 | "MyBL" | 完整设备名 |
03 03 0D 18 |
3 | 0x03 | 0x180D | 完整 16-bit UUID:Heart Rate |
练习:用 nRF Connect 的 Raw Data 视图对照上表,逐字节解析一个 iBeacon 广播包(厂商数据 0xFF)。
7 GATT Service 设计实战
7.1 一个自定义 LED 控制 Service 设计
#mermaid-svg-YdRWT62qcLMNFXEi{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-YdRWT62qcLMNFXEi .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-YdRWT62qcLMNFXEi .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-YdRWT62qcLMNFXEi .error-icon{fill:#552222;}#mermaid-svg-YdRWT62qcLMNFXEi .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-YdRWT62qcLMNFXEi .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-YdRWT62qcLMNFXEi .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-YdRWT62qcLMNFXEi .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-YdRWT62qcLMNFXEi .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-YdRWT62qcLMNFXEi .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-YdRWT62qcLMNFXEi .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-YdRWT62qcLMNFXEi .marker{fill:#333333;stroke:#333333;}#mermaid-svg-YdRWT62qcLMNFXEi .marker.cross{stroke:#333333;}#mermaid-svg-YdRWT62qcLMNFXEi svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-YdRWT62qcLMNFXEi p{margin:0;}#mermaid-svg-YdRWT62qcLMNFXEi .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-YdRWT62qcLMNFXEi .cluster-label text{fill:#333;}#mermaid-svg-YdRWT62qcLMNFXEi .cluster-label span{color:#333;}#mermaid-svg-YdRWT62qcLMNFXEi .cluster-label span p{background-color:transparent;}#mermaid-svg-YdRWT62qcLMNFXEi .label text,#mermaid-svg-YdRWT62qcLMNFXEi span{fill:#333;color:#333;}#mermaid-svg-YdRWT62qcLMNFXEi .node rect,#mermaid-svg-YdRWT62qcLMNFXEi .node circle,#mermaid-svg-YdRWT62qcLMNFXEi .node ellipse,#mermaid-svg-YdRWT62qcLMNFXEi .node polygon,#mermaid-svg-YdRWT62qcLMNFXEi .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-YdRWT62qcLMNFXEi .rough-node .label text,#mermaid-svg-YdRWT62qcLMNFXEi .node .label text,#mermaid-svg-YdRWT62qcLMNFXEi .image-shape .label,#mermaid-svg-YdRWT62qcLMNFXEi .icon-shape .label{text-anchor:middle;}#mermaid-svg-YdRWT62qcLMNFXEi .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-YdRWT62qcLMNFXEi .rough-node .label,#mermaid-svg-YdRWT62qcLMNFXEi .node .label,#mermaid-svg-YdRWT62qcLMNFXEi .image-shape .label,#mermaid-svg-YdRWT62qcLMNFXEi .icon-shape .label{text-align:center;}#mermaid-svg-YdRWT62qcLMNFXEi .node.clickable{cursor:pointer;}#mermaid-svg-YdRWT62qcLMNFXEi .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-YdRWT62qcLMNFXEi .arrowheadPath{fill:#333333;}#mermaid-svg-YdRWT62qcLMNFXEi .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-YdRWT62qcLMNFXEi .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-YdRWT62qcLMNFXEi .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YdRWT62qcLMNFXEi .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-YdRWT62qcLMNFXEi .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YdRWT62qcLMNFXEi .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-YdRWT62qcLMNFXEi .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-YdRWT62qcLMNFXEi .cluster text{fill:#333;}#mermaid-svg-YdRWT62qcLMNFXEi .cluster span{color:#333;}#mermaid-svg-YdRWT62qcLMNFXEi 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-YdRWT62qcLMNFXEi .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-YdRWT62qcLMNFXEi rect.text{fill:none;stroke-width:0;}#mermaid-svg-YdRWT62qcLMNFXEi .icon-shape,#mermaid-svg-YdRWT62qcLMNFXEi .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YdRWT62qcLMNFXEi .icon-shape p,#mermaid-svg-YdRWT62qcLMNFXEi .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-YdRWT62qcLMNFXEi .icon-shape .label rect,#mermaid-svg-YdRWT62qcLMNFXEi .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YdRWT62qcLMNFXEi .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-YdRWT62qcLMNFXEi .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-YdRWT62qcLMNFXEi :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Service: LED Control
UUID: 0xAA00
Characteristic: LED State
UUID: 0xAA01
Properties: Read/Write/Notify
Characteristic: LED Blink
UUID: 0xAA02
Properties: Write Without Response
Descriptor: CCCD
UUID: 0x2902
用于订阅通知
Descriptor: User Description
UUID: 0x2901
7.2 属性设计表
| 属性 | UUID | Properties | Permissions | 说明 |
|---|---|---|---|---|
| Service | 0xAA00 | --- | --- | 自定义 LED 服务 |
| LED State | 0xAA01 | Read/Write/Notify | Read/Write | 0=灭,1=亮 |
| LED Blink | 0xAA02 | Write Without Response | Write | 闪烁频率参数 |
| CCCD | 0x2902 | Read/Write | Read/Write | 订阅通知 |
7.3 设计检查清单
- Service UUID 是否与标准服务冲突
- Characteristic 的 Properties 是否最小必要
- 是否需要 CCCD 支持通知
- 权限是否要求加密/认证
- 数据长度是否考虑 MTU 限制
- 是否使用 128-bit UUID(自定义时)
8 连接参数与功耗优化
8.1 连接参数三要素
| 参数 | 范围 | 含义 | 约束 |
|---|---|---|---|
| Connection Interval | 7.5 ms ~ 4 s | 两次连接事件间隔 | 1.25 ms 整数倍 |
| Slave Latency | 0 ~ 499 | Peripheral 可跳过的连接事件数 | 实际间隔 = Interval × (1 + Latency) |
| Supervision Timeout | 100 ms ~ 32 s | 未收到数据判定断连 | > Interval × (1 + Latency) × 2 |
8.2 参数与功耗/延迟的权衡
#mermaid-svg-UjuvXTwiIMAvqzsN{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-UjuvXTwiIMAvqzsN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-UjuvXTwiIMAvqzsN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-UjuvXTwiIMAvqzsN .error-icon{fill:#552222;}#mermaid-svg-UjuvXTwiIMAvqzsN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-UjuvXTwiIMAvqzsN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-UjuvXTwiIMAvqzsN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-UjuvXTwiIMAvqzsN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-UjuvXTwiIMAvqzsN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-UjuvXTwiIMAvqzsN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-UjuvXTwiIMAvqzsN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-UjuvXTwiIMAvqzsN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-UjuvXTwiIMAvqzsN .marker.cross{stroke:#333333;}#mermaid-svg-UjuvXTwiIMAvqzsN svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-UjuvXTwiIMAvqzsN p{margin:0;}#mermaid-svg-UjuvXTwiIMAvqzsN .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-UjuvXTwiIMAvqzsN .cluster-label text{fill:#333;}#mermaid-svg-UjuvXTwiIMAvqzsN .cluster-label span{color:#333;}#mermaid-svg-UjuvXTwiIMAvqzsN .cluster-label span p{background-color:transparent;}#mermaid-svg-UjuvXTwiIMAvqzsN .label text,#mermaid-svg-UjuvXTwiIMAvqzsN span{fill:#333;color:#333;}#mermaid-svg-UjuvXTwiIMAvqzsN .node rect,#mermaid-svg-UjuvXTwiIMAvqzsN .node circle,#mermaid-svg-UjuvXTwiIMAvqzsN .node ellipse,#mermaid-svg-UjuvXTwiIMAvqzsN .node polygon,#mermaid-svg-UjuvXTwiIMAvqzsN .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-UjuvXTwiIMAvqzsN .rough-node .label text,#mermaid-svg-UjuvXTwiIMAvqzsN .node .label text,#mermaid-svg-UjuvXTwiIMAvqzsN .image-shape .label,#mermaid-svg-UjuvXTwiIMAvqzsN .icon-shape .label{text-anchor:middle;}#mermaid-svg-UjuvXTwiIMAvqzsN .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-UjuvXTwiIMAvqzsN .rough-node .label,#mermaid-svg-UjuvXTwiIMAvqzsN .node .label,#mermaid-svg-UjuvXTwiIMAvqzsN .image-shape .label,#mermaid-svg-UjuvXTwiIMAvqzsN .icon-shape .label{text-align:center;}#mermaid-svg-UjuvXTwiIMAvqzsN .node.clickable{cursor:pointer;}#mermaid-svg-UjuvXTwiIMAvqzsN .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-UjuvXTwiIMAvqzsN .arrowheadPath{fill:#333333;}#mermaid-svg-UjuvXTwiIMAvqzsN .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-UjuvXTwiIMAvqzsN .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-UjuvXTwiIMAvqzsN .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UjuvXTwiIMAvqzsN .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-UjuvXTwiIMAvqzsN .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UjuvXTwiIMAvqzsN .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-UjuvXTwiIMAvqzsN .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-UjuvXTwiIMAvqzsN .cluster text{fill:#333;}#mermaid-svg-UjuvXTwiIMAvqzsN .cluster span{color:#333;}#mermaid-svg-UjuvXTwiIMAvqzsN 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-UjuvXTwiIMAvqzsN .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-UjuvXTwiIMAvqzsN rect.text{fill:none;stroke-width:0;}#mermaid-svg-UjuvXTwiIMAvqzsN .icon-shape,#mermaid-svg-UjuvXTwiIMAvqzsN .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UjuvXTwiIMAvqzsN .icon-shape p,#mermaid-svg-UjuvXTwiIMAvqzsN .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-UjuvXTwiIMAvqzsN .icon-shape .label rect,#mermaid-svg-UjuvXTwiIMAvqzsN .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UjuvXTwiIMAvqzsN .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-UjuvXTwiIMAvqzsN .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-UjuvXTwiIMAvqzsN :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Connection Interval 小
延迟低、吞吐高
功耗高
Slave Latency 大
功耗低
响应延迟高
Supervision Timeout 大
抗断连强
异常断开发现慢
8.3 推荐参数组合
| 场景 | Interval | Latency | Timeout | 说明 |
|---|---|---|---|---|
| 实时控制(手柄) | 7.5--15 ms | 0 | 2--4 s | 低延迟优先 |
| 传感器数据流 | 30--50 ms | 0 | 4--6 s | 平衡吞吐与功耗 |
| 低频状态同步 | 100--500 ms | 4--10 | 6--10 s | 低功耗优先 |
| 信标/广播 | 不连接 | --- | --- | 只广播不连接 |
8.4 吞吐量估算
text
理论吞吐 ≈ (ATT MTU - 3) × 8 / Connection Interval
示例:MTU=247, CI=7.5ms
≈ (247 - 3) × 8 / 0.0075 ≈ 260 Kbps
| 优化项 | 效果 |
|---|---|
| MTU 协商到 247/517 | 减少 ATT 分段 |
| DLE 协商到 251 | 单 LL PDU 承载更多数据 |
| 2M PHY | 传输时间减半 |
| 合理 Connection Interval | 每个连接事件传更多包 |
9 调试工具链
9.1 工具对比
| 工具 | 用途 | 优点 | 局限 |
|---|---|---|---|
| nRF Connect for Mobile | 扫描、连接、GATT 浏览 | 直观、免费 | 看不到底层 PDU |
| Wireshark + nRF Sniffer | 空口抓包 | 能看到 LL/ATT/SMP | 需要硬件 Sniffer |
| HCI 日志(Android bt_snoop) | Host-Controller 交互 | 能看到 HCI 命令/事件 | 看不到空口 |
| 逻辑分析仪 | 硬件时序/功耗 | 精确 | 不解析协议 |
| 串口日志 | 设备内部状态 | 简单直接 | 需代码埋点 |
9.2 抓包分析流程
#mermaid-svg-wxiyLCoMT3n5fUUq{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-wxiyLCoMT3n5fUUq .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-wxiyLCoMT3n5fUUq .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-wxiyLCoMT3n5fUUq .error-icon{fill:#552222;}#mermaid-svg-wxiyLCoMT3n5fUUq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-wxiyLCoMT3n5fUUq .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-wxiyLCoMT3n5fUUq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-wxiyLCoMT3n5fUUq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-wxiyLCoMT3n5fUUq .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-wxiyLCoMT3n5fUUq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-wxiyLCoMT3n5fUUq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-wxiyLCoMT3n5fUUq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-wxiyLCoMT3n5fUUq .marker.cross{stroke:#333333;}#mermaid-svg-wxiyLCoMT3n5fUUq svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-wxiyLCoMT3n5fUUq p{margin:0;}#mermaid-svg-wxiyLCoMT3n5fUUq .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-wxiyLCoMT3n5fUUq .cluster-label text{fill:#333;}#mermaid-svg-wxiyLCoMT3n5fUUq .cluster-label span{color:#333;}#mermaid-svg-wxiyLCoMT3n5fUUq .cluster-label span p{background-color:transparent;}#mermaid-svg-wxiyLCoMT3n5fUUq .label text,#mermaid-svg-wxiyLCoMT3n5fUUq span{fill:#333;color:#333;}#mermaid-svg-wxiyLCoMT3n5fUUq .node rect,#mermaid-svg-wxiyLCoMT3n5fUUq .node circle,#mermaid-svg-wxiyLCoMT3n5fUUq .node ellipse,#mermaid-svg-wxiyLCoMT3n5fUUq .node polygon,#mermaid-svg-wxiyLCoMT3n5fUUq .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-wxiyLCoMT3n5fUUq .rough-node .label text,#mermaid-svg-wxiyLCoMT3n5fUUq .node .label text,#mermaid-svg-wxiyLCoMT3n5fUUq .image-shape .label,#mermaid-svg-wxiyLCoMT3n5fUUq .icon-shape .label{text-anchor:middle;}#mermaid-svg-wxiyLCoMT3n5fUUq .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-wxiyLCoMT3n5fUUq .rough-node .label,#mermaid-svg-wxiyLCoMT3n5fUUq .node .label,#mermaid-svg-wxiyLCoMT3n5fUUq .image-shape .label,#mermaid-svg-wxiyLCoMT3n5fUUq .icon-shape .label{text-align:center;}#mermaid-svg-wxiyLCoMT3n5fUUq .node.clickable{cursor:pointer;}#mermaid-svg-wxiyLCoMT3n5fUUq .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-wxiyLCoMT3n5fUUq .arrowheadPath{fill:#333333;}#mermaid-svg-wxiyLCoMT3n5fUUq .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-wxiyLCoMT3n5fUUq .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-wxiyLCoMT3n5fUUq .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wxiyLCoMT3n5fUUq .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-wxiyLCoMT3n5fUUq .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wxiyLCoMT3n5fUUq .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-wxiyLCoMT3n5fUUq .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-wxiyLCoMT3n5fUUq .cluster text{fill:#333;}#mermaid-svg-wxiyLCoMT3n5fUUq .cluster span{color:#333;}#mermaid-svg-wxiyLCoMT3n5fUUq 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-wxiyLCoMT3n5fUUq .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-wxiyLCoMT3n5fUUq rect.text{fill:none;stroke-width:0;}#mermaid-svg-wxiyLCoMT3n5fUUq .icon-shape,#mermaid-svg-wxiyLCoMT3n5fUUq .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-wxiyLCoMT3n5fUUq .icon-shape p,#mermaid-svg-wxiyLCoMT3n5fUUq .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-wxiyLCoMT3n5fUUq .icon-shape .label rect,#mermaid-svg-wxiyLCoMT3n5fUUq .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-wxiyLCoMT3n5fUUq .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-wxiyLCoMT3n5fUUq .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-wxiyLCoMT3n5fUUq :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 开启 Sniffer
手机扫描/连接
Wireshark 过滤 btle
定位连接建立
看 ATT 读写/通知
看 SMP 配对
定位异常 PDU
9.3 Wireshark 常用过滤
| 过滤条件 | 作用 |
|---|---|
btle |
所有 BLE 包 |
btatt |
ATT 层 |
btatt.opcode == 0x1b |
只看 Notification |
btsmp |
SMP 配对 |
btle.advertising_address |
按广播地址过滤 |
btl2cap.cid == 0x0006 |
SMP 信道 |
10 完整双向通信项目实战
10.1 项目目标
做一个 "手机控制 LED + 设备上报温度" 的双向通信 Demo:
开发板(Peripheral) 手机(Central) 开发板(Peripheral) 手机(Central) #mermaid-svg-T3PiB1CRWmNl62CU{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-T3PiB1CRWmNl62CU .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-T3PiB1CRWmNl62CU .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-T3PiB1CRWmNl62CU .error-icon{fill:#552222;}#mermaid-svg-T3PiB1CRWmNl62CU .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-T3PiB1CRWmNl62CU .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-T3PiB1CRWmNl62CU .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-T3PiB1CRWmNl62CU .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-T3PiB1CRWmNl62CU .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-T3PiB1CRWmNl62CU .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-T3PiB1CRWmNl62CU .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-T3PiB1CRWmNl62CU .marker{fill:#333333;stroke:#333333;}#mermaid-svg-T3PiB1CRWmNl62CU .marker.cross{stroke:#333333;}#mermaid-svg-T3PiB1CRWmNl62CU svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-T3PiB1CRWmNl62CU p{margin:0;}#mermaid-svg-T3PiB1CRWmNl62CU .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-T3PiB1CRWmNl62CU text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-T3PiB1CRWmNl62CU .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-T3PiB1CRWmNl62CU .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-T3PiB1CRWmNl62CU .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-T3PiB1CRWmNl62CU .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-T3PiB1CRWmNl62CU #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-T3PiB1CRWmNl62CU .sequenceNumber{fill:white;}#mermaid-svg-T3PiB1CRWmNl62CU #sequencenumber{fill:#333;}#mermaid-svg-T3PiB1CRWmNl62CU #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-T3PiB1CRWmNl62CU .messageText{fill:#333;stroke:none;}#mermaid-svg-T3PiB1CRWmNl62CU .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-T3PiB1CRWmNl62CU .labelText,#mermaid-svg-T3PiB1CRWmNl62CU .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-T3PiB1CRWmNl62CU .loopText,#mermaid-svg-T3PiB1CRWmNl62CU .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-T3PiB1CRWmNl62CU .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-T3PiB1CRWmNl62CU .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-T3PiB1CRWmNl62CU .noteText,#mermaid-svg-T3PiB1CRWmNl62CU .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-T3PiB1CRWmNl62CU .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-T3PiB1CRWmNl62CU .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-T3PiB1CRWmNl62CU .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-T3PiB1CRWmNl62CU .actorPopupMenu{position:absolute;}#mermaid-svg-T3PiB1CRWmNl62CU .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-T3PiB1CRWmNl62CU .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-T3PiB1CRWmNl62CU .actor-man circle,#mermaid-svg-T3PiB1CRWmNl62CU line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-T3PiB1CRWmNl62CU :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 扫描并连接 1 MTU 协商(247) 2 服务发现 3 写 CCCD 订阅温度通知 4 Notify 温度数据(每 1s) 5 写 LED State(开/关) 6 控制 LED 7 Notify 当前 LED 状态 8
10.2 项目分解
| 模块 | Peripheral 侧 | Central 侧 |
|---|---|---|
| 广播 | 广播设备名 + LED Service UUID | 按 Service UUID 过滤扫描 |
| 连接 | 接受连接,保存 conn_handle | 连接并请求 MTU |
| 服务发现 | 注册 LED State / Temperature | 发现服务并缓存 handle |
| 订阅 | 处理 CCCD 写入 | 写 CCCD 开启通知 |
| 数据上报 | 定时 Notify 温度 | 接收并解析 Notify |
| 控制 | 处理 LED State 写入 | 写 LED State |
| 绑定 | 支持 Just Works 配对 | 保存绑定并重连 |
10.3 验收标准
- 手机能扫描到设备并连接
- MTU 协商到 247
- 能发现自定义 Service 和两个 Characteristic
- 订阅后能收到温度 Notify
- 写 LED State 能控制开发板 LED
- 断开后能重连,绑定信息保留
- Wireshark 能抓到完整交互流程
11 功耗测量与优化
11.1 功耗测量方法
| 方法 | 工具 | 精度 | 适用 |
|---|---|---|---|
| 万用表 | 普通万用表 | 低 | 平均电流粗测 |
| 电流探头 + 示波器 | 示波器 | 高 | 瞬态电流分析 |
| 功耗分析仪 | Nordic PPK2 / Otii | 高 | 长时间记录 |
| 开发板自带电流测量 | nRF52 DK | 中 | 快速对比 |
11.2 功耗优化检查表
| 优化项 | 动作 | 预期效果 |
|---|---|---|
| 广播间隔 | 从 20 ms 调到 1--2 s | 广播功耗大幅下降 |
| 连接间隔 | 从 7.5 ms 调到 100--500 ms | 连接功耗下降 |
| Slave Latency | 设置 4--10 | 空闲时跳过连接事件 |
| 发射功率 | 从 +4 dBm 降到 0/-4 dBm | 近距离场景省电 |
| 2M PHY | 启用 2M PHY | 传输时间减半 |
| 关闭外设 | 空闲时关传感器/屏幕 | 系统功耗下降 |
| 休眠模式 | 使用低功耗休眠 | 待机电流 μA 级 |
11.3 功耗与性能权衡图
#mermaid-svg-lnVcVawIAIX2Bw9S{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-lnVcVawIAIX2Bw9S .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-lnVcVawIAIX2Bw9S .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-lnVcVawIAIX2Bw9S .error-icon{fill:#552222;}#mermaid-svg-lnVcVawIAIX2Bw9S .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-lnVcVawIAIX2Bw9S .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-lnVcVawIAIX2Bw9S .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-lnVcVawIAIX2Bw9S .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-lnVcVawIAIX2Bw9S .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-lnVcVawIAIX2Bw9S .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-lnVcVawIAIX2Bw9S .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-lnVcVawIAIX2Bw9S .marker{fill:#333333;stroke:#333333;}#mermaid-svg-lnVcVawIAIX2Bw9S .marker.cross{stroke:#333333;}#mermaid-svg-lnVcVawIAIX2Bw9S svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-lnVcVawIAIX2Bw9S p{margin:0;}#mermaid-svg-lnVcVawIAIX2Bw9S .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-lnVcVawIAIX2Bw9S .cluster-label text{fill:#333;}#mermaid-svg-lnVcVawIAIX2Bw9S .cluster-label span{color:#333;}#mermaid-svg-lnVcVawIAIX2Bw9S .cluster-label span p{background-color:transparent;}#mermaid-svg-lnVcVawIAIX2Bw9S .label text,#mermaid-svg-lnVcVawIAIX2Bw9S span{fill:#333;color:#333;}#mermaid-svg-lnVcVawIAIX2Bw9S .node rect,#mermaid-svg-lnVcVawIAIX2Bw9S .node circle,#mermaid-svg-lnVcVawIAIX2Bw9S .node ellipse,#mermaid-svg-lnVcVawIAIX2Bw9S .node polygon,#mermaid-svg-lnVcVawIAIX2Bw9S .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-lnVcVawIAIX2Bw9S .rough-node .label text,#mermaid-svg-lnVcVawIAIX2Bw9S .node .label text,#mermaid-svg-lnVcVawIAIX2Bw9S .image-shape .label,#mermaid-svg-lnVcVawIAIX2Bw9S .icon-shape .label{text-anchor:middle;}#mermaid-svg-lnVcVawIAIX2Bw9S .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-lnVcVawIAIX2Bw9S .rough-node .label,#mermaid-svg-lnVcVawIAIX2Bw9S .node .label,#mermaid-svg-lnVcVawIAIX2Bw9S .image-shape .label,#mermaid-svg-lnVcVawIAIX2Bw9S .icon-shape .label{text-align:center;}#mermaid-svg-lnVcVawIAIX2Bw9S .node.clickable{cursor:pointer;}#mermaid-svg-lnVcVawIAIX2Bw9S .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-lnVcVawIAIX2Bw9S .arrowheadPath{fill:#333333;}#mermaid-svg-lnVcVawIAIX2Bw9S .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-lnVcVawIAIX2Bw9S .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-lnVcVawIAIX2Bw9S .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lnVcVawIAIX2Bw9S .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-lnVcVawIAIX2Bw9S .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lnVcVawIAIX2Bw9S .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-lnVcVawIAIX2Bw9S .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-lnVcVawIAIX2Bw9S .cluster text{fill:#333;}#mermaid-svg-lnVcVawIAIX2Bw9S .cluster span{color:#333;}#mermaid-svg-lnVcVawIAIX2Bw9S 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-lnVcVawIAIX2Bw9S .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-lnVcVawIAIX2Bw9S rect.text{fill:none;stroke-width:0;}#mermaid-svg-lnVcVawIAIX2Bw9S .icon-shape,#mermaid-svg-lnVcVawIAIX2Bw9S .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-lnVcVawIAIX2Bw9S .icon-shape p,#mermaid-svg-lnVcVawIAIX2Bw9S .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-lnVcVawIAIX2Bw9S .icon-shape .label rect,#mermaid-svg-lnVcVawIAIX2Bw9S .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-lnVcVawIAIX2Bw9S .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-lnVcVawIAIX2Bw9S .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-lnVcVawIAIX2Bw9S :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 低功耗
左下:低功耗 · 低延迟
● 传感器数据流
Interval 30--50ms / Latency 0
右下:低功耗 · 高延迟
● 低频同步 Interval 100--500ms / Latency 4--10
● 信标/广播(不连接)
高功耗
左上:高功耗 · 低延迟
● 实时控制
Interval 7.5--15ms / Latency 0
右上:高功耗 · 高延迟
(一般不推荐)
12 常见开发问题与排障
12.1 常见问题表
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 手机扫描不到设备 | 广播未启动/广播数据过长 | 看串口日志、抓广播包 |
| 连接后立即断开 | 参数不满足约束/服务注册失败 | 检查 Timeout 约束、GATT 注册 |
| 服务发现为空 | Service 未启动/权限问题 | 检查 GATT 注册顺序 |
| 写特征值失败 | Properties 不支持 Write/权限不足 | 检查 Properties 和 Permissions |
| 收不到 Notify | CCCD 未订阅/未推送 | 检查 CCCD 写入和 Notify 调用 |
| MTU 协商失败 | 对端不支持/时序问题 | 抓 ATT Exchange MTU 包 |
| Android 连接慢 | 服务缓存/扫描参数 | refresh()、调整扫描模式 |
| 重连失败 | 绑定信息不同步 | 删除绑定重新配对 |
| 功耗偏高 | 广播/连接间隔过小 | 用功耗仪对比参数调整 |
12.2 排障决策树
#mermaid-svg-AiKMY83qhg2GbuEz{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-AiKMY83qhg2GbuEz .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-AiKMY83qhg2GbuEz .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-AiKMY83qhg2GbuEz .error-icon{fill:#552222;}#mermaid-svg-AiKMY83qhg2GbuEz .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-AiKMY83qhg2GbuEz .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-AiKMY83qhg2GbuEz .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-AiKMY83qhg2GbuEz .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-AiKMY83qhg2GbuEz .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-AiKMY83qhg2GbuEz .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-AiKMY83qhg2GbuEz .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-AiKMY83qhg2GbuEz .marker{fill:#333333;stroke:#333333;}#mermaid-svg-AiKMY83qhg2GbuEz .marker.cross{stroke:#333333;}#mermaid-svg-AiKMY83qhg2GbuEz svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-AiKMY83qhg2GbuEz p{margin:0;}#mermaid-svg-AiKMY83qhg2GbuEz .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-AiKMY83qhg2GbuEz .cluster-label text{fill:#333;}#mermaid-svg-AiKMY83qhg2GbuEz .cluster-label span{color:#333;}#mermaid-svg-AiKMY83qhg2GbuEz .cluster-label span p{background-color:transparent;}#mermaid-svg-AiKMY83qhg2GbuEz .label text,#mermaid-svg-AiKMY83qhg2GbuEz span{fill:#333;color:#333;}#mermaid-svg-AiKMY83qhg2GbuEz .node rect,#mermaid-svg-AiKMY83qhg2GbuEz .node circle,#mermaid-svg-AiKMY83qhg2GbuEz .node ellipse,#mermaid-svg-AiKMY83qhg2GbuEz .node polygon,#mermaid-svg-AiKMY83qhg2GbuEz .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-AiKMY83qhg2GbuEz .rough-node .label text,#mermaid-svg-AiKMY83qhg2GbuEz .node .label text,#mermaid-svg-AiKMY83qhg2GbuEz .image-shape .label,#mermaid-svg-AiKMY83qhg2GbuEz .icon-shape .label{text-anchor:middle;}#mermaid-svg-AiKMY83qhg2GbuEz .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-AiKMY83qhg2GbuEz .rough-node .label,#mermaid-svg-AiKMY83qhg2GbuEz .node .label,#mermaid-svg-AiKMY83qhg2GbuEz .image-shape .label,#mermaid-svg-AiKMY83qhg2GbuEz .icon-shape .label{text-align:center;}#mermaid-svg-AiKMY83qhg2GbuEz .node.clickable{cursor:pointer;}#mermaid-svg-AiKMY83qhg2GbuEz .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-AiKMY83qhg2GbuEz .arrowheadPath{fill:#333333;}#mermaid-svg-AiKMY83qhg2GbuEz .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-AiKMY83qhg2GbuEz .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-AiKMY83qhg2GbuEz .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-AiKMY83qhg2GbuEz .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-AiKMY83qhg2GbuEz .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-AiKMY83qhg2GbuEz .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-AiKMY83qhg2GbuEz .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-AiKMY83qhg2GbuEz .cluster text{fill:#333;}#mermaid-svg-AiKMY83qhg2GbuEz .cluster span{color:#333;}#mermaid-svg-AiKMY83qhg2GbuEz 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-AiKMY83qhg2GbuEz .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-AiKMY83qhg2GbuEz rect.text{fill:none;stroke-width:0;}#mermaid-svg-AiKMY83qhg2GbuEz .icon-shape,#mermaid-svg-AiKMY83qhg2GbuEz .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-AiKMY83qhg2GbuEz .icon-shape p,#mermaid-svg-AiKMY83qhg2GbuEz .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-AiKMY83qhg2GbuEz .icon-shape .label rect,#mermaid-svg-AiKMY83qhg2GbuEz .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-AiKMY83qhg2GbuEz .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-AiKMY83qhg2GbuEz .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-AiKMY83qhg2GbuEz :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否
是
否
是
否
是
否
是
否
是
BLE 开发问题
能扫描到?
检查广播启动
和广播数据长度
能连接?
检查连接参数
和 GATT 注册
能发现服务?
检查 Service 启动
和权限
能读写/通知?
检查 Properties
CCCD 和权限
功耗是否达标?
优化广播/连接参数
发射功率
✅ 功能基本完成
13 阶段检查点
- 能独立完成一个 BLE Peripheral 的广播、GATT Service、连接管理。
- 能开发 Android BLE Central 完成扫描、连接、读写、通知订阅。
- 能手工解析一个广播包,说明每个 AD Structure 的含义。
- 能设计一个自定义 GATT Service,并说明 Properties 和 Permissions 的选择理由。
- 能使用 Wireshark + BLE Sniffer 抓包并分析 ATT PDU。
- 能计算并优化 BLE 吞吐量。
- 能测量并解释连接参数对功耗的影响。
- 能实现一个完整的双向通信项目(Peripheral + Central 联调)。
- 能排查"扫描不到、连不上、收不到通知、功耗高"等常见问题。
14 动手实验
| 实验 | 工具 | 预期收获 |
|---|---|---|
| 编译并烧录官方 Peripheral 示例 | nRF52 / ESP32 | 熟悉 SDK 和烧录流程 |
| 用 nRF Connect 解析广播包 | 手机 + 开发板 | 掌握 AD Structure |
| 实现自定义 LED Service | 开发板 | 掌握 Service/Characteristic 定义 |
| 实现 Android Central 扫描连接 | Android Studio | 掌握 Central 流程 |
| 写 CCCD 订阅温度通知 | 开发板 + 手机 | 理解 Notify 机制 |
| 测试不同 MTU/Interval 对吞吐的影响 | 开发板 + 手机 | 理解参数协商效果 |
| 用 Wireshark 抓完整交互 | Sniffer + Wireshark | 看到真实 ATT PDU |
| 测量不同广播间隔的功耗 | 功耗分析仪 | 理解功耗与参数关系 |
| 实现绑定并测试重连 | 开发板 + 手机 | 理解绑定和 LTK 重连 |
5.14.1 推荐实验顺序
#mermaid-svg-xH3zsbrsU2SlJsMY{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-xH3zsbrsU2SlJsMY .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-xH3zsbrsU2SlJsMY .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-xH3zsbrsU2SlJsMY .error-icon{fill:#552222;}#mermaid-svg-xH3zsbrsU2SlJsMY .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-xH3zsbrsU2SlJsMY .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-xH3zsbrsU2SlJsMY .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-xH3zsbrsU2SlJsMY .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-xH3zsbrsU2SlJsMY .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-xH3zsbrsU2SlJsMY .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-xH3zsbrsU2SlJsMY .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-xH3zsbrsU2SlJsMY .marker{fill:#333333;stroke:#333333;}#mermaid-svg-xH3zsbrsU2SlJsMY .marker.cross{stroke:#333333;}#mermaid-svg-xH3zsbrsU2SlJsMY svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-xH3zsbrsU2SlJsMY p{margin:0;}#mermaid-svg-xH3zsbrsU2SlJsMY .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-xH3zsbrsU2SlJsMY .cluster-label text{fill:#333;}#mermaid-svg-xH3zsbrsU2SlJsMY .cluster-label span{color:#333;}#mermaid-svg-xH3zsbrsU2SlJsMY .cluster-label span p{background-color:transparent;}#mermaid-svg-xH3zsbrsU2SlJsMY .label text,#mermaid-svg-xH3zsbrsU2SlJsMY span{fill:#333;color:#333;}#mermaid-svg-xH3zsbrsU2SlJsMY .node rect,#mermaid-svg-xH3zsbrsU2SlJsMY .node circle,#mermaid-svg-xH3zsbrsU2SlJsMY .node ellipse,#mermaid-svg-xH3zsbrsU2SlJsMY .node polygon,#mermaid-svg-xH3zsbrsU2SlJsMY .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-xH3zsbrsU2SlJsMY .rough-node .label text,#mermaid-svg-xH3zsbrsU2SlJsMY .node .label text,#mermaid-svg-xH3zsbrsU2SlJsMY .image-shape .label,#mermaid-svg-xH3zsbrsU2SlJsMY .icon-shape .label{text-anchor:middle;}#mermaid-svg-xH3zsbrsU2SlJsMY .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-xH3zsbrsU2SlJsMY .rough-node .label,#mermaid-svg-xH3zsbrsU2SlJsMY .node .label,#mermaid-svg-xH3zsbrsU2SlJsMY .image-shape .label,#mermaid-svg-xH3zsbrsU2SlJsMY .icon-shape .label{text-align:center;}#mermaid-svg-xH3zsbrsU2SlJsMY .node.clickable{cursor:pointer;}#mermaid-svg-xH3zsbrsU2SlJsMY .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-xH3zsbrsU2SlJsMY .arrowheadPath{fill:#333333;}#mermaid-svg-xH3zsbrsU2SlJsMY .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-xH3zsbrsU2SlJsMY .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-xH3zsbrsU2SlJsMY .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xH3zsbrsU2SlJsMY .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-xH3zsbrsU2SlJsMY .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xH3zsbrsU2SlJsMY .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-xH3zsbrsU2SlJsMY .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-xH3zsbrsU2SlJsMY .cluster text{fill:#333;}#mermaid-svg-xH3zsbrsU2SlJsMY .cluster span{color:#333;}#mermaid-svg-xH3zsbrsU2SlJsMY 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-xH3zsbrsU2SlJsMY .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-xH3zsbrsU2SlJsMY rect.text{fill:none;stroke-width:0;}#mermaid-svg-xH3zsbrsU2SlJsMY .icon-shape,#mermaid-svg-xH3zsbrsU2SlJsMY .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xH3zsbrsU2SlJsMY .icon-shape p,#mermaid-svg-xH3zsbrsU2SlJsMY .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-xH3zsbrsU2SlJsMY .icon-shape .label rect,#mermaid-svg-xH3zsbrsU2SlJsMY .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xH3zsbrsU2SlJsMY .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-xH3zsbrsU2SlJsMY .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-xH3zsbrsU2SlJsMY :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 实验 1
烧录示例
实验 2
解析广播
实验 3
自定义 Service
实验 4
Android Central
实验 5
通知订阅
实验 6
MTU/吞吐
实验 7
抓包分析
实验 8
功耗测量
实验 9
绑定重连
15 面试与工程高频问题
| 问题 | 回答要点 |
|---|---|
| 广播数据最大多少字节? | Legacy 31 字节;Extended 最多 1650 字节 |
| 为什么手机扫描不到设备? | 广播未启动、数据过长、地址类型、过滤条件 |
| 连接后为什么立即断开? | 参数不满足约束、GATT 注册失败、超时设置 |
| Notify 和 Indicate 怎么选? | 可靠性优先 Indicate,吞吐优先 Notify |
| CCCD 的作用? | 控制客户端是否订阅 Notify/Indicate |
| MTU 协商有什么用? | 减少 ATT 分段,提高吞吐 |
| DLE 和 MTU 的区别? | MTU 是 ATT 层,DLE 是 LL 层 |
| 如何降低功耗? | 增大广播/连接间隔、Slave Latency、降低发射功率 |
| Android 为什么只能串行 GATT 操作? | 协议栈限制,需排队 |
| 绑定后重连为什么不用重新配对? | 双方保存 LTK,直接加密 |
| 如何计算吞吐量? | (MTU-3)×8/CI,再考虑 DLE/2M PHY |
| 抓包看到 ATT 错误码怎么办? | 查 Core Spec 错误码表,对应权限/状态问题 |
阶段总结
开发实战的核心:把前四阶段的协议知识落到"能跑起来的代码"上。
最容易卡住的四个点:
- 广播数据构造(长度、AD Type、地址类型)
- GATT Service 设计(Properties/Permissions/CCCD)
- Central 的异步流程(连接、发现、订阅的时序)
- 调试手段(没有抓包就很难定位问题)
建议交付物:一个能双向通信的 Demo + 一份 Wireshark 抓包记录 + 一组功耗对比数据。这三样做完,第五阶段就算真正过关。