由于本人的脑子比较笨,根本看不懂文献关于CRC的讲解,被博士女友骂了说智商低 不配看论文 不能像博士那样能死磕论文。于是自己琢磨加上网上大神的文章一步一步弄出了CRC的原理 下面和大家一起分享。
首先讲模2除法
【说明】“模2除法”与“算术除法”类似,但它既不向上位借位,也不比较除数和被除数的相同位数值的大小,只要以相同位数进行相除即可。模2加法运算为:1+1=0,0+1=1,0+0=0,无进位,也无借位;模2减法运算为:1-1=0,0-1=1,1-0=1,0-0=0,也无进位,无借位。相当于二进制中的逻辑异或运算。也就是比较后,两者对应位相同则结果为“0”,不同则结果为“1”。如100101除以1110,结果得到商为11,余数为1,如图5-9左图所示。如11×11=101,如图5-9右图所示。
这个是CRC校验的基础。我们的CRC校验就是以这个为基础的来进行计算的。
2. 多项式 我们可以把一个二进制用一个多项式来进行表示:
例如 0x25 = 00100101就可以变成0*x7 + 0*x6 + 1*x5 + 0*x4 + 0*x3 + 1*x2 + 0*x1 + 1*x0
就可以变成 x5 + x2 + 1
3. CRC的校验过程:
在发送端要进行CRC的加密过程:
1. 二进制的信息首先根据CRC的多项式的长度在后面加入相应的0 (比如CRC8 就是加上8个0 CRC16就是加上16个0) 模2除以 上面给定的多项式(一会儿给出多项式) 然后求出最后的余数 把余数append到这个信息的最后 加密完成
在接收端:
1. 接收到的数据首先模2的除以CRC多项式 如果求出的CRC为0 则说明验证成功。
我们首先看下面的例子:下面的例子给出了一个信息和CRC-8之间在发送端加密的过程:
阅读上面的英文我们发现CRC的多项式是由9个二进制组成的,我们后面会讲第一个1是可以忽略的。
由此我们就得到了CRC的值是0x0F 然后把这个值放到信息的最后 当接收端收到数据之后进行模2的处理 如果得到0 就说明验证成功。
底下是接收端的计算过程请看图:
4.CRC的移位寄存器的概念:
为了模仿这个模2的过程 我们使用CRC寄存器的概念 这个寄存器是把传递的信息放入,对于CRC8 来说里面可以一次性放8位 对于CRC16 来说可以放置16位 对于CRC32 来说可以放置32位信息(二进制信息),我们在上图看到了其实信息是一位一位的从高位到低位和多项式进行比较的。先比较的是bit7 bit6 bit5 bit4 bit3 bit2 bit1 然后比较bit6 bit5 bit4 bit3 bit2 bit1 bit-1(表示后面新进入的数据)
5.完成CRC-8的程序。
我们下面开始完成CRC8的程序,CRC8 一共是9位 但是其实最高位是可以去掉的,因为他一定是1 而且每次他都是和信息的是1 的最高位对齐(下面的图会告诉你是怎么对齐的)所以每次的异或总是0
我们假设信息是0xC2 多项式是0x1D 那么程序可以写成如下的形式:
public static byte Compute_CRC8_Simple_OneByte_ShiftReg(byte byteVal)
{
const byte generator = 0x1D;
byte crc = 0; /* init crc register with 0 */
/* append 8 zero bits to the input byte */
byte[] inputstream = new byte[] { byteVal, 0x00 }; /*the 0 presenting in the array is to append at the end of the valid information*/
/* handle each bit of input stream by iterating over each bit of each input byte */
foreach (byte b in inputstream)
{
for (int i = 7; i >= 0; i--)
{
/* check if MSB is set */
if ((crc & 0x80) != 0)
{ /* MSB set, shift it out of the register */
crc = (byte)(crc << 1); //请注意一上来的时候CRC等于0 所以第一次循环的之后CRC还是等于0
/* shift in next bit of input stream:
* If it's 1, set LSB of crc to 1.
* If it's 0, set LSB of crc to 0. */
crc = ((byte)(b & (1 << i)) != 0) ? (byte)(crc | 0x01) : (byte)(crc & 0xFE);
/* Perform the 'division' by XORing the crc register with the generator polynomial */
crc = (byte)(crc ^ generator); //第一次循环的时候CRC 就是b的值 然后异或上poly的值
}
else
{ /* MSB not set, shift it out and shift in next bit of input stream. Same as above, just no division */
crc = (byte)(crc << 1);
crc = ((byte)(b & (1 << i)) != 0) ? (byte)(crc | 0x01) : (byte)(crc & 0xFE);
}
}
}
return crc;
}
6 CRC8的改进:
我们发现一上来就把CRC的值给成信息的值 那么就不用一上来就移位了而且由于移位的时候信息后面会自动不零 所以我们也不用手工在后面补零了。于是程序变成了
public static byte Compute_CRC8_Simple_OneByte(byte byteVal)
{
const byte generator = 0x1D;
byte crc = byteVal; /* init crc directly with input byte instead of 0, avoid useless 8 bitshifts until input byte is in crc register */
for (int i = 0; i < 8; i++)
{
if ((crc & 0x80) != 0)
{ /* most significant bit set, shift crc register and perform XOR operation, taking not-saved 9th set bit into account */
crc = (byte)((crc << 1) ^ generator);
}
else
{ /* most significant bit not set, go to next bit */
crc <<= 1;
}
}
return crc;
}
处理过程如下:
其中有一个问题就是为什么这里要先移位然后再进行异或?
回答:因为CRC8 是九位的(虽然在程序里面是8位的,我们其实是省去了最靠前的那一位)但是如图所示我们每一次MSB都是会异或得0 所以其实是我们同时把信息的最高位和多项式的多高位一起去掉了。所以每一次信息都是先移位在异或。
7. 多个字节的CRC校验:
上面我们都是校验了一个字节 那么多个字节怎么做?
1. 请看下图,描述了多个字节的校验:
我们会发现其实他的过程是首先和第一个字节异或 然后和第二个字节异或。
下面是代码:
我们已经学会了CRC的基本原理,可是上面的复杂度太高 我们需要优化代码:
我们需要建立一个表来进行查询。原理 因为每一个byte都是和poly异或 然后再以此异或到一起 所以我们先求出(0~255)和poly异或的结果 然后再让程序去查表。
程序如下:
public static void CalulateTable_CRC8()
{
const byte generator = 0x1D;
crctable = new byte[256];
/* iterate over all byte values 0 - 255 */
for (int divident = 0; divident < 256; divident++)
{
byte currByte = (byte)divident;
/* calculate the CRC-8 value for current byte */
for (byte bit = 0; bit < 8; bit++)
{
if ((currByte & 0x80) != 0)
{
currByte <<= 1;
currByte ^= generator;
}
else
{
currByte <<= 1;
}
}
/* store CRC value in lookup table */
crctable[divident] = currByte;
}
}
public static byte Compute_CRC8(byte[] bytes)
{
byte crc = 0;
foreach (byte b in bytes)
{
/* XOR-in next input byte */
byte data = (byte)(b ^ crc);
/* get current CRC value = remainder */
crc = (byte)(crctable[data]);
}
return crc;
}
CRC16:
我们学会了CRC8的校验 如何进行CRC16呢?其实就是把每个字节的信息向左位移8位 然后再进行计算:
代码如下:
public static ushort Compute_CRC16_Simple(byte[] bytes)
{
const ushort generator = 0x1021; /* divisor is 16bit */
ushort crc = 0; /* CRC value is 16bit */
foreach (byte b in bytes)
{
crc ^= (ushort(b << 8); /* move byte into MSB of 16bit CRC */
for (int i = 0; i < 8; i++)
{
if ((crc & 0x8000) != 0) /* test for MSB = bit 15 */
{
crc = (ushort((crc << 1) ^ generator);
}
else
{
crc <<= 1;
}
}
}
return crc;
}
CRC16 制表的程序:
public static void CalculateTable_CRC16()
{
const ushort generator = 0x1021;
crctable16 = new ushort[256];
for (int divident = 0; divident < 256; divident++) /* iterate over all possible input byte values 0 - 255 */
{
ushort curByte = (ushort(divident << 8); /* move divident byte into MSB of 16Bit CRC */
for (byte bit = 0; bit < 8; bit++)
{
if ((curByte & 0x8000) != 0)
{
curByte <<= 1;
curByte ^= generator;
}
else
{
curByte <<= 1;
}
}
crctable16[divident] = curByte;
}
}
CRC-8 Shift Register Example: Input data = 0xC2 = b11000010 (with 8 zero bits appended: b1100001000000000), Polynomial = b100011101
1. CRC-8 register initialized with 0. --- --- --- --- --- --- --- --- | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | <-- b1100001000000000 --- --- --- --- --- --- --- --- 2. Left-Shift register by one position. MSB is 0, so nothing do happen, shift in next byte of input stream. --- --- --- --- --- --- --- --- | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | <-- b100001000000000 --- --- --- --- --- --- --- --- 3. Repeat those steps. All steps are left out until there is a 1 in the MSB (nothing interesting happens), then the state looks like: --- --- --- --- --- --- --- --- | 1 | 1 | 0 | 0 | 0 | 0 | 1 | 0 | <-- b00000000 --- --- --- --- --- --- --- --- 4. Left-Shift register. MSB 1 pops out: --- --- --- --- --- --- --- --- 1 <- | 1 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | <-- b0000000 --- --- --- --- --- --- --- --- So XOR the CRC register (with popped out MSB) b110000100 with polynomial b100011101 = b010011001 = 0x99. The MSB is discarded, so the new CRC register value is 010011001: --- --- --- --- --- --- --- --- | 1 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | <-- b0000000 --- --- --- --- --- --- --- --- 5. Left-Shift register. MSB 1 pops out: b100110010 ^ b100011101 = b000101111 = 0x2F: --- --- --- --- --- --- --- --- | 0 | 0 | 1 | 0 | 1 | 1 | 1 | 1 | <-- b000000 --- --- --- --- --- --- --- --- 6. Left-shift register until a 1 is in the MSB position: --- --- --- --- --- --- --- --- | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | <-- b0000 --- --- --- --- --- --- --- --- 7. Left-Shift register. MSB 1 pops out: b101111000 ^ b100011101 = b001100101 = 0x65: --- --- --- --- --- --- --- --- | 0 | 1 | 1 | 0 | 0 | 1 | 0 | 1 | <-- b000 --- --- --- --- --- --- --- --- 8. Left-shift register until a 1 is in the MSB position: --- --- --- --- --- --- --- --- | 1 | 1 | 0 | 0 | 1 | 0 | 1 | 0 | <-- b00 --- --- --- --- --- --- --- --- 9. Left-Shift register. MSB 1 pops out: b110010100 ^ b100011101 = b010001001 = 0x89: --- --- --- --- --- --- --- --- | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | <-- b0 --- --- --- --- --- --- --- --- 10. Left-Shift register. MSB 1 pops out: b10001001 ^ b100011101 = b000001111 = 0x0F: --- --- --- --- --- --- --- --- | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | <-- <empty> --- --- --- --- --- --- --- --- All input bits are processed, the algorithm stops. The shift register contains now the CRC value which is 0x0F.