第1章:HCI协议详解:指令、事件、数据的格式与传输机制

各位同学,咱们今天聊聊HCI。说实话,很多做蓝牙开发的朋友,一听到HCI就觉得头大。我当年刚入行时也一样,总觉得这玩意儿就是个黑盒子。但后来做了几个车载项目,踩了不少坑,才真正搞明白——HCI其实就是蓝牙芯片和主机之间的“翻译官”。

1.1 HCI到底是什么?

HCI,全称是Host Controller Interface。说白了,就是蓝牙协议栈里,主机(Host)和控制器(Controller)之间的通信接口。

你想想看,蓝牙芯片内部有两部分:一个是跑协议栈的Host,一个是负责射频收发的Controller。这两者怎么沟通?就是靠HCI。HCI定义了它们之间怎么发指令、怎么回事件、怎么传数据。

核心要点:HCI不是协议,是接口。它规定了格式,不规定具体怎么实现。

1.2 HCI的三种“语言”

HCI通信分三种类型:指令、事件、数据。我习惯把它们比作“命令-回复-干活”的过程。

1.2.1 HCI指令(Command)

指令是主机发给控制器的。比如“你开始扫描吧”、“你连那个设备”。每条指令都有固定的格式:

OpCode (2字节) | 参数总长度 (1字节) | 参数 (可变长度)

OpCode又分两部分:OGF(OpCode Group Field,高6位)和OCF(OpCode Command Field,低10位)。OGF决定指令属于哪个组,比如扫描、连接、配对等。OCF决定具体是哪个指令。

我记得第一次写HCI指令时,把OGF和OCF搞反了,结果芯片一直没反应。查了半天才发现是字节序的问题。嗯,这里要注意:蓝牙用的是小端序。

1.2.2 HCI事件(Event)

事件是控制器回复主机的。比如“扫描到设备了”、“连接成功了”。事件格式也很固定:

事件码 (1字节) | 参数总长度 (1字节) | 参数 (可变长度)

最常见的几个事件码:

  • 0x0E:指令完成事件(Command Complete)—— 指令执行完了,这是最常用的
  • 0x0F:指令状态事件(Command Status)—— 指令开始执行了,但还没完
  • 0x3E:LE元事件(LE Meta Event)—— LE相关的各种事件都包在这里

我在项目中遇到过一个问题:发了一个连接指令,等了半天没收到完成事件。后来发现是忘了处理指令状态事件。有些指令会先回状态事件,再回完成事件。你如果只等完成事件,就会一直卡着。

1.2.3 HCI数据(Data)

数据就是真正要传的业务数据了。分两种:ACL数据(用于普通数据传输)和SCO数据(用于语音传输)。

ACL数据的格式:

句柄 (2字节) | 数据总长度 (2字节) | 数据 (可变长度)

句柄里还藏着一些标志位,比如PB标志(Packet Boundary Flag)和BC标志(Broadcast Flag)。PB标志告诉你这个包是完整包还是分段包。我刚开始做车载蓝牙时,没处理好分段包,结果音频数据一直断断续续的。

1.3 传输机制:UART vs USB

HCI的传输方式主要有两种:UART和USB。车载音响里,UART用得更多一些。为什么?因为便宜、简单、够用。

对比项 UART USB
硬件成本
传输速率 一般(最高4Mbps) 高(USB 2.0可达480Mbps)
协议复杂度 简单 复杂
适用场景 车载、嵌入式 PC、手机

1.3.1 UART传输

UART传输HCI时,用的是H4协议。说白了,就是在每个包前面加一个字节的“类型指示”:

  • 0x01:HCI指令包
  • 0x02:HCI ACL数据包
  • 0x03:HCI SCO数据包
  • 0x04:HCI事件包

我曾经在一个项目中,UART通信老是丢包。查来查去,发现是波特率不匹配。主机设的115200,控制器设的9600。你说这能通吗?

个人建议:车载环境里,UART波特率建议用921600或更高。115200在数据量大时容易丢包。

1.3.2 USB传输

USB传输HCI时,用的是H2协议。USB本身有复杂的枚举、描述符、端点等概念。HCI数据通过USB的Bulk端点传输,事件通过Interrupt端点传输。

USB的好处是速度快、可靠性高。但坏处是协议栈复杂,调试起来麻烦。我记得有一次USB枚举总是失败,最后发现是描述符里的字符串长度写错了。

注意:USB传输时,HCI数据包的最大长度受USB端点大小限制。一般Bulk端点是64字节,Interrupt端点是16字节。超过这个长度就要分包。

1.4 实际项目中的避坑指南

做车载蓝牙HCI开发,有几个坑我踩过,分享给大家:

  1. 字节序问题:蓝牙用的小端序,但有些MCU是大端序。记得做转换。
  2. 超时处理:发完指令后,一定要设超时。我曾经遇到过控制器死机,主机一直等事件,整个系统卡死。
  3. 事件过滤:HCI事件很多,不要每个都处理。只处理你关心的,其他的忽略掉。
  4. 数据缓冲区:UART接收时,要用环形缓冲区。不然数据一多就丢。

嗯,今天就先讲到这里。HCI看起来简单,但真正用好它,需要很多实战经验。下一章我们聊聊HCI指令的具体实现,包括扫描、连接、配对这些常用操作。

有什么问题,欢迎随时交流。