蓝牙协议详解:经典蓝牙与低功耗蓝牙(BLE)区别、BLE协议栈架构、广播与扫描、连接与数据传输

蓝牙这玩意儿,做物联网的几乎天天打交道。但说实话,很多人对它的理解停留在「能连上就行」。我早年做一款智能手环时,就被经典蓝牙的功耗坑过一次——设备续航直接从两周掉到两天。从那以后,我花了不少功夫把蓝牙协议栈从头捋了一遍。

这一章,咱们就聚焦两个核心问题:经典蓝牙和 BLE 到底差在哪?BLE 的广播、扫描、连接、数据传输又是怎么玩的? 我会结合项目里的实际踩坑经验来讲,希望能帮你少走弯路。

经典蓝牙 vs 低功耗蓝牙(BLE):不只是功耗的区别

很多人以为 BLE 就是经典蓝牙的「省电版」,其实没那么简单。它们俩在协议栈、应用场景、数据传输方式上都有本质区别。

对比维度 经典蓝牙(BR/EDR) 低功耗蓝牙(BLE)
设计目标 连续数据流,如音频、文件传输 间歇性小数据包,如传感器读数
功耗 mA 级(持续连接) μA 级(大部分时间休眠)
数据传输速率 1-3 Mbps 125 Kbps - 2 Mbps
连接建立时间 约 3-5 秒 约 3-5 毫秒
网络拓扑 点对点(Piconet) 点对点、广播、Mesh
应用场景 蓝牙耳机、音箱、车载 传感器、信标、穿戴设备

核心结论:经典蓝牙适合「一直传」,BLE 适合「偶尔传」。如果你做的是温度传感器,每 5 秒发一个数据包,用经典蓝牙就是杀鸡用牛刀——功耗根本扛不住。

我记得有一次帮客户选型,他们想用经典蓝牙做智能门锁的蓝牙开锁功能。我一看需求:每天开锁 10 次,每次传输几十个字节。我说,这活儿 BLE 干正合适,经典蓝牙的握手时间都够 BLE 传完数据再睡一觉了。后来换了 BLE 方案,电池寿命从 3 个月延长到了 1 年半。

BLE 协议栈架构:分层拆解

BLE 协议栈分三层:Controller(控制器)、Host(主机) 和 Application(应用)。咱们从下往上捋。

BLE 协议栈分层架构 Application(应用层) 用户自定义 Profile / 业务逻辑 Host(主机层) GATT(通用属性协议) ATT(属性协议) SM(安全管理器) L2CAP(逻辑链路控制与适配协议) Controller(控制器层) LL(链路层) PHY(物理层) RF(射频)

1. Controller(控制器层)

这一层负责最底层的射频收发。说白了,就是管「怎么发出去、怎么收进来」的脏活累活。

  • PHY(物理层):工作在 2.4GHz ISM 频段,有 40 个信道(37 个数据信道 + 3 个广播信道)。
  • LL(链路层):这是控制器的核心。它管理设备的五种状态:Standby(待机)、Advertising(广播)、Scanning(扫描)、Initiating(发起连接)、Connected(已连接)。

避坑指南:我曾经在调试 BLE 连接不稳定时,一直怀疑是天线问题。后来用抓包工具一看,是链路层的连接间隔(Connection Interval)设得太短,导致设备频繁唤醒,功耗飙升。记住:连接间隔越长,功耗越低,但延迟越大。要根据实际场景做权衡。

2. Host(主机层)

主机层在控制器之上,负责数据的组织、安全加密和应用层交互。

  • L2CAP:把上层数据包拆成适合底层传输的小包,也负责多路复用。
  • ATT(属性协议):定义了「客户端-服务器」模型。服务器暴露属性(Attribute),客户端读写这些属性。
  • GATT:在 ATT 之上定义了 Profile 和 Service 的规范。比如你读一个温度传感器的值,就是通过 GATT 的 Read Characteristic 操作完成的。
  • SM(安全管理器):负责配对、绑定和加密。嗯,这里容易踩坑——配对方式选不对,设备可能连不上或者不安全。

3. Application(应用层)

这一层就是咱们写业务逻辑的地方。比如定义 Service UUID、处理接收到的数据、控制外设等。

