第4章 蓝牙CRC参数规范:多项式0x1021、初始值0xFFFF、异或值0x0000、输入输出反转规则

蓝牙基带层采用CRC-CCITT的定制变体,其参数并非标准CRC-CCITT的默认值,而是针对蓝牙无线传输特性做了专门适配。本章逐项拆解多项式0x1021、初始值0xFFFF、异或值0x0000以及输入/输出反转规则,说明每个参数在蓝牙协议栈中的物理含义与配置方法。

4.1 四参数总览

蓝牙CRC参数由四个核心字段唯一确定。下表列出蓝牙协议栈采用的参数及其与标准CRC-CCITT的对比:

参数 蓝牙值 标准CRC-CCITT值 差异说明
多项式 (Poly) 0x1021 0x1021 相同,均为生成多项式 x¹⁶ + x¹² + x⁵ + 1
初始值 (Init) 0xFFFF 0xFFFF 相同,全1初始值
异或值 (XorOut) 0x0000 0x0000 相同,不对结果做额外掩码
输入反转 (RefIn) TRUE TRUE 相同,字节内LSB先入
输出反转 (RefOut) TRUE TRUE 相同,结果比特顺序反转
关键共识:蓝牙CRC参数与标准CRC-CCITT(全称为CRC-16-CCITT,参数模型0x1021/0xFFFF/0x0000/true/true)完全一致。这意味着任何标准CRC-CCITT硬件加速单元或软件库均可直接用于蓝牙数据包校验,无需额外参数适配。

4.2 多项式0x1021的位级含义

多项式0x1021对应二进制 1 0001 0000 0010 0001,去掉最高位后为 0001 0000 0010 0001。该多项式是16位CRC中经过理论验证的汉明距离最大化选择之一,在蓝牙2.4GHz ISM频段下对≤3比特的突发错误具有100%检测能力。

// 多项式除法中的移位掩码定义(C语言风格)
#define CRC_POLY 0x1021  // x^16 + x^12 + x^5 + 1

// 硬件实现中,多项式通过移位寄存器抽头位置体现
// 抽头位置: bit12, bit5, bit0 (bit16隐式)
实践要点:在软件实现中,0x1021通常以左对齐形式参与循环冗余计算,即 0x1021 << 0 或直接作为16位掩码。部分低级驱动会将多项式按反射形式存储为0x8408,用于LSB优先的移位算法,两者等价但字节序不同。

4.3 初始值0xFFFF与异或值0x0000的作用

初始值0xFFFF将CRC移位寄存器预置为全1状态,确保数据前面的连续0不会被漏检。如果初始值为0x0000,则数据流前导的0字节不会改变寄存器状态,导致错误隐藏。

异或值0x0000意味着CRC结果在输出前不做任何掩码或求反操作。这一选择简化了蓝牙基带控制器的硬件设计——最终CRC结果直接附加在数据包尾部,接收端比较时无需额外修正。

// 初始化寄存器(伪代码)
uint16_t crc = 0xFFFF;  // 初始值

// 逐字节处理完成后,不进行额外异或
// 最终结果直接使用:
uint16_t final_crc = crc;  // XorOut = 0x0000

4.4 输入/输出反转规则详解

输入反转 (RefIn = TRUE) 表示每个数据字节在进入CRC计算器之前,其比特顺序被反转——最低有效位(LSB)被当作最高有效位(MSB)处理。这符合蓝牙空中接口的小端字节序传输惯例:数据字节在无线帧中总是LSB优先发送。

输出反转 (RefOut = TRUE) 表示CRC计算完成后,寄存器结果的比特顺序同样被反转,然后才附加到数据包尾部。反转后的CRC值与接收端预期的小端序校验值对齐。

// 输入反转示例:字节 0xB1 (1011 0001)
// 反转前:b7 b6 b5 b4 b3 b2 b1 b0 = 1 0 1 1 0 0 0 1
// 反转后:b0 b1 b2 b3 b4 b5 b6 b7 = 1 0 0 0 1 1 0 1 = 0x8D

// 标准反转宏(C语言)
#define REFLECT_BYTE(b)  (uint8_t)(((b) * 0x0202020202ULL & 0x010884422010ULL) % 1023)
常见配置陷阱:如果软件实现时遗漏了输出反转步骤,生成的CRC将与蓝牙协议栈期望的值相差一个比特序维度,导致接收端CRC校验持续失败。务必确认底层CRC库的 ref_out 参数设置为 true。

4.5 参数配置实战:crcmod与BlueZ API

在Python原型验证或测试工具中,使用 crcmod 库配置蓝牙CRC参数:

import crcmod

# 蓝牙CRC-CCITT参数:poly=0x1021, init=0xFFFF, xor=0x0000, rev_in=True, rev_out=True
bluetooth_crc = crcmod.mkCrcFun(
    0x11021,      # 多项式(含隐式最高位)
    initCrc=0xFFFF,
    xorOut=0x0000,
    rev=True      # 同时启用输入反转和输出反转
)

# 验证:对空数据计算CRC
test_crc = bluetooth_crc(b'')
print(f"Empty CRC: 0x{test_crc:04X}")  # 应输出 0x1D0F

在BlueZ蓝牙协议栈的HCI层,CRC参数由固件直接管理,主机仅通过HCI命令使能或禁用CRC。常见配置命令如下:

# 启用蓝牙基带CRC(通常默认开启)
hciconfig hci0 encap 3 0x05 0x01

# 读取当前CRC状态(通过HCI Read Packet Boundary Flag)
hcitool cmd 0x03 0x05

# 注意:蓝牙规范中CRC参数为固定值,主机不可修改多项式或初始值

4.6 蓝牙CRC计算完整流程

下图展示了蓝牙CRC从数据输入到最终校验值的完整计算链路,清晰标出每个参数的作用位置:

蓝牙CRC参数配置与计算流程 蓝牙CRC-CCITT 参数配置与计算流程 原始数据包 输入反转 (RefIn) LSB → MSB 字节内重排 CRC 计算引擎 Poly=0x1021, Init=0xFFFF 多项式除法 + 移位寄存器 初始值 0xFFFF 输出反转 (RefOut) 寄存器比特序反转 异或输出 (XorOut) XOR 0x0000 (无操作) ⬇ 接收端:使用相同参数重新计算CRC,与附加在包尾的CRC值比较 ⬇ 最终 CRC 值 16-bit 校验码 CRC 校验判决 匹配 ✓ 不匹配 ✗ 参数固定不可修改 蓝牙规范规定CRC参数为固定值,主机软件不能更改多项式、初始值、反转规则。所有蓝牙设备必须使用同一套参数。

4.7 常见参数配置错误与验证

  1. 多项式字节序选用错误:软件实现时误将0x1021按反射形式写为0x8408,但算法未配套使用LSB优先移位,导致结果完全不匹配。统一使用标准CRC-CCITT库可避免此问题。
  2. 输出反转遗漏:接收端计算CRC后直接与原值比较,未做RefOut,导致校验失败。确认库参数 rev_out=True。
  3. 初始值误用为0x0000:部分裁剪版实现为“简化”将Init设为0,导致前导零字节被漏检。严格按照蓝牙规范使用0xFFFF。
  4. 异或值误配为0xFFFF:如果使用了其他协议的CRC配置(如Modbus的XorOut=0xFFFF),结果会按位取反,与蓝牙不兼容。
快速自检方法:使用空数据串 b'' 计算CRC,预期输出应为 0x1D0F(RefIn=true, RefOut=true, Init=0xFFFF, XorOut=0x0000)。如果结果不同,则参数配置存在偏差。

4.8 本章小结