第二章 蓝牙协议栈基础:BR/EDR与BLE的区别、HCI层详解、GATT与ATT协议、蓝牙音频协议(A2DP、HFP、AVRCP)

做TWS开发,蓝牙协议栈是绕不开的坎。说实话,我刚入行那会儿,看到那一堆缩写——BR、EDR、BLE、HCI、GATT、ATT、A2DP、HFP、AVRCP——头都大了。但后来我发现,只要把核心脉络理清楚,这东西其实没那么玄乎。

这一章,我就带你把这些基础概念掰开揉碎。咱们不搞教科书式的罗列,而是从实际开发的角度,聊聊它们到底是什么、怎么用、坑在哪。

2.1 BR/EDR与BLE:一对“兄弟”的恩怨

蓝牙从诞生到现在,分了两条技术路线:BR/EDR(经典蓝牙)和BLE(低功耗蓝牙)。

你可能会问:“为什么要有两个?”

嗯,这得从应用场景说起。经典蓝牙设计之初,是为了替代有线耳机、文件传输这类场景,追求的是高带宽、持续连接。而BLE呢,是后来为了物联网、穿戴设备这类低功耗、间歇性通信的需求诞生的。

我个人的习惯是这么区分的:

  • BR/EDR:适合音频流、大数据传输。比如TWS耳机播放音乐,用的就是它。
  • BLE:适合小数据、低功耗、控制类。比如耳机与手机配对、电量同步、按键控制,这些活儿交给BLE更合适。

这里有个关键点:现在的TWS耳机,基本都是双模蓝牙。什么意思?就是芯片同时支持BR/EDR和BLE。音频走经典蓝牙,控制走低功耗蓝牙。我在项目中遇到过,有些新手工程师只开了经典蓝牙,结果发现电量上报延迟特别高——其实就是没启用BLE通道。

核心区别速览:

特性 BR/EDR(经典蓝牙) BLE(低功耗蓝牙)
设计目标 高带宽、持续连接 低功耗、间歇通信
典型应用 音频流、文件传输 传感器、控制、广播
功耗 较高(mA级) 极低(μA级)
连接延迟 约100ms 约3-6ms
数据速率 最高3Mbps(EDR) 最高2Mbps(BLE 5.0)

避坑指南:我曾经在调试一款TWS耳机时,发现手机搜索不到设备。查了半天,原来是BLE广播参数设置得太激进,导致芯片频繁重启。记住,BLE广播间隔建议设在100ms以上,太短了反而容易出问题。

2.2 HCI层详解:主机与控制器的“翻译官”

HCI,全称Host Controller Interface。说白了,它就是蓝牙芯片内部主机(Host)控制器(Controller)之间的通信接口。

你想想看,蓝牙协议栈分两层:上层是协议栈软件(比如L2CAP、GATT、A2DP这些),下层是射频硬件和固件。这两层怎么沟通?靠的就是HCI。

HCI定义了三种传输方式:

  • UART:最常见,成本低,适合大多数嵌入式平台。
  • USB:PC端蓝牙适配器常用。
  • SDIO:高速场景,比如Wi-Fi+蓝牙二合一模块。

在TWS开发中,我们最常用的是UART HCI。我记得第一次调HCI时,波特率设错了,结果主机发出去的指令,控制器根本收不到。折腾了两天才发现是波特率不匹配——嗯,这种低级错误,犯过一次就记住了。

HCI的数据包分四种类型:

  1. 命令包:主机发给控制器,比如“开始扫描”、“建立连接”。
  2. 事件包:控制器回复主机,比如“扫描完成”、“连接成功”。
  3. 数据包:双向传输的实际数据,比如音频流。
  4. 同步数据包:用于SCO/eSCO链路,比如通话语音。

这里有个小技巧:调试HCI时,可以用逻辑分析仪抓UART数据,对照蓝牙规范里的HCI命令格式,一眼就能看出问题出在哪。我曾经靠这招,半小时就定位了一个连接失败的问题——原来是控制器返回的“连接完成”事件里,状态码是0x3E(连接超时)。

注意:HCI命令和事件都是固定格式的,千万别自己“发明”字段。我见过有人为了省事,把命令包里的参数长度写错了,结果控制器直接不响应。严格按照蓝牙Core Spec 5.x来,别偷懒。

2.3 GATT与ATT协议:BLE世界的“数据字典”

GATT(通用属性协议)和ATT(属性协议)是BLE的灵魂。没有它们,BLE就只能做广播,没法进行有结构的数据交互。

ATT定义了属性(Attribute)这个概念。每个属性都有一个UUID、一组权限、一段数据。GATT则是在ATT之上,定义了服务(Service)特征(Characteristic)的层级结构。

