第3章:BLE协议栈之HCI——主机与控制器的“翻译官”

HCI,全称Host Controller Interface,中文叫主机控制器接口。

说白了,它就是BLE协议栈里,主机(Host)和控制器(Controller)之间通信的那条“电话线”。

我刚开始接触BLE时,总觉得HCI是个可有可无的中间层。后来被一个诡异的bug折磨了三天,才发现是HCI命令的Payload长度填错了。嗯,从那以后,我再也不敢小看它了。

一句话总结:HCI定义了Host如何给Controller下命令,Controller如何给Host回事件,以及两边如何传数据。

HCI在BLE协议栈中的位置 Host(主机) GAP / GATT / ATT / SMP L2CAP HCI(主机控制器接口) 命令 / 事件 / 数据(ACL) Controller(控制器) Link Layer(链路层) PHY(物理层) 说明: • 双芯片方案:Host跑在主MCU上,Controller跑在BLE芯片上,HCI走UART/USB/SPI • 单芯片方案:Host和Controller在同一芯片内,HCI走内部API或共享内存 • 无论哪种方案,HCI的逻辑接口定义完全一致

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包就等于拿到了“上帝视角”