第三章: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命令的发送与接收,到时候我会拿一个实际的蓝牙扫描命令做例子,手把手带你走一遍流程。