第2章:HCI协议基础
好,咱们开始聊HCI协议。说实话,这是整个蓝牙驱动开发中最核心的一环。你想想看,Host和Controller之间怎么通信?全靠HCI这个桥梁。我当年刚接触蓝牙时,就是被HCI的各种包格式搞得晕头转向,后来踩了不少坑才摸清楚门道。
2.1 HCI数据包格式
HCI定义了四种数据包类型。每种包都有自己的使命,千万别搞混。我个人习惯在代码里用枚举来区分它们:
// HCI Packet Types
#define HCI_COMMAND_PKT 0x01
#define HCI_ACL_DATA_PKT 0x02
#define HCI_SCO_DATA_PKT 0x03
#define HCI_EVENT_PKT 0x04
嗯,这里要注意:包类型标识符是HCI传输层用来区分数据流的第一个字节。说白了,你收到一个字节0x01,就知道后面跟着的是命令包;收到0x04,那就是事件包。
核心要点:HCI数据包在Host和Controller之间传输时,第一个字节永远是包类型指示符。这是HCI传输层的约定,不能改。
2.2 HCI命令
HCI命令是Host发给Controller的指令。每个命令都有固定的格式:
typedef struct {
uint16_t opcode; // 操作码,高6位是OGF,低10位是OCF
uint8_t param_len; // 参数长度
uint8_t param[255]; // 参数内容
} hci_command_t;
操作码(Opcode)怎么理解?我打个比方:OGF就像是大类编号,比如"链路控制命令"是0x01,"链路策略命令"是0x02;OCF就是具体命令编号。两者组合起来,就能唯一定位一条命令。
举个例子,HCI_Reset命令的OGF=0x03,OCF=0x0003,组合起来opcode就是0x0C03。我在项目中调试时,经常用逻辑分析仪抓HCI总线,看到0x0C03就知道是复位命令。
个人经验:写驱动时,建议把常用命令的opcode定义成宏。我习惯这样写:
#define HCI_OP_RESET 0x0C03
#define HCI_OP_READ_BD_ADDR 0x1009
#define HCI_OP_LE_SET_SCAN_EN 0x200C
这样代码可读性高,也不容易写错。
2.3 HCI事件
事件是Controller主动发给Host的。说白了,就是Controller告诉你:"刚才那个命令执行完了"或者"有设备连上来了"。
事件包格式很简单:
typedef struct {
uint8_t event_code; // 事件码
uint8_t param_len; // 参数长度
uint8_t param[255]; // 参数内容
} hci_event_t;
最常见的几个事件码:
- 0x0E - Command Complete:命令执行完成,带返回参数
- 0x0F - Command Status:命令已接收,状态反馈
- 0x3E - LE Meta Event:LE相关的事件,比如连接完成、广告报告
我曾经踩过一个坑:以为所有命令都会返回Command Complete事件。其实有些命令只返回Command Status,比如那些需要长时间执行的命令。如果你在驱动里死等Command Complete,程序就卡死了。
避坑指南:我曾经在写HCI驱动时,没有区分Command Complete和Command Status,结果在发送LE Create Connection命令后,程序一直阻塞等待。后来查了三天才发现,这个命令只返回Command Status,真正的连接结果是通过LE Connection Complete事件通知的。
2.4 HCI数据流
HCI数据流分为两种:ACL数据和SCO数据。
ACL(Asynchronous Connection-Less)用于普通数据传输,比如你手机传文件给蓝牙音箱。SCO(Synchronous Connection-Oriented)用于实时语音数据,比如蓝牙耳机通话。
ACL数据包格式:
typedef struct {
uint16_t handle; // 连接句柄,含PB/BC标志
uint16_t data_len; // 数据长度
uint8_t data[0]; // 数据内容
} hci_acl_data_t;
这里有个细节:连接句柄的高4位被用来存放PB(Packet Boundary)和BC(Broadcast)标志。PB标志告诉Controller这个数据包是分片的第一包还是中间包。我刚开始写驱动时,没注意这个位域,结果传大文件时总是丢包。
数据流处理要点:
- ACL数据支持分片重组,大包会被拆成多个HCI包传输
- SCO数据是固定间隔的同步数据,不支持分片
- Host端需要维护一个重组缓冲区,把分片拼回完整L2CAP包
2.5 HCI传输层概述
HCI传输层,说白了就是Host和Controller之间物理传输的通道。常见的传输方式有:
| 传输类型 | 特点 | 典型应用 |
|---|---|---|
| UART | 简单、低成本、速率有限 | 经典蓝牙模块、低功耗嵌入式 |
| USB | 高速、即插即用、复杂 | 蓝牙适配器、PC蓝牙 |
| SDIO | 高速、适合WiFi+蓝牙组合 | 手机、平板 |
| SPI | 中等速率、低功耗 | IoT设备 |
我个人最常用的是UART传输。为什么?因为调试方便。你拿个串口工具就能看到HCI数据流。但UART有个坑:流控。如果不启用硬件流控(RTS/CTS),高速传输时很容易丢数据。
我记得有一次,客户反馈蓝牙连接不稳定。我抓了HCI日志一看,全是CRC错误。最后发现是UART波特率设得太高,又没有流控,导致Controller来不及处理数据。降到115200并启用硬件流控后,问题就解决了。
调试技巧:如果你用UART做HCI传输,建议在驱动层加一个超时重传机制。我曾经写过一个简单的HCI UART驱动,每次发送命令后启动一个定时器,如果500ms内没收到事件,就重发一次。这个机制帮我抓到了不少硬件问题。
嗯,HCI协议基础就聊这么多。下一章我们会深入HCI命令的具体实现,包括如何构造命令、解析事件、处理超时等。到时候我会分享更多实战中的踩坑经验。