你想想看,这就像一本字典:

  • 服务是“章节”,比如“电池服务”、“设备信息服务”。
  • 特征是“词条”,比如“电池电量”、“设备名称”。
  • 描述符是“注释”,比如“电量单位是百分比”。

在TWS耳机里,GATT主要用来做控制类通信。比如:

  • 读取耳机电量(电池服务)
  • 同步左右耳状态(自定义服务)
  • 接收手机发来的按键指令(HID over GATT)

我个人习惯,在开发BLE应用时,先画一张GATT结构图。把服务、特征、UUID、读写权限都列清楚。这样写代码时思路清晰,不容易漏掉。

举个例子,一个简单的电量服务:

// 电池服务UUID: 0x180F
// 电池电量特征UUID: 0x2A19
// 属性: 只读 + 通知

static const ble_uuid128_t battery_service_uuid = 
    BLE_UUID128_INIT(0x0000180F, 0x0000, 0x1000, 0x8000, 0x00805F9B34FB);

static const ble_uuid128_t battery_level_char_uuid = 
    BLE_UUID128_INIT(0x00002A19, 0x0000, 0x1000, 0x8000, 0x00805F9B34FB);

// 注册服务
ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY, &battery_service_uuid, &service_handle);
// 添加特征
ble_gatts_characteristic_add(service_handle, &char_md, &attr_char_value, &char_handles);

经验之谈:GATT的MTU(最大传输单元)默认是23字节,但你可以通过MTU协商把它提高到512字节甚至更多。我在项目中遇到过,如果不协商MTU,传大一点的数据包就会被拆成好几段,效率很低。建议在连接建立后,主动发起MTU请求。

2.4 蓝牙音频协议:A2DP、HFP、AVRCP

做TWS,这三个协议你必须吃透。它们分别负责:

  • A2DP:高级音频分发协议,负责把音乐从手机传到耳机。
  • HFP:免提协议,负责通话。
  • AVRCP:音视频远程控制协议,负责控制播放、暂停、切歌。

咱们一个一个说。

A2DP:音乐传输的“高速公路”

A2DP定义了音频流的编码、传输和解码流程。常见的编码格式有SBC、AAC、aptX、LDAC。在TWS耳机里,SBC是强制支持的,AAC在iOS设备上更常见。

我记得有一次,客户反馈耳机播放音乐时断断续续。排查后发现,是A2DP的编码比特率设得太高,蓝牙带宽扛不住了。后来把比特率从328kbps降到256kbps,问题解决。说白了,A2DP的配置要跟实际射频环境匹配,别一味追求高音质。

HFP:通话的“生命线”

HFP负责处理电话的音频通道。它使用SCO或eSCO链路,保证低延迟。在TWS里,HFP的难点在于双耳同步——左右耳必须同时收到通话语音,否则会有回声或延迟感。

我曾经在调试HFP时,发现左耳能听到声音,右耳却静音。查了半天,原来是HFP的音频路由配置错了,只把SCO数据发给了左声道。嗯,这种问题,配置表里一行代码的事,但找起来真要命。

AVRCP:遥控器的“灵魂”

AVRCP让你能用耳机控制手机的音乐播放。它分两个版本:AVRCP 1.4(基础控制)和AVRCP 1.6(带媒体信息同步)。

在TWS里,AVRCP通常走BLE通道(AVRCP over BLE),而不是经典蓝牙。为什么?因为BLE功耗低,而且控制命令的数据量很小,没必要占用经典蓝牙的带宽。

音频协议对比:

协议 用途 传输通道 关键参数
A2DP 音乐播放 ACL(异步无连接) 编码格式、比特率、采样率
HFP 通话 SCO/eSCO(同步连接) 语音编码(CVSD、mSBC)
AVRCP 远程控制 ACL或BLE 命令集(播放、暂停、音量)

重要提醒:这三个协议不是独立的,它们会互相影响。比如,你在听音乐时(A2DP),突然来了电话(HFP),系统需要暂停A2DP流,切换到HFP。如果切换逻辑没写好,就会出现“音乐还在放,电话接不起来”的尴尬。我见过不少TWS耳机栽在这个坑里。

好了,这一章的内容就到这里。蓝牙协议栈的基础,说白了就是理解“经典蓝牙管音频,低功耗蓝牙管控制”这个核心思路。HCI是它们沟通的桥梁,GATT/ATT是BLE的数据结构,A2DP/HFP/AVRCP是音频应用的三驾马车。

下一章,我们会深入TWS的核心——左右耳同步与主从切换。到时候,你会发现,今天这些基础,全都会用上。