← 返回 中控与协议

校验算法专题:和校验 Sum / 异或 XOR / CRC16 / BCC 手算与代码

最后更新 2026-06-22
s4 · 中控与协议 🟡 涉接线/工业配置
你将学到
  • 理解展厅设备指令里"校验字节"的作用——不是加密是查错
  • 掌握 Sum8、XOR、CRC16-Modbus、BCC 四种校验的算法原理和手算步骤
  • 能写出或读懂这四种校验的 Python 代码
  • 学会从手册识别用的是哪种校验算法
  • 知道校验错误时从哪里排查(是算法选错、计算范围不对,还是字节顺序问题)

你在手册里找到了一条开机指令,格式是:

A0 01 00 00 XX

手册说最后一个字节 XX 是"校验字节",但只写了"请发送时计算校验",没说怎么算。你对着指令发出去,设备无应答;试着把最后一个字节改成 00FFA1……还是不动。

这是展厅控制调试里最令人崩溃的场景之一。不是线路问题,不是参数问题,是校验字节算错了,设备把帧整条丢弃,你什么应答都收不到。

这一节就是专门解决这个问题的。展厅设备常用的校验算法只有四种:Sum8(和校验)、XOR(异或校验)、CRC16-Modbus、BCC(块校验)。把这四种搞透,见到任何手册里的校验字节,你都能算出来。

涉及协议调试与帧格式(R2)。CRC16-Modbus 多项式和算法以 Modbus 官方规范(Modbus Application Protocol V1.1b3)为准;其他算法原理均为通用算法,与具体设备无关。具体某台设备用哪种校验,以该设备手册为准。


为什么需要校验字节

串口和网络传输都可能出错:信号反射、电磁干扰、瞬间的接触不良,都会让某个字节里的某一位发生翻转。

如果没有校验,设备收到一条"被干扰后的错误指令",可能会执行一个不该执行的动作——比如本来要"关灯",干扰后字节变了,结果执行了"开大功率设备"。

校验字节的作用是让接收方检测出帧是否在传输中出错。 发送方按约定的算法,把帧里的数据字节计算出一个"校验值",附在帧末尾;接收方收到帧后,用同样的算法重新计算校验值,和帧末尾的校验字节对比,不一样就说明帧出错了,直接丢弃,不执行。

注意:校验字节不加密、不防篡改,只检查传输错误(1 位翻转等),有些错误模式理论上检测不到,但比没有校验要好得多。


第一种:Sum8(和校验 / 累加和)

算法原理:把所有数据字节相加(每个字节作为 0~255 的整数),结果取低 8 位(即对 256 取余)。

手算示例:帧数据部分为 A0 01 00 00,计算校验:

0xA0 + 0x01 + 0x00 + 0x00
= 160 + 1 + 0 + 0
= 161
= 0xA1

Sum8 校验字节 = 0xA1

所以完整帧是 A0 01 00 00 A1

有时候手册里写的是"取补"或"取反加一",这就不是直接取和,而是先算和再取反(NOT)然后加 1,得到负数(二进制补码)的低 8 位:

带补码的 Sum8 变体(有些厂商叫"Check Sum")

Sum8 = 0xA1 = 1010 0001
取反  = 0101 1110 = 0x5E
加 1  = 0101 1111 = 0x5F

校验字节 = 0x5F
💡 提示

两种 Sum8 变体(直接取和 vs 取补),手册描述通常有区别:写"累加和(模 256)"是直接取和;写"取反加一"或"负数补码"是补码变体。遇到不确定的手册,两个都试一下,通常能快速定位。

Python 代码(直接 Sum8)

def sum8(data: bytes) -> int:
    """
    计算 Sum8 校验:数据字节累加取低 8 位
    参数 data: 需要计算校验的字节范围(不含校验字节本身)
    返回: 单字节校验值(0x00 ~ 0xFF)
    """
    return sum(data) & 0xFF

# 示例
frame_data = bytes([0xA0, 0x01, 0x00, 0x00])
checksum = sum8(frame_data)
print(f"Sum8 校验: 0x{checksum:02X}")   # 输出: Sum8 校验: 0xA1

Python 代码(取补码 Sum8 变体)

def sum8_complement(data: bytes) -> int:
    """
    计算 Sum8 取补(取反加一)变体
    """
    return (~sum(data) + 1) & 0xFF

frame_data = bytes([0xA0, 0x01, 0x00, 0x00])
print(f"Sum8补码: 0x{sum8_complement(frame_data):02X}")   # 输出: 0x5F