广播与扫描:设备如何「发现彼此」

BLE 设备之间要通信,第一步就是「发现」。这个过程靠广播和扫描完成。

广播(Advertising)

广播设备(Peripheral)在 3 个广播信道(37/38/39)上周期性地发送数据包。数据包内容由开发者定义,通常包含设备名称、Service UUID、厂商自定义数据等。

// 一个典型的 BLE 广播包结构(伪代码)
struct ble_advertising_packet {
    uint8_t preamble;       // 前导码
    uint32_t access_address; // 固定为 0x8E89BED6
    uint8_t pdu[37];        // 协议数据单元
    uint8_t crc[3];         // 循环冗余校验
};

// PDU 中的 AD Structure 示例
// 0x01 - Flags
// 0x09 - Complete Local Name
// 0xFF - Manufacturer Specific Data

关键参数:广播间隔(Advertising Interval)范围从 20ms 到 10.24s。间隔越短,设备被发现得越快,但功耗越高。我一般建议:需要快速连接的场景(如门锁)用 30-50ms;周期性广播的场景(如信标)用 500ms-1s。

扫描(Scanning)

扫描设备(Central)在广播信道上监听。扫描分两种模式:

  • 被动扫描(Passive Scanning):只收不发,不向广播设备请求额外数据。
  • 主动扫描(Active Scanning):收到广播包后,发送扫描请求(Scan Request),广播设备回复扫描响应(Scan Response),携带更多数据。

我做过一个项目,需要扫描 50 米外的信标。一开始用被动扫描,死活收不到。后来换成主动扫描,把扫描窗口(Scan Window)调大,才稳定抓到。说白了,主动扫描能拿到更多信息,但功耗也更高。

连接与数据传输:建立稳定通道

当扫描设备决定和某个广播设备建立连接时,它会发送一个连接请求(Connect Request)。这个请求里包含了后续通信的关键参数。

连接参数

参数 说明 典型值
连接间隔(Connection Interval) 两个连接事件之间的时间 7.5ms - 4s
从机延迟(Slave Latency) 从设备可以跳过多少个连接事件而不响应 0 - 499
监督超时(Supervision Timeout) 超过此时间未收到数据包,认为连接断开 100ms - 32s

注意:连接间隔和从机延迟是功耗与实时性的平衡点。我曾经遇到一个客户,把从机延迟设成 100,结果设备响应延迟高达 2 秒,用户体验极差。后来调整为 10,延迟降到 200ms,功耗只增加了 15%。建议从机延迟不要超过连接间隔的 10 倍。

数据传输方式

连接建立后,数据通过 GATT 的 Characteristic 进行读写。主要有三种操作:

  • Read(读):客户端主动读取服务器的属性值。适合低频、按需获取的数据。
  • Write(写):客户端向服务器的属性写入数据。分带响应(Write Request)和不带响应(Write Command)两种。
  • Notify/Indicate(通知/指示):服务器主动向客户端推送数据。Notify 不需要确认,速度快;Indicate 需要确认,更可靠。
// 使用 Notify 推送传感器数据(Nordic nRF5 SDK 示例)
static void send_sensor_data(uint16_t conn_handle, uint16_t value) {
    uint8_t data[2];
    data[0] = (value >> 8) & 0xFF;
    data[1] = value & 0xFF;

    uint32_t err_code = sd_ble_gatts_hvx(conn_handle, &hvx_params);
    // hvx_params 中设置 type 为 BLE_GATT_HVX_NOTIFICATION
    // 这样对端就能通过 Notification 回调收到数据
}

个人经验:做数据传输时,尽量用 Notify 而不是 Write。Notify 是服务器主动推,客户端不用轮询,省电又高效。我做过一个心率带项目,每秒发 10 个数据包,用 Notify 跑了一年多,电池都没换过。

小结

这一章咱们把 BLE 的核心机制过了一遍。从经典蓝牙和 BLE 的差异,到协议栈的分层结构,再到广播、扫描、连接和数据传输的细节。说实话,BLE 协议栈看起来复杂,但只要你抓住「状态机」和「GATT 属性」这两条主线,剩下的就是参数调优和踩坑积累了。