第三章:HCI传输层详解(UART)

好,咱们今天来聊聊HCI传输层里最经典、也最常用的一种——UART。说白了,就是通过串口把蓝牙芯片和主机连起来。很多刚入行的朋友觉得UART简单,不就是TX、RX两根线嘛?嗯,话是这么说,但实际调起来,坑还真不少。我当年第一次调H4协议时,就被波特率匹配和流控折腾得够呛。

3.1 UART硬件接口

先看硬件。UART接口最少需要三根线:TX(发送)、RX(接收)、GND(地)。但做蓝牙HCI,我强烈建议你至少再加两根流控线——CTS和RTS。为什么?后面讲流控时你就明白了。

实际项目中,我见过不少工程师为了省一根线,把CTS/RTS直接接地或悬空。结果呢?数据量一大就开始丢包。嗯,这里要注意:HCI UART传输对时序要求很高,硬件上别偷懒

标准HCI UART引脚定义:

  • TX —— 主机发送,蓝牙芯片接收
  • RX —— 主机接收,蓝牙芯片发送
  • RTS —— 主机请求发送(流控用)
  • CTS —— 主机清除发送(流控用)
  • GND —— 共地

3.2 波特率配置

波特率,就是每秒传输的比特数。蓝牙HCI最常用的是115200 bps,但很多高性能模块支持到921600甚至更高。我个人习惯:开发调试阶段用115200,稳定后再切高速率

为什么?因为波特率越高,对时钟精度要求越苛刻。我曾经在一个项目里,直接把波特率设成4Mbps,结果发现蓝牙芯片和主控的晶振偏差加起来超过2%,数据全乱套了。后来老老实实降到921600,加了个硬件流控,问题才解决。

避坑指南: 我曾经遇到过波特率配置正确,但通信就是不稳定。查了半天,发现是UART的FIFO深度没配置对。很多MCU的UART FIFO默认是16字节,如果你一次发超过16字节的HCI包,又不做流控,很容易溢出。建议把FIFO触发阈值设到一半以下。

配置波特率时,一般通过HCI命令来实现。比如TI的CC256x系列,发送一个VS(厂商特定)命令就能改。但要注意:改完波特率后,主机和蓝牙芯片要同步切换,否则两边速率不匹配,立刻断连。

3.3 HCI UART传输协议(H4)

H4协议,是蓝牙规范里定义的UART传输层协议。你想想看,UART本身只负责传字节流,它不知道哪个字节是命令、哪个是事件、哪个是数据。H4就是来解决这个问题的——它给每个包加了个“类型标签”。

H4协议的核心思想很简单:每个HCI包前面加一个字节的“类型指示符”。接收方看到这个字节,就知道后面跟着的是什么类型的包。

H4类型指示符定义:

类型值 含义
0x01 HCI Command(命令包)
0x02 HCI ACL Data(ACL数据包)
0x03 HCI Synchronous Data(同步数据包,如语音)
0x04 HCI Event(事件包)

举个例子:主机要发送一个HCI命令,它先发0x01,然后紧跟着命令包的内容。蓝牙芯片收到0x01,就知道“哦,这是个命令”,然后按命令包的格式解析。反过来,蓝牙芯片回复事件时,先发0x04,再发事件内容。

3.4 H4数据包格式

H4本身只定义了那个类型字节,真正的数据包格式还是HCI规范里的那套。我简单列一下,方便你对照着看代码。

HCI Command Packet(命令包):

字节0: H4类型 (0x01)
字节1-2: OpCode(操作码,2字节)
字节3: Parameter Total Length(参数总长度)
字节4...: 参数内容

HCI Event Packet(事件包):

字节0: H4类型 (0x04)
字节1: Event Code(事件码)
字节2: Parameter Total Length(参数总长度)
字节3...: 参数内容

HCI ACL Data Packet(ACL数据包):

字节0: H4类型 (0x02)
字节1-2: Connection Handle + Flags(连接句柄和标志)
字节3-4: Data Total Length(数据总长度)
字节5...: 数据内容

注意: H4类型字节本身不参与校验。我曾经见过有人把类型字节也算进CRC里,结果两边对不上。记住,类型字节只是“信封”,里面的“信”才是HCI包。

3.5 流控机制

流控,说白了就是防止数据“撑爆”接收缓冲区。UART本身没有流控能力,所以H4协议里定义了一套软件流控机制——HCI UART Transport Flow Control

这套机制基于两个特殊字节:

  • XON (0x11):告诉对方“我可以继续接收了”
  • XOFF (0x13):告诉对方“我快满了,先停一下”

实际项目中,我更推荐用硬件流控(CTS/RTS)。为什么?软件流控有个致命问题:XON/XOFF字节本身也是数据。如果数据流里恰好出现了0x11或0x13,就会误触发流控。虽然H4协议规定这些字节要转义,但实现起来麻烦,而且容易出错。

我的经验: 在低功耗蓝牙项目中,我一般这样配置流控:

  • 开发阶段:硬件流控 + 115200波特率
  • 量产阶段:硬件流控 + 921600波特率
  • 如果硬件不支持流控:软件流控 + 转义处理

记住:没有流控的HCI UART,就像没有红绿灯的十字路口——迟早要出事故。

最后说一句,H4协议还有一个变种叫H5(三线UART),它用SLIP协议封装数据,支持软件流控和重传。但H5比较复杂,一般用在只有TX/RX/GND三根线的场景。如果你硬件上能拉出CTS/RTS,直接用H4+硬件流控,省心得多。

嗯,关于HCI UART传输层,核心就是这些。下一章咱们聊聊HCI命令的发送与接收,到时候我会拿一个实际的蓝牙扫描命令做例子,手把手带你走一遍流程。