简介
循环冗余校验(Cyclic Redundancy Check, CRC)是一种根据网络数据包或计算机文件等数据产生简短固定位数校验码的一种信道编码技术,主要用来检测或校验数据传输或者保存后可能出现的错误。它是利用除法及余数的原理来作错误侦测的。
CRC参数模型
参数名称 | 参数意义 |
---|---|
Name | CRC校验的名称,存在许多用于特定场合的CRC校验模型,比如“CRC-16/MODBUS” |
Width | 代表生成项的长度 |
Poly | 多项式码 |
Init | CRC校验码的初始值吗,数据在进行上述二项式运算之前,需要先将要计算的数据与初始值进行异或,然后再与多项式进行计算,其作用是可以保留数据头部的初始零(如果与0xFFFF异或,则头部省略的零变成1进行运算) |
RefIn | 输入数据是否反转,通常与数据的接受方式有关,比如MODBUS是先接受低位数据,所以就需要对输入数据进行反转,可以在接收端“负负得正” |
XorOut | 最后的CRC校验码与XorOUt进行异或后再输出 |
RefOut | 输出的CRC校验码是否反转,一般于RefIn配对 |
- 不同的二项式、初始值、结果异或值、反转原则都会造成最终的结果不一致,所以存在许多模型参数不同的CRC校验,但是只要发送方和接收方约定相同,就可以成功校验。
- 出现数据反转的原因是由于一些协议传输数据是从低位开始的,比如:因为Modbus在传输数据的时候是低位优先的,也就是它是从0位出去的,然后接收端是从0位接收的。所以RefIn和RefOut一般是成对出现的。
多项式码
性质
- 生成多项式的最高位和最低位必须为1。
- 当被传送信息(CRC码)任何一位发生错误时,被生成多项式做模2除后,应该使余数不为0。
- 不同位发生错误时,应该使余数不同。
- 对余数继续做模2除,应使余数循环。
特点
- 多项式码的首位1可以忽略
* 因为在实现往往是检测数据的置1数据位,然后跳过,对剩下的取余。既跳过了数据的置1数据位,也跳过了多项式码的首位1.
实例
- x16 + x15 + x2 + 1对应的多项式码是1’0x8005(1000 0000 0000 0101B),其首位的1可以忽略,其反向校验的值为1’0xA001(1010 0000 0000 0001B)。
实现方式
从理解原理到实际实现,是有很多技巧可以使用的。(在Qt中实现)
- 直接计算法
unsigned short crc_check(QByteArray data) { /* CRC16参数模型 */ // 模型名称:CRC-16/SERIALPORT // 初始值:0xFFFF // 校验方向:反向 // 多项式检验码:0x8005(反向0xA001):x16 + x12 + x2 + 1 // 输入反向:否 // 输出反向:否 // 实现算法:直接算法 unsigned short temp = 0x0000; int i,j; /* 处理过程不要与"模二除法"混淆,应该把每个数据独立出来看待,否则会解释不通temp ^= (unsigned char)data.at(i) */ // 反向校验 for (i = 0;i < data.size(); i++) { // 第一次处理是与CRC初始值进行异或,后面是数据对前面一个数据单独异或得到的余数进行异或 temp ^= ((unsigned char)data.at(i) << 8); for (j = 0; j < 8; j++) { if (temp & 0x8000) temp = (temp << 1) ^ 0x8005; else temp = temp << 1; } } return temp; }
- 查表法
- CRC校验的数据往往是以字节形式存储,一个字节有8个bit,其范围为0x00 ~ 0xFF,既0 - 255,大小是固定的,而且并不是很大, 所以我们可以实现构造表,可以直接得到字节对应的CRC校验码。
- 其实质就是将这一段代码给封装起来了,使用表的形式存储。
for (j = 0; j < 8; j++) { if (temp & 0x01) temp = (temp >> 1) ^ 0xA001; else temp = temp >> 1; }
- 输入反转镜像算法
unsigned short crc16_check(QByteArray data) { /* CRC16参数模型 */ // 模型名称:CRC-16/MODBUS // 初始值:0xFFFF // 校验方向:反向 // 多项式检验码:0x8005(反向0xA001):x16 + x12 + x2 + 1 // 输入反向:是 // 输出反向:是 // 实现算法:镜像算法(使用右移替代左移,使得数据不用反向再来处理 unsigned short temp = 0xFFFF; int i,j; /* 处理过程不要与"CRC除法"混淆,应该把每个数据独立出来看待,否则会解释不通temp ^= (unsigned char)data.at(i) */ // 反向校验 for (i = 0;i < data.size(); i++) { // 第一次处理是与CRC初始值进行异或,后面是数据对前面一个数据单独异或得到的余数进行异或 temp ^= (unsigned char)data.at(i); for (j = 0; j < 8; j++) { if (temp & 0x01) temp = (temp >> 1) ^ 0xA001; else temp = temp >> 1; } } return temp; }
- 可以通过下图[1]来理解
temp ^= (unsigned char)data.at(i)
,图中数据长度为5,如果替换成8就是我们实现中的代码流程。应该将一个数据的"模二除法”独立出来看待,第一个数据“模二除法”后的余数再与第二个数据异或。同时可以理解为使用到了异或的组合率:A XOR B XOR C XOR D = A XOR (B XOR C XOR D),这里的A可以理解为第二个字符11011,而(B XOR C XOR D)可以理解为多项式码在第一个字符操作期间,在第二个字符区域的最终异或结果。
- 可以通过下图[1]来理解
常见CRC校验模型
模型名称 | 多项式公式 | 宽度 | 多项式 | 初始值 | 结果异或值 | 输入反转 | 输出反转 |
---|---|---|---|---|---|---|---|
CRC-8 | x8 + x2 + x + 1 | 8 | 07 | 00 | 00 | false | false |
CRC-16/USB | x16 + x15 + x2 + 1 | 16 | 8005 | FFFF | FFFF | true | true |
CRC-16/MODBUS | x16 + x15 + x2 + 1 | 16 | 8005 | FFFF | 0000 | true | true |
CRC-32 | x32 + x26 + x23 + x22 + x16 + x12 + x11 + x10 + x8 + x7 + x5 + x4 + x2 + x + 1 | 32 | 04C11DB7 | FFFFFFFF | FFFFFFFF | true | true |
参考资料
- https://segmentfault.com/a/1190000018094567?utm_source=tag-newest
- http://blog.sina.com.cn/s/blog_17e2ca0810102z3dz.html
- http://blog.sina.com.cn/s/blog_17e2ca0810102z3e0.html
- CRC校验测试网站
- https://baike.baidu.com/item/CRC/1453359?fr=aladdin