BLE 开发实战详解:Peripheral 与 Central 全流程介绍、GATT 设计、抓包调试与功耗优化

第五阶段:开发实战(详细版)

文章目录

  • 第五阶段:开发实战(详细版)
  • [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 错误码表,对应权限/状态问题

阶段总结

开发实战的核心:把前四阶段的协议知识落到"能跑起来的代码"上。

最容易卡住的四个点:

  1. 广播数据构造(长度、AD Type、地址类型)
  2. GATT Service 设计(Properties/Permissions/CCCD)
  3. Central 的异步流程(连接、发现、订阅的时序)
  4. 调试手段(没有抓包就很难定位问题)

建议交付物:一个能双向通信的 Demo + 一份 Wireshark 抓包记录 + 一组功耗对比数据。这三样做完,第五阶段就算真正过关。


相关推荐
你怎么知道我是队长20 小时前
BLE 安全机制详解:配对、绑定、密钥体系与隐私保护
bluetooth
K成长日志5 天前
BLE Host层L2CAP -- 配置参数选项
物联网·网络协议·嵌入式·蓝牙·iot·ble·bluetooth
你怎么知道我是队长6 天前
BLE 协议栈深入:从物理层到应用层的系统性拆解
bluetooth
你怎么知道我是队长16 天前
经典蓝牙 - 中间人攻击(MIMT) - Method Confusion Attack(2021)
bluetooth
你怎么知道我是队长19 天前
经典蓝牙 - 安全机制 - Numeric Comparison和justwork的差异是什么?
bluetooth
你怎么知道我是队长1 个月前
经典蓝牙(BR/EDR)核心流程详解:设备发现、连接建立、配对绑定与服务发现
bluetooth
rls_v2 个月前
jieli:I2S外设是如何工作的?
bluetooth
rls_v2 个月前
什么是蓝牙的信令测试
bluetooth
rls_v2 个月前
AC696X-双模蓝牙功耗与时隙分析
bluetooth