第2章:蓝牙协议栈架构:Host与Controller的划分、HCI接口的作用、L2CAP层详解、RFCOMM协议、SDP服务发现协议
好,咱们直接切入正题。蓝牙协议栈,说白了就是一套通信规则的总和。很多初学者一上来就被那几十层协议吓住了,其实没那么复杂。我个人习惯把蓝牙协议栈想象成一个「快递系统」——有分拣中心、有运输车队、有配送员。今天这一章,咱们就把这个系统的骨架搭起来。
2.1 Host与Controller的划分:谁负责动脑,谁负责动手?
蓝牙协议栈最核心的分界线,就是Host和Controller。你想想看,一个蓝牙设备既要处理复杂的逻辑,又要实时收发无线信号,如果全揉在一起,代码维护起来简直是噩梦。
Controller,通常是一个独立的芯片(比如CSR、TI的CC系列),它负责最底层的活儿:
- 射频收发、跳频、调制解调
- 基带处理、链路控制
- 物理层的时序控制
Host,则跑在主处理器上(比如你的手机AP、Linux主机),它负责:
- 逻辑链路控制(L2CAP)
- 服务发现(SDP)
- 应用层协议(RFCOMM、ATT等)
- Profile的管理
关键点:Host和Controller可以跑在同一个芯片上,也可以分开。比如经典的蓝牙耳机方案,Controller是独立的蓝牙芯片,Host跑在耳机的主控MCU上。而像一些低成本的蓝牙模块,两者就集成在一起了。
我在项目中遇到过一种情况:某款智能手表,Host和Controller跑在不同的核上,结果因为HCI通信的延迟没处理好,导致音频断断续续。嗯,这里要注意——Host和Controller的划分,不仅仅是逻辑上的,更是物理上的性能权衡。
2.2 HCI接口的作用:Host和Controller之间的「翻译官」
Host和Controller之间怎么通信?靠的就是HCI(Host Controller Interface)。说白了,HCI就是一套标准化的命令和事件格式。
HCI支持三种物理传输方式:
| 传输方式 | 典型场景 | 特点 |
|---|---|---|
| UART | 经典蓝牙模块(如HC-05) | 简单、低成本、速度一般 |
| USB | PC蓝牙适配器 | 高速、即插即用 |
| SDIO | 嵌入式WiFi+蓝牙 combo芯片 | 高带宽、适合大数据量 |
HCI的数据包分为四种类型:
- 命令包:Host发给Controller,比如「开始扫描」
- 事件包:Controller回复Host,比如「扫描到设备了」
- 数据包:ACL数据(普通数据)和SCO数据(音频数据)
- 同步数据包:用于语音等实时流
避坑指南:我曾经调试过一个项目,蓝牙一直连不上。查了两天,最后发现是HCI命令包的参数长度填错了。HCI命令的格式非常严格,多一个字节或少一个字节,Controller直接不认。所以写HCI驱动时,一定要对照Spec逐字节核对。
2.3 L2CAP层详解:蓝牙的「数据管道」
L2CAP(Logical Link Control and Adaptation Protocol),名字很长,但作用很简单——把上层的数据切成合适大小的包,然后通过Controller发出去。
L2CAP的核心功能有四个:
- 协议复用:多个上层协议(RFCOMM、SDP、ATT)共用同一条ACL链路,L2CAP通过CID(Channel Identifier)来区分。
- 分段与重组:上层发来的大包,L2CAP会切成MTU大小的段;接收端再拼回去。
- 流量控制:通过Credit机制,防止发送方太快把接收方撑爆。
- 错误重传:在BR/EDR中,L2CAP支持可选的重传模式。
你想想看,如果没有L2CAP,上层协议就得自己处理分包和复用,那得多乱?
MTU(最大传输单元)是L2CAP最重要的参数。经典蓝牙的默认MTU是672字节,但实际应用中,我建议根据你的数据量来调整。比如传音频流,MTU设大一点可以减少包数量;传控制命令,MTU设小一点可以降低延迟。
L2CAP的通信流程大致是这样的:
// 伪代码示例:L2CAP连接建立
1. Host发送L2CAP_Connection_Req (CID = 0x0040)
2. Controller回复L2CAP_Connection_Rsp (结果=成功, CID = 0x0041)
3. 双方协商MTU大小
4. 开始数据传输
我记得有一次,客户反映他们的蓝牙键盘偶尔会丢键。查到最后,发现是L2CAP层的Credit用完了,但发送方没有及时等待Credit更新。说白了,就是流量控制没做好。
2.4 RFCOMM协议:蓝牙上的「虚拟串口」
RFCOMM,全称是Radio Frequency Communication。它的设计初衷很朴素——让蓝牙能模拟串口通信。为什么需要这个?因为很多老设备(比如GPS模块、打印机)都是用串口通信的,RFCOMM让它们可以无缝迁移到蓝牙上。
RFCOMM基于L2CAP,它定义了一套帧格式,包括:
- SABM/UA/DISC:链路建立和释放
- UIH帧:携带数据,支持多路复用
- 流控帧:控制数据发送节奏
RFCOMM支持最多30个虚拟串口(通道号从1到30)。每个虚拟串口对应一个应用,比如通道1给蓝牙键盘,通道2给蓝牙鼠标。
注意:RFCOMM本身不提供加密和认证。如果你传输敏感数据,必须在RFCOMM之上再加一层加密(比如通过SPP Profile的配置)。我曾经见过一个项目,用RFCOMM传支付数据,结果被中间人攻击了——嗯,那场面挺尴尬的。
RFCOMM的典型应用场景:
- 蓝牙串口适配器(SPP Profile)
- 蓝牙打印机
- 蓝牙工业数据采集
2.5 SDP服务发现协议:蓝牙设备的「自我介绍」
两台蓝牙设备连接后,怎么知道对方支持什么服务?靠的就是SDP(Service Discovery Protocol)。
SDP的工作方式很简单:
- 设备A广播自己的服务列表(比如「我支持耳机服务、文件传输服务」)
- 设备B发起SDP查询(比如「你支持耳机服务吗?」)
- 设备A返回服务属性(比如「支持,我的音频编码是SBC」)
SDP的核心数据结构是服务记录,包含:
| 属性 | 说明 | 示例值 |
|---|---|---|
| ServiceClassIDList | 服务类别 | 0x1108(Headset) |
| ProtocolDescriptorList | 协议栈信息 | L2CAP + RFCOMM |
| ServiceName | 服务名称 | "My Bluetooth Headset" |
个人经验:调试SDP时,我最常用的工具是hcitool和sdptool。比如用sdptool browse XX:XX:XX:XX:XX:XX就能看到对方设备的所有服务。有一次我发现某个设备返回的服务记录里,协议描述符写错了,导致连接一直失败。所以,SDP的解析代码一定要写得健壮,因为不是所有设备都严格遵守规范。
SDP还有一个特点:它是无连接的。也就是说,查询服务时临时建立L2CAP连接,查完就断开。这样设计是为了节省资源,毕竟服务发现只是连接前的「打招呼」环节。
小结
这一章咱们把蓝牙协议栈的骨架讲清楚了:
- Host和Controller:分工明确,一个动脑一个动手
- HCI:两者之间的通信桥梁,格式必须严格
- L2CAP:数据管道,负责分包和复用
- RFCOMM:虚拟串口,让老设备也能用蓝牙
- SDP:服务发现,让设备互相认识
下一章,咱们会深入BLE协议栈,看看它和经典蓝牙有什么不同。嗯,到时候你会发现,BLE的架构更简洁,但坑也不少。
一句话总结:蓝牙协议栈就像一座大楼,Host是楼上的办公室,Controller是楼下的机房,HCI是电梯,L2CAP是走廊,RFCOMM是电话线,SDP是前台。理解了这些,你就掌握了蓝牙开发的「地图」。