第二种:XOR(异或校验)

算法原理:把所有数据字节两两异或(XOR),最终结果就是校验字节。异或运算:相同为 0,不同为 1。

手算示例:帧数据部分为 A0 01 00 00

A0 XOR 01 = 1010 0000 XOR 0000 0001 = 1010 0001 = 0xA1
A1 XOR 00 = 1010 0001 XOR 0000 0000 = 1010 0001 = 0xA1
A1 XOR 00 = 1010 0001 XOR 0000 0000 = 1010 0001 = 0xA1

XOR 校验字节 = 0xA1

(巧合,这里 XOR 和 Sum8 结果相同,是因为这组字节相加时没有产生进位——A001 在二进制上没有重叠位、加 00 又不改变结果,所以"异或"和"累加"恰好一致;大多数数据下两者并不相同。)

换一个数据更明显的例子,帧数据 B4 2A 55 7F

B4 XOR 2A:
  1011 0100
  0010 1010
= 1001 1110 = 0x9E

9E XOR 55:
  1001 1110
  0101 0101
= 1100 1011 = 0xCB

CB XOR 7F:
  1100 1011
  0111 1111
= 1011 0100 = 0xB4

XOR 校验字节 = 0xB4

Python 代码

def xor_checksum(data: bytes) -> int:
    """
    计算 XOR 校验:所有数据字节连续异或
    参数 data: 需要计算校验的字节范围(不含校验字节本身)
    返回: 单字节 XOR 校验值
    """
    result = 0
    for byte in data:
        result ^= byte
    return result

# 简洁写法(Python functools.reduce)
from functools import reduce
def xor_checksum_v2(data: bytes) -> int:
    return reduce(lambda a, b: a ^ b, data, 0)

# 示例
frame_data = bytes([0xB4, 0x2A, 0x55, 0x7F])
checksum = xor_checksum(frame_data)
print(f"XOR 校验: 0x{checksum:02X}")   # 输出: XOR 校验: 0xB4

XOR 的特点

  • 比 Sum8 更能检测出多位翻转(特别是偶数位同时翻转,Sum8 可能检测不出来)
  • 展厅里常见于灯光协议、某些私有 RS232 协议

第三种:CRC16-Modbus(重点)

这是展厅控制里用得最多的校验算法,所有走 Modbus RTU 的设备都用这种。正式名称是 CRC-16/IBM,Modbus 官方规范称其为 CRC16,多项式是 0x8005,初始值 0xFFFF,输入/输出数据反转,最终不反转。

💡 提示

市面上有很多不同的 CRC16 变体(CRC16/CCITT、CRC16/BUYPASS 等),它们初始值和反转设置不同,计算结果完全不一样。Modbus 用的是 CRC-16/IBM 配置。你在网上搜 CRC16 计算器,一定要确认是"CRC-16/Modbus"或"CRC-16/IBM",否则算出来对不上。crccalc.com 上可以验证,选 CRC-16/IBM 模式。

算法原理(简化版)

CRC16-Modbus 的计算步骤(以字节为单位的查表法或位运算法,结果等价):

  1. 初始化 CRC 寄存器 = 0xFFFF
  2. 对每个数据字节: a. 把 CRC 低字节和当前数据字节做 XOR b. 循环 8 次:如果 CRC 最低位 = 1,则 CRC 右移 1 位再 XOR 0xA001;否则只右移 1 位
  3. 全部字节处理完后,CRC 寄存器的值就是结果
  4. RTU 帧里低字节先发(Little-Endian)

0xA0010x8005 的位翻转(因为 Modbus CRC 是反转输入数据处理的),这是 Modbus 规范的要求。

手算示例(完整走一遍)

帧数据(从站地址到功能码到数据,不含校验字节):01 03 00 00 00 01(读从站 1 的 1 个保持寄存器)

手算 CRC16-Modbus,逐字节:

初始 CRC = 0xFFFF

字节 1:0x01

