使用 Python 3 实现 CRC16 校验码的生成,主要涉及到位操作和对多项式的理解。下面我将提供一个通用的 CRC16 实现,并解释其中的关键概念。
CRC16 校验码生成原理
CRC(Cyclic Redundancy Check,循环冗余校验)是一种广泛使用的差错控制技术。CRC16 校验码的生成基于一个预设的生成多项式(Polynomial)。其基本思想是将要校验的数据看作一个二进制多项式,然后用生成多项式去除以这个数据多项式,得到的余数就是 CRC 校验码。
核心步骤:
- 初始化: CRC 寄存器通常初始化为 0x0000 或 0xFFFF,具体取决于所使用的 CRC16 变种。
- 数据处理: 每次处理一个字节的数据。
- 异或操作: 将当前字节与 CRC 寄存器的高位字节进行异或操作。
- 移位和条件异或: 将 CRC 寄存器左移一位。如果最高位(MSB)为 1,则将 CRC 寄存器与生成多项式进行异或操作。这个过程重复 8 次(对于每个字节的 8 位)。
- 循环: 对所有数据字节重复上述步骤。
- 结果处理: 最后得到的 CRC 寄存器中的值就是校验码。有些 CRC16 变种还会对最终结果进行异或或字节翻转。
常见的 CRC16 多项式和初始化值
CRC16 有多种变种,它们的主要区别在于使用的生成多项式、初始值、以及最终结果是否进行异或或字节翻转。以下是一些常见的 CRC16 变种:
CRC-16-CCITT (Kermit, X.25, V.41, IBM) (Most common):
- 多项式:
0x1021
- 初始值:
0x0000
或 0xFFFF
- XOR out:
0x0000
- Reflect In/Out: 通常是
False
/ False
或 True
/ True
(视具体实现而定)
CRC-16-MODBUS:
- 多项式:
0x8005
(反转后是 0xA001
) - 初始值:
0xFFFF
- XOR out:
0x0000
- Reflect In/Out:
True
/ True
CRC-16-ARC (or CRC-16-IBM):
- 多项式:
0x8005
- 初始值:
0x0000
- XOR out:
0x0000
- Reflect In/Out:
True
/ True
Python3 实现 CRC16
下面我将提供一个通用的 CRC16 实现,你可以通过修改 POLYNOMIAL
和 INITIAL_VALUE
来适应不同的 CRC16 变种。这个实现是基于位操作,模拟了硬件层面的 CRC 计算。
def crc16(data: bytes, polynomial: int = 0x8005, initial_value: int = 0xFFFF, reflect_in: bool = True, reflect_out: bool = True, xor_out: int = 0x0000) -> int:
"""
计算给定数据的 CRC16 校验码。
Args:
data: 要计算校验码的字节数据。
polynomial: CRC16 生成多项式。例如,0x8005 (CRC-16-MODBUS) 或 0x1021 (CRC-16-CCITT)。
initial_value: CRC 寄存器的初始值。例如,0xFFFF (CRC-16-MODBUS) 或 0x0000 (CRC-16-CCITT)。
reflect_in: 输入字节是否反转。
reflect_out: 最终结果是否反转。
xor_out: 最终结果是否与此值进行异或。
Returns:
计算出的 16 位 CRC 校验码。
"""
crc = initial_value
for byte in data:
if reflect_in:
byte = reflect_byte(byte) # 反转字节的位顺序
crc ^= (byte << 8) # 将当前字节与 CRC 寄存器的高位字节异或
for _ in range(8):
if (crc & 0x8000) != 0: # 如果最高位是 1
crc = (crc << 1) ^ polynomial
else:
crc = crc << 1
crc &= 0xFFFF # 确保 CRC 保持 16 位
if reflect_out:
crc = reflect_word(crc) # 反转最终 CRC 结果的位顺序
crc ^= xor_out
return crc
def reflect_byte(byte: int) -> int:
"""反转一个字节的位顺序。"""
reflected_byte = 0
for i in range(8):
if (byte >> i) & 1:
reflected_byte |= (1 << (7 - i))
return reflected_byte
def reflect_word(word: int) -> int:
"""反转一个 16 位字的位顺序。"""
reflected_word = 0
for i in range(16):
if (word >> i) & 1:
reflected_word |= (1 << (15 - i))
return reflected_word
# --- 示例用法 ---
if __name__ == "__main__":
# 示例 1: CRC-16-MODBUS
# 多项式: 0x8005, 初始值: 0xFFFF, Reflect In/Out: True, XOR Out: 0x0000
# 测试数据: 0x01 0x02 0x03 0x04 0x05 (ASCII 'abcde')
# 预期结果: 0x9B10 (根据在线工具验证)
data_modbus = b'\x01\x02\x03\x04\x05'
crc_modbus = crc16(data_modbus, polynomial=0x8005, initial_value=0xFFFF, reflect_in=True, reflect_out=True, xor_out=0x0000)
print(f"CRC-16-MODBUS for '{data_modbus.hex()}': 0x{crc_modbus:04X}")
# 示例 2: CRC-16-CCITT (初始值 0x0000, 不反转)
# 多项式: 0x1021, 初始值: 0x0000, Reflect In/Out: False, XOR Out: 0x0000
# 测试数据: b"123456789"
# 预期结果: 0x29B1 (根据在线工具验证)
data_ccitt_0000 = b"123456789"
crc_ccitt_0000 = crc16(data_ccitt_0000, polynomial=0x1021, initial_value=0x0000, reflect_in=False, reflect_out=False, xor_out=0x0000)
print(f"CRC-16-CCITT (init 0x0000) for '{data_ccitt_0000.decode()}': 0x{crc_ccitt_0000:04X}")
# 示例 3: CRC-16-CCITT (初始值 0xFFFF, 反转) ( often called CRC-16-X.25)
# 多项式: 0x1021, 初始值: 0xFFFF, Reflect In/Out: True, XOR Out: 0xFFFF
# 测试数据: b"123456789"
# 预期结果: 0x906E (根据在线工具验证)
data_ccitt_ffff_x25 = b"123456789"
crc_ccitt_ffff_x25 = crc16(data_ccitt_ffff_x25, polynomial=0x1021, initial_value=0xFFFF, reflect_in=True, reflect_out=True, xor_out=0xFFFF)
print(f"CRC-16-CCITT (init 0xFFFF, X.25) for '{data_ccitt_ffff_x25.decode()}': 0x{crc_ccitt_ffff_x25:04X}")
# 示例 4: CRC-16-ARC (or CRC-16-IBM)
# 多项式: 0x8005, 初始值: 0x0000, Reflect In/Out: True, XOR Out: 0x0000
# 测试数据: b"123456789"
# 预期结果: 0xBB3D (根据在线工具验证)
data_arc = b"123456789"
crc_arc = crc16(data_arc, polynomial=0x8005, initial_value=0x0000, reflect_in=True, reflect_out=True, xor_out=0x0000)
print(f"CRC-16-ARC for '{data_arc.decode()}': 0x{crc_arc:04X}")
代码解释
crc16(data, polynomial, initial_value, reflect_in, reflect_out, xor_out)
函数:
data
: 输入字节序列。polynomial
: CRC16 的生成多项式。initial_value
: CRC 寄存器的起始值。reflect_in
: 布尔值,指示每个输入字节的位是否需要反转。reflect_out
: 布尔值,指示最终的 CRC 结果的位是否需要反转。xor_out
: 最终 CRC 结果与此值进行异或。- 核心循环: 遍历输入数据的每个字节。
crc ^= (byte << 8)
: 将当前字节左移 8 位(使其与 CRC 寄存器的高位对齐),然后与 CRC 寄存器进行异或。- 内层循环(8 次): 模拟多项式除法的过程。
if (crc & 0x8000) != 0:
: 检查 CRC 寄存器的最高位(第 15 位)是否为 1。如果为 1,则表示当前多项式除法有余数。crc = (crc << 1) ^ polynomial
: 将 CRC 寄存器左移一位,并与生成多项式进行异或。else: crc = crc << 1
: 如果最高位为 0,则直接左移一位。crc &= 0xFFFF
: 确保 CRC 寄存器始终保持在 16 位。
- 反射和异或: 根据
reflect_out
和 xor_out
参数对最终的 CRC 结果进行处理。
reflect_byte(byte)
和 reflect_word(word)
函数:
- 这两个辅助函数用于反转字节或字的位顺序。例如,
0b10000000
反转后是 0b00000001
。这在一些 CRC 变种中是必需的。
如何选择参数
在实际应用中,你需要根据所使用的 CRC16 协议(例如,MODBUS RTU、蓝牙、SD 卡等)来确定正确的 polynomial
、initial_value
、reflect_in
、reflect_out
和 xor_out
参数。通常这些参数都会在协议规范中明确指出。
如果你不确定参数,可以尝试在网上搜索 “CRC-16 [协议名称]” 或 “CRC-16 [多项式] 参数” 来查找对应的配置。有许多在线 CRC 计算器也可以帮助你验证参数是否正确。
通过调整这些参数,你可以使用同一个 crc16
函数来实现各种 CRC16 校验码的生成。
在线工具