第二章 蓝牙协议栈基础: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的数据包分四种类型:
- 命令包:主机发给控制器,比如“开始扫描”、“建立连接”。
- 事件包:控制器回复主机,比如“扫描完成”、“连接成功”。
- 数据包:双向传输的实际数据,比如音频流。
- 同步数据包:用于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的核心——左右耳同步与主从切换。到时候,你会发现,今天这些基础,全都会用上。