第3章:BLE协议栈之HCI——主机与控制器的“翻译官”
HCI,全称Host Controller Interface,中文叫主机控制器接口。
说白了,它就是BLE协议栈里,主机(Host)和控制器(Controller)之间通信的那条“电话线”。
我刚开始接触BLE时,总觉得HCI是个可有可无的中间层。后来被一个诡异的bug折磨了三天,才发现是HCI命令的Payload长度填错了。嗯,从那以后,我再也不敢小看它了。
一句话总结:HCI定义了Host如何给Controller下命令,Controller如何给Host回事件,以及两边如何传数据。
3.1 HCI接口定义——物理与逻辑的双重约定
HCI接口分两层:物理接口和逻辑接口。
物理接口,就是Host和Controller之间实际走的那根线。双芯片方案里,最常见的是UART、USB、SDIO。我做过一个项目,UART的波特率配错了,HCI命令发出去全是乱码,折腾了两天才发现是波特率不匹配。
逻辑接口,就是数据包的格式和交互流程。不管物理层用UART还是USB,逻辑包的结构是一样的。这就是为什么同一套Host代码,换个Controller芯片,只要改个驱动层就能跑。
我的习惯:做双芯片方案时,先把HCI的UART波特率固定为115200或1M,调试阶段不要用自动波特率检测,少踩很多坑。
3.2 HCI数据包格式——四种包,各司其职
HCI数据包一共四种类型。你想想看,就像快递公司有四种面单:
| 包类型 | 方向 | 用途 | Packet Indicator |
|---|---|---|---|
| HCI Command | Host → Controller | 下发指令(扫描、连接、广播等) | 0x01 |
| HCI Event | Controller → Host | 上报状态、结果、异步通知 | 0x04 |
| HCI ACL Data | 双向 | 传输用户数据(ATT payload) | 0x02 |
| HCI ISO Data | 双向 | 传输等时数据(LE Audio) | 0x05 |
每个包开头都有一个Packet Indicator(1字节),告诉接收方“我是什么类型的包”。
3.2.1 HCI Command Packet
格式很简单:
| Packet Indicator (1B) | OpCode (2B) | Parameter Total Length (1B) | Parameters (0-255B) |
| 0x01 | 0xXXXX | N | N bytes |
OpCode又分两部分:OGF(OpCode Group Field,高6位)和OCF(OpCode Command Field,低10位)。
OGF用来分组,比如链路控制命令、链路策略命令、状态参数命令等。OCF是组内具体的命令编号。
举个例子:LE Set Scan Enable命令的OpCode是0x200C。OGF=0x08(LE Controller命令组),OCF=0x000C。
3.2.2 HCI Event Packet
Controller主动上报时用Event包:
| Packet Indicator (1B) | Event Code (1B) | Parameter Total Length (1B) | Parameters (0-255B) |
| 0x04 | 0xXX | N | N bytes |
最常见的Event Code是0x0E(Command Complete),表示命令执行完毕。还有一个0x0F(Command Status),表示命令已被接收但还没执行完。
我曾经犯过一个错:以为所有命令都会回Command Complete。结果发了个LE Create Connection命令,等了半天没收到Complete,后来才发现连接建立成功后回的是LE Connection Complete(0x0E的子事件),不是普通的Command Complete。
注意:异步命令(如连接、扫描)不会回Command Complete,而是回Command Status + 对应的异步事件。别傻等Complete。
3.2.3 HCI ACL Data Packet
这个包承载的是真正的用户数据,比如你通过GATT读到的传感器值:
| Packet Indicator (1B) | Handle (2B) | PB Flag (2bit) | BC Flag (2bit) | Data Total Length (2B) | Data |
| 0x02 | 0xXXXX | 2 bits | 2 bits | N | N B |
Handle是Connection Handle,标识是哪条连接的数据。PB Flag用来做L2CAP层的分包重组。
3.2.4 HCI ISO Data Packet
这是BLE Audio引入的新包型,用于等时通道。格式和ACL类似,但多了ISO Interval等字段。目前用得不多,但未来会是主流。
3.3 HCI命令与事件——一问一答,也有异步
HCI的交互模式分两种:同步和异步。
- 同步模式:Host发一个命令,Controller回一个Command Complete。比如设置广播参数、读取本地版本。
- 异步模式:Host发一个命令,Controller先回Command Status表示“收到,正在处理”,等处理完了再回一个异步事件。比如启动扫描、发起连接。
我建议你把所有命令的同步/异步属性整理成一张表,贴在工位上。不然调试时很容易搞混。
| 命令示例 | OpCode | 响应方式 |
|---|---|---|
| LE Set Scan Parameters | 0x200B | Command Complete |
| LE Set Scan Enable | 0x200C | Command Complete |
| LE Create Connection | 0x200D | Command Status → LE Connection Complete |
| LE Read Local Version | 0x2001 | Command Complete |
避坑指南:我曾经在发LE Create Connection后,立即又发了LE Set Scan Enable,结果连接直接失败。因为Controller还在处理连接,你突然让它去扫描,它懵了。正确的做法是:等连接事件到了,再发下一个命令。
3.4 HCI在双芯片与单芯片方案中的角色
这个知识点,说白了就是“分家”和“合住”的区别。
3.4.1 双芯片方案
Host(主MCU)跑应用和协议栈上层,Controller(BLE芯片)跑链路层和物理层。两者通过UART/USB/SPI连接,HCI包就在这条物理链路上传输。
优点很明显:
- 主MCU选型灵活,不用自带BLE
- BLE芯片可以选性能更好的,比如Nordic、TI的专用芯片
- 协议栈升级方便,只换BLE芯片的固件就行
缺点也实在:
- 多一颗芯片,成本高、PCB面积大
- HCI通信有延迟,UART波特率不够高时,吞吐量受限
- 功耗优化更复杂,两边要协调休眠唤醒
我做过一个双芯片的穿戴设备,主MCU是STM32,BLE芯片用DA14531。UART波特率配到1M,HCI通信才勉强满足数据吞吐。如果当时选921600,可能就翻车了。
3.4.2 单芯片方案
Host和Controller在同一颗芯片内。比如Nordic nRF52840、TI CC2640、ESP32。HCI不再走物理总线,而是通过内部函数调用或共享内存交互。
优点:
- 成本低、体积小
- 延迟低,没有串口瓶颈
- 功耗好优化,整个协议栈在一个芯片内
缺点:
- 芯片选型受限,必须自带BLE
- 协议栈通常由芯片厂商提供,定制空间小
- 如果Host任务重,可能会挤占Controller的实时性
我的建议:产品对成本敏感、BLE吞吐要求不高,选单芯片。产品需要高性能主控、复杂外设、或者BLE吞吐要求高(比如OTA升级),选双芯片。
3.4.3 两种方案的HCI视角差异
| 维度 | 双芯片 | 单芯片 |
|---|---|---|
| HCI物理层 | UART/USB/SPI | 内部函数调用/共享内存 |
| HCI延迟 | 毫秒级(取决于波特率) | 微秒级 |
| HCI包可见性 | 可以用逻辑分析仪抓包 | 通常不可见,需厂商日志 |
| 调试难度 | 中等(能看到HCI包) | 较高(HCI包被封装了) |
| 典型场景 | 高性能穿戴、网关 | 传感器、Beacon、简单外设 |
嗯,这里要注意:单芯片方案虽然HCI不可见,但大部分厂商SDK都提供了HCI日志打印功能。调试时打开它,能看到Host和Controller之间的命令交互,对排查问题非常有帮助。
警告:双芯片方案中,HCI的UART RX/TX必须加流控(RTS/CTS)。我见过一个团队没加流控,高速数据传输时频繁丢包,查了两个月才发现是硬件流控没接。
本章小结
HCI是BLE协议栈的“腰”,腰不好,全身都难受。
- 四种包类型:Command、Event、ACL Data、ISO Data,每种都有自己的Packet Indicator和格式
- 命令交互分同步和异步,别搞混Command Complete和Command Status
- 双芯片方案HCI走物理总线,单芯片方案HCI走内部调用,逻辑接口完全一致
- 调试时,能抓到HCI包就等于拿到了“上帝视角”