CRC循环冗余校验

时间:2021-10-17 11:57:53

1. CRC校验原理

    CRC校验原理看起来比较复杂,好难懂,因为大多数书上基本上是以二进制的多项式形式来说明的。其实很简单的问题,其根本思想就是先在要发送的帧后面附加一个数(这个就是用来校验的校验码,但要注意,这里的数也是二进制序列的,下同),生成一个新帧发送给接收端。当然,这个附加的数不是随意的,它要使所生成的新帧能与发送端和接收端共同选定的某个特定数整除(注意,这里不是直接采用二进制除法,而是采用一种称之为2除法)。到达接收端后,再把接收到的新帧除以(同样采用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,如11×11=101,

 

 CRC循环冗余校验CRC循环冗余校验

 

 

 

     具体来说,CRC校验原理就是以下几个步骤:

 

   (1)先选择(可以随机选择,也可按标准选择,具体在后面介绍)一个用于在接收端进行校验时,对接收的帧进行除法运算的除数(是二进制比较特串,通常是以多项方式表示,所以CRC又称多项式编码方法,这个多项式也称之为“生成多项式”)。

 

   (2)看所选定的除数二进制位数(假设为k位),然后在要发送的数据帧(假设为m位)后面加上k-1位“0”,然后以这个加了k-1个“0“的新帧(一共是m+k-1位)以“模2除法”方式除以上面这个除数,所得到的余数(也是二进制的比特串)就是该帧的CRC校验码,也称之为FCS(帧校验序列)。但要注意的是,余数的位数一定要是比除数位数只能少一位,哪怕前面位是0,甚至是全为0(附带好整除时)也都不能省略。

 

   (3)再把这个校验码附加在原数据帧(就是m位的帧,注意不是在后面形成的m+k-1位的帧)后面,构建一个新帧发送到接收端;最后在接收端再把这个新帧以“模2除法”方式除以前面选择的除数,如果没有余数,则表明该帧在传输过程中没出错,否则出现了差错。

  通过以上介绍,大家一定可以理解CRC校验的原理,并且不再认为很复杂吧。

    从上面可以看出,CRC校验中有两个关键点:一是要预先确定一个发送端和接收端都用来作为除数的二进制比特串(或多项式);二是把原始帧与上面选定的除进行二进制除法运算,计算出FCS。前者可以随机选择,也可按国际上通行的标准选择,但最高位和最低位必须均为“1”,如在IBM的SDLC(同步数据链路控制)规程中使用的CRC-16(也就是这个除数一共是17位)生成多项式g(x)= x16 + x15 + x2 +1(对应二进制比特串为:11000000000000101);而在ISO HDLC(高级数据链路控制)规程、ITU的SDLC、X.25、V.34、V.41、V.42等中使用CCITT-16生成多项式g(x)= x16 + x15 + x5+1(对应二进制比特串为:11000000000100001)。

2.    CRC校验码的计算示例

   由以上分析可知,既然除数是随机,或者按标准选定的,所以CRC校验的关键是如何求出余数,也就是校验码(CRC校验码)。

    下面以一个例子来具体说明整个过程。现假设选择的CRC生成多项式为G(X) = X4 + X3 + 1,要求出二进制序列10110011的CRC校验码。下面是具体的计算过程:

   (1)首先把生成多项式转换成二进制数,由G(X) = X4 + X3 + 1可以知道(,它一共是5位(总位数等于最高位的幂次加1,即4+1=5),然后根据多项式各项的含义(多项式只列出二进制值为1的位,也就是这个二进制的第4位、第3位、第0位的二进制均为1,其它位均为0)很快就可得到它的二进制比特串为11001。

   (2)因为生成多项式的位数为5,根据前面的介绍,得知CRC校验码的位数为4(校验码的位数比生成多项式的位数少1)。因为原数据帧10110011,在它后面再加4个0,得到101100110000,然后把这个数以“模2除法”方式除以生成多项式,得到的余数(即CRC码)为0100,如图所示。注意参考前面介绍的“模2除法”运算法则。

CRC循环冗余校验

 (3)把上步计算得到的CRC校验0100替换原始帧101100110000后面的四个“0”,得到新帧101100110100。再把这个新帧发送到接收端。

    (4)当以上新帧到达接收端后,接收端会把这个新帧再用上面选定的除数11001以“模2除法”方式去除,验证余数是否为0,如果为0,则证明该帧数据在传输过程中没有出现差错,否则出现了差错。

    通过以上CRC校验原理的剖析和CRC校验码的计算示例的介绍,大家应该对这种看似很复杂的CRC校验原理和计算方法应该比较清楚了。

3.    CRC校验码代码实现(c)

  1 // FCS_CRC.cpp : 定义控制台应用程序的入口点。
  2 //
  3 
  4 #include "stdafx.h"
  5 #include <stdio.h>
  6 #include <bitset>
  7 #include <string.h>
  8 using namespace std;
  9 
 10 
 11 int StrLen(char* src)
 12 {
 13     int count = 0;
 14     while(src[count])
 15         count++;
 16     return count;
 17 }
 18 
 19 bitset<16> SubBitset(bitset<16> src,int start,int num)//从start取num位
 20 {
 21     bitset<16> res;
 22     for(int i = 0;i<num;i++)
 23     {
 24         res[i] = src[start+i];
 25     }
 26     return res;
 27 }
 28 
 29 bitset<16> ConvertTOBit(char* src)
 30 {
 31     bitset<16> res;
 32     int bitnum;
 33     int flag,i;
 34     res.reset();//将所有位设置为0
 35     bitnum = StrLen(src);
 36     i = 0;
 37     flag = src[i] - '0';
 38     while(i<bitnum)
 39     {
 40         if(flag)
 41             res.set(bitnum-i-1);//将第i位置1
 42         i++;
 43         if(i!=bitnum)
 44             flag = src[i] - '0';
 45     }
 46     return res;
 47 }
 48 
 49 void ConvertTOString(char* res,bitset<16> src,int n)//n为bitset中有效的位数,避免将多余的0转换进string
 50 {
 51     for(int i = 0;i < n;i++)
 52     {
 53         res[n-1-i] = src[i] + '0';
 54     }
 55 }
 56 
 57 
 58 bitset<16> CaculateFCS(char* fcs,char* data,char* p)//计算fcs,返回加上fcs后的帧
 59 {
 60     int pnum;//p的位数
 61     int dividendnum;//被除数位数
 62     int datanum;//数据的位数
 63     int i = 0;//记录被除数的除到的位置
 64     bitset<16> bremainder;//余数
 65     bitset<16> btemp;
 66     bitset<16> bdata = ConvertTOBit(data);
 67     bitset<16> bp = ConvertTOBit(p);
 68 
 69     datanum = StrLen(data);
 70     pnum = StrLen(p);
 71     dividendnum = StrLen(data);
 72     bremainder.reset();
 73     btemp.reset();
 74     
 75     bdata = bdata<<(pnum-1);//数据左移n-1位
 76     dividendnum += pnum-1; 
 77 
 78 
 79     bremainder = SubBitset(bdata,dividendnum-pnum-i,pnum);
 80     bremainder ^= bp;
 81     i++;
 82 
 83     while(i <= dividendnum-pnum)
 84     {
 85         bremainder = bremainder<<1;
 86         btemp = SubBitset(bdata,dividendnum-pnum-i,1);
 87         bremainder[0] = btemp[0];//将除数的后一位加上
 88         i++;
 89         if(bremainder[pnum-1]==0)
 90         {
 91             continue;
 92         }
 93         else
 94         {
 95             bremainder ^= bp;
 96         }
 97     }
 98     ConvertTOString(fcs,bremainder,pnum-1);
 99     printf("remainder(fcs):%s\n",fcs);
100     for(i = 0;i < pnum-1;i++)
101     {
102         bdata[i] = bremainder[i];
103     }
104     ConvertTOString(data,bdata,datanum+pnum-1);
105     printf("发送帧:%s\n",data);
106 
107     return bdata;
108 }
109 
110 void PutError(bitset<16> &b)
111 {
112     b.flip(rand()%8);
113 }
114 
115 bool CRC(char* alldata,char* p)//alldata加上fcs的数据   p是除数
116 {
117 
118 
119     int pnum;//p的位数
120     int dividendnum;//被除数位数
121     int i = 0;//记录被除数的除到的位置
122     bitset<16> bremainder;//余数
123     bitset<16> btemp;
124     bitset<16> balldata = ConvertTOBit(alldata);
125     bitset<16> bp = ConvertTOBit(p);
126 
127     dividendnum = StrLen(alldata);
128     pnum = StrLen(p);
129     bremainder.reset();
130     btemp.reset();
131 
132     bremainder = SubBitset(balldata,dividendnum-pnum-i,pnum);
133     bremainder ^= bp;
134     i++;
135 
136     while(i <= dividendnum-pnum)
137     {
138         bremainder = bremainder<<1;
139         btemp = SubBitset(balldata,dividendnum-pnum-i,1);
140         bremainder[0] = btemp[0];//将除数的后一位加上
141         i++;
142         if(bremainder[pnum-1]==0)
143         {
144             continue;
145         }
146         else
147         {
148             bremainder ^= bp;
149         }
150     }
151     if(bremainder.to_ulong()!=0)
152     {
153         printf("数据出错!\n");
154     }
155     else
156     {
157         printf("CRC校验完成,数据正确!\n");
158     }
159     return bremainder.to_ulong()==0;
160 }
161 
162 int _tmain(int argc, _TCHAR* argv[])
163 {
164     char data[20]={0};
165     char p[20]={0};
166     char fcs[20]={0};
167     bitset<16> fps;//加上fcs的一帧数据
168     char alldata[20] = {0};
169     printf("insert some bytes.\n");
170     scanf("%s",data);
171     printf("insert a P.\n");
172     scanf("%s",p);
173     fps = CaculateFCS(fcs,data,p);
174     //PutError(fps);
175     ConvertTOString(alldata,fps,StrLen(data));
176     CRC(alldata,p);
177     return 0;
178 }

 

 

介绍部分转载自 “王达博客” 博客,代码由自己实现。