CRC 低字节 XOR 0x01:0xFF XOR 0x01 = 0xFE  → CRC = 0xFFFE
循环 8 位:
  第1位: 0xFFFE 最低位=0,右移1 → 0x7FFF
  第2位: 0x7FFF 最低位=1,右移1=0x3FFF,XOR 0xA001 → 0x3FFF XOR 0xA001
         0x3FFF = 0011 1111 1111 1111
         0xA001 = 1010 0000 0000 0001
         XOR  =  1001 1111 1111 1110 = 0x9FFE
  第3位: 0x9FFE 最低位=0,右移1 → 0x4FFF
  第4位: 0x4FFF 最低位=1,右移1=0x27FF,XOR 0xA001 → 0x27FF XOR 0xA001
         0x27FF = 0010 0111 1111 1111
         0xA001 = 1010 0000 0000 0001
         XOR  =  1000 0111 1111 1110 = 0x87FE
  第5位: 0x87FE 最低位=0,右移1 → 0x43FF
  第6位: 0x43FF 最低位=1,右移1=0x21FF,XOR 0xA001 → 0x21FF XOR 0xA001
         0x21FF XOR 0xA001 = 0x81FE
  第7位: 0x81FE 最低位=0,右移1 → 0x40FF
  第8位: 0x40FF 最低位=1,右移1=0x207F,XOR 0xA001 → 0x207F XOR 0xA001
         0x207F XOR 0xA001 = 0x807E

字节 01 处理完 → CRC = 0x807E

(后续字节 0x03、0x00、0x00、0x00、0x01 的手算过程类似,篇幅原因省略逐步骤,最终结果用代码验证)

用 Python 代码验证

def crc16_modbus(data: bytes) -> int:
    """
    计算 CRC16-Modbus 校验
    多项式: 0x8005(反转处理)
    初始值: 0xFFFF
    输入反转: True,输出反转: False
    参数 data: 需要计算校验的字节范围(不含校验字节本身)
    返回: 16 位 CRC 值
    """
    crc = 0xFFFF
    for byte in data:
        crc ^= byte          # 低字节 XOR 当前数据字节
        for _ in range(8):   # 处理 8 位
            if crc & 0x0001:  # 最低位为 1
                crc = (crc >> 1) ^ 0xA001
            else:
                crc >>= 1
    return crc

def crc16_modbus_bytes(data: bytes) -> tuple[int, int]:
    """
    返回 RTU 帧中的两个 CRC 字节:(低字节, 高字节)
    RTU 帧里 CRC 低字节先传
    """
    crc = crc16_modbus(data)
    return crc & 0xFF, (crc >> 8) & 0xFF

# 验证:01 03 00 00 00 01 的 CRC
frame_data = bytes([0x01, 0x03, 0x00, 0x00, 0x00, 0x01])
crc_lo, crc_hi = crc16_modbus_bytes(frame_data)
print(f"CRC 低字节: 0x{crc_lo:02X}, 高字节: 0x{crc_hi:02X}")
# 输出: CRC 低字节: 0x84, 高字节: 0x0A
# 完整帧: 01 03 00 00 00 01 84 0A

验证方法:把完整帧(含 CRC)输入 crccalc.com,选 CRC-16/IBM,得到 0x0000,说明 CRC 正确(接收方用同样算法对含 CRC 的完整帧计算,结果 0x0000 表示校验通过)。

RTU 帧里 CRC 的字节顺序

低字节先,高字节后。上面例子 CRC = 0x0A84,帧里写:

... 84 0A
     │   └── CRC 高字节(0x0A)
     └────── CRC 低字节(0x84)

这是 Modbus over Serial Line 规范的规定,不要反着写。


第四种:BCC(块校验字符 / Block Check Character)

算法原理:BCC 和 XOR 校验完全一样,就是所有数据字节连续异或的结果。BCC 是 XOR 校验的另一个名称,在不同厂商的手册和标准文档(如 ISO 1745、某些私有协议)里叫法不同,但算法完全等价。

BCC = data[0] XOR data[1] XOR data[2] XOR ... XOR data[n-1]

唯一要注意的细节:有些协议里 BCC 计算范围包含帧头,有些不包含;要看手册说明的计算范围是"从哪个字节算到哪个字节"。

def bcc(data: bytes) -> int:
    """
    BCC = XOR 的别名,算法完全相同
    """
    result = 0
    for byte in data:
        result ^= byte
    return result

怎么从手册判断用的是哪种校验

常见手册描述和对应算法

手册里的写法 对应算法
"CRC校验"、"CRC-16"、"Modbus CRC" CRC16-Modbus(几乎可以确定)
"累加和"、"和校验"、"SUM"、"CheckSum(mod 256)" Sum8(直接取和低字节)
"累加和取反加一"、"CheckSum"(后跟取补说明) Sum8 补码变体
"异或"、"XOR"、"LRC"(纵向冗余校验) XOR
"BCC"、"块校验" BCC(等同 XOR)
"LRC"(Modbus ASCII 里) 这是个特殊情况,见下方说明

Modbus ASCII 里的 LRC:Modbus ASCII 协议(不同于 RTU)用的是 LRC(Longitudinal Redundancy Check),算法是:把所有数据字节相加,取负数(即 0x100 - Sum8),低字节作为校验。和 Sum8 补码变体等价,但 Modbus ASCII 里是以 ASCII 十六进制字符传输的(每字节变成两个字符),展厅里 RTU 用得更多。

判断算法的实战方法:手册里通常会给一两个例子帧(含校验字节),你可以把这个例子的数据字节用四种算法各算一遍,哪个结果和手册给的校验字节一致,就是哪种。


校验错误时怎么排查

发出帧后设备无应答,首先要确认是"校验错"还是"其他原因"。排查步骤:

  1. 先排除基础原因:参考设备控制与指令调试,先确认串口参数对、接线对、HEX/ASCII 模式对。在排除了这些之后再看校验。

  2. 算法确认:从手册里找到协议说明部分,找"校验"、"CRC"、"Check"等关键词,找一个手册给出的完整帧例子(带校验字节),用你选定的算法验算——结果和手册示例一致,算法就对了。

  3. 计算范围确认:CRC 或校验是对"哪些字节"计算的?常见两种:

    • 从从站地址到数据末尾(不含校验字节本身)——大多数 Modbus 设备
    • 从功能码开始(不包含地址字节)——少数私有协议 手册里通常有"对以下字节计算校验"的表述,确认范围,不含校验字节本身。
  4. 字节顺序:CRC16 结果是 16 位,要发两个字节——低字节先还是高字节先?Modbus RTU 规范是低字节先;但有些私有协议是高字节先。如果算法对了但还是不通,试一下把两个字节顺序颠倒。

  5. 在线工具验证crccalc.com 可以验证 CRC 类算法。输入你的帧数据(十六进制),选 CRC-16/IBM,比对结果。


一个综合示例:私有协议校验判断

假设手册给出一条指令的例子:

发送:A5 01 03 00 00 00 XX
说明:A5 是帧头,01 是设备地址,03 是功能,00 00 00 是数据,XX 是校验
手册写:校验 = 地址到数据所有字节异或

手算校验:

01 XOR 03 XOR 00 XOR 00 XOR 00
= 01 XOR 03 = 0000 0001 XOR 0000 0011 = 0000 0010 = 0x02
0x02 XOR 0x00 = 0x02
0x02 XOR 0x00 = 0x02
0x02 XOR 0x00 = 0x02

XX = 0x02
完整帧:A5 01 03 00 00 00 02

注意:帧头 A5 不参与校验(手册说"从地址字节开始"),所以 A5 不放进计算。这种细节一定要看手册,不同设备的计算范围定义不同。


四种校验算法对比

算法 复杂度 典型场景 特点
Sum8 最简单 某些私有协议、早期工控设备 连续相同字节累加不能检测出来
XOR / BCC 简单 私有串口协议、灯光控制器 检测任意单位翻转,代码极简
CRC16-Modbus 中等 所有 Modbus RTU 设备 检错能力强,16 位覆盖多字节
CRC32 复杂 网络帧(以太网) 展厅控制协议很少用,无需关注

小结 · 你现在掌握了什么

  • 校验字节的作用:检测传输错误(不是加密),接收方计算不符就丢帧。
  • 四种算法:Sum8(累加取低字节)、XOR(逐字节异或)、CRC16-Modbus(多项式 0x8005,初始 0xFFFF,低字节先)、BCC(等同 XOR)。
  • 从手册判断:找示例帧,四种算法都试一遍,哪个对上就用哪种;注意计算范围(从哪个字节到哪个字节)和 CRC 字节顺序。
  • 校验错时:先确认算法、再确认计算范围、再确认字节顺序,用 crccalc.com 或自写代码逐步验证。

下一步:协议、帧格式、校验都搞透了,去看展厅投影控制协议 PJLink 控投影:认证、Class 1/2、电源/输入/状态常用指令——这是一个公开的投影统一标准,比各家私有 RS232 协议省力得多。

📄 来源 / 自校链接

本文为公开资料整理,非亲测。关键参数与代码请结合实物与下列官方来源验证。

内容有错、看不懂、或想看下一期?告诉我们 →

本文为公开资料的学习整理,非亲测。涉接线/花钱/合规的步骤请结合实物与官方最新资料验证,风险自负。见免责声明

需要展厅软硬件方案或定制开发?