CRC 的简介和应用(转载)

转自做而论道的百度空间 http://hi.baidu.com/do_sermon/item/6eb87a5425d25baeacc85783

CRC,Cyclic Redundancy Check,中文称为“循环冗余校验”。

它的应用很广,一般常见的说法都是用于通信。其实,在压缩、解压文件的时候,也普遍用到了它。

另外,单片机系统在掉电时,一般都要把当前有用的状态信息,保存在 EEPROM 中,为了保证信息的正确,也可以用 CRC 来检验。

1.CRC 的作用

还是用通信来说明 CRC 的作用。

由于线路上的干扰,通信时可能会有错码,那么当接收方收到了信息,怎么来确认这些信息就是正确的呢?
为了确认通信的正确性,发送方还要在信息之后,再发送一组“校验码”,这些校验码,是用前面的信息数据算出来的。
接收方收到信息数据和校验码之后,再按照同样的算法来计算,如果结果符合规则,就认为这次数据传输是正确的。

2.CRC 的计算规则

设信息数据 M(x)有 k 位,后面的 CRC 校验码为 r 位,那么总共发送的位数则为 n = k + r 位,因此,这种编码又叫(n, k)码。

利用 k 位信息码,求取 r 位 CRC 码时,需要先确定一个“生成多项式 G(x)”。

G(x) 也是一个二进制数,要求最高位和最低位都是 1 才行。

在不同的标准里面,G(x)的数值是不同的。常见的 G(x) 如下:

CRC-CCITT:  G(x) = X16 + X12 + X5 + 1 = 1 0001 0000 0010 0001 = 1 1021H
CRC-16:     G(x) = X16 + X15 + X2 + 1 = 1 1000 0000 0000 0101 = 1 8005H

CRC-32:     G(x) = X32+X26+X23+X22+X16+X12+X11+X10+X8+X7+X5+X4+X2+X1+1
                 = 1 0000 0100 1100 0001 0001 1101 1011 0111  = 04C1 1DB7H

选定了 G(x) 之后,就可以用信息数据 M(x) 除以 G(x),得出的余数 R(x) 就是 r 位的 CRC 码。
注意,这里使用除法是“模2除”,其实就是“异或”算法。

把 M(x) 和 r 位的 CRC 码合并在一起(即(n, k)码),再除以 G(x),得出的余数就是 0。
如果这组(n, k)码中含有错误,再除以 G(x),得出的余数就不是 0。

根据余数的状态,就可以判定传送的数据信息是否正确。

3.CRC 的算法举例

例:已知信息码为 M(x) = 1100,生成多项式 G(x) = x3 + x + 1,求 CRC 码,及(n, k)码。

解:

根据 G(x) = x3 + x + 1,可知除数为 1011;余数(CRC 码)应为 3 位二进制数。

那么,先在 M(x) 后面填写上 3 个 0,即为:1100 000,再用它“模2除” 1011。

现在填写的这些 0,将来是要用 CRC 码来填充,组成的(n, k)码的。这个步骤也可以写成:

    CRC 码 = M(x) * x3  /  G(x)

乘以 x3,就代表把 M(x) 左移 3 位,后面填上 3 个 0。 /,代表“模2除”。

计算的竖式如下图的左式:

 


在此得出的余数 R(x) = 010 即为 CRC 码。

那么,(n, k)码 = M(x) * x3 + R(x) = 1100 000 + 010 = 1100 010。

4.CRC 的验证

还是利用上面的例题来说明验证的方法。

当收到的代码是:1100 010,按照同样的方法求余数,竖式如上图中的右式。

余数为 0,就说明数据传输是正确的。

5.用 CRC 的检错

当有一位数是错误的,模2除的余数,就不是 0 了,可见下面的竖式:

 

可以看出,发生错误的位置不同,余数也不同。

使用不同的信息码 M(x) 来实验,能够看出,错误位置和余数的关系是固定的,不会因为信息码的不同而变化。

如果知道了错误的位置,是不是就可以纠错呢?不可,因为难以确定错误是不是仅仅有一位,错了更多的位,余数并不会对此有所表现。

因此,有人说利用 CRC 的方法可以纠错,这是不对的,CRC 只能用于检错,不能用于纠错。

CRC 检错的能力和生成多项式的位数有关,位数越多,检错的概率越高。

6.实用的 CRC 计算步骤

前面介绍的 CRC 算法,仅仅用于说明基本概念,实用时的计算,规模要大的多。
最常用的形式是:数据是 8 位数,CRC 校验码是 16 位数。

下面就详细描述一下从一个字节数据求出 16 位数 CRC 码的过程,从而得出编程的思路。

设 8 位的信息数据是 0x4A,G(x) 选用 CRC-CCITT,即 1 0001 0000 0010 0001 = 1 1021H,求 16 位的 CRC 竖式如下:


从竖式的计算过程中可以看出以下的特点:

(1)异或运算进行了多次,是从数据的高位到低位,依次判断进行的;

(2)信息数据中,有一个 1,就进行一次异或 11021H、是 0 就移位,判断下一位;
   信息数据中,只有 3 个 1,但是有一次的异或,在信息数据的范围中,异或出来一个 1,
   对于这个 1,也要进行一次异或 11021H 的运算,所以共进行了 4 次异或;

(3)异或后保留下来的余数,实际上仅仅是用 1021H 异或的结果;

(4)16 个 0,是从左边到右边,逐个参加运算。

7.实用的 CRC 汇编程序

看懂了上述的步骤,编写程序就是轻而易举的了。下面是用 51 单片机的汇编语言编写的 CRC 程序。

    ORG  0000H
    MOV  R2, P2      ;从P2输入数据,假设是4A
    CALL CRC         ;求取CRC
    MOV  P0, R2      ;输出CRC的高8位 E9
    MOV  P1, R3      ;输出CRC的低8位 8E
    NOP
    SJMP $
;------------------------------------
CRC:
    MOV  R3, #0      ;CRC低8位
    MOV  R4, #8
C1: CLR  C

    MOV  A,  R3      ;R2R3左移一位
    RLC  A
    MOV  R3,  A
    MOV  A,  R2
    RLC  A
    MOV  R2,  A
    JNC  C_NEXT      ;移出位为0就转移
    XRL  A,  #10H     ;为1就异或1021H
    MOV  R2, A
    MOV  A,  R3
    XRL  A,  #21H
    MOV  R3, A
C_NEXT:
    DJNZ R4, C1
    RET
;------------------------------------
END

呵呵,上述程序,仅仅用了 30 字节,执行时间平均为 114 个机器周期。

8.求取 CRC 的 C 程序

用 C 语言编程就可以规模大一些,借助计算机的屏幕,可以显示更多的内容。
下面就是显示数据00~FF 的全部 CRC 代码的程序。

//===============================================
#include<stdio.h>
#include<stdlib.h>

void main(void)
{
    unsigned char i;
    unsigned int  j, crc;
//--------------------------------------下面循环求出CRC
    for(j = 0; j < 256; j++)  {         //8位数据00~FF
      crc = 0;                          //每个数据的16位的CRC
      for(i = 0x80; i != 0; i >>= 1)  { //对8位数据从高位到低位进行判断
        if((crc & 0x8000) != 0)  {
          crc <<= 1;      crc ^= 0x1021;
        }
        else  crc = crc << 1;
        if((j & i) != 0)  crc ^= 0x1021;
      }
//--------------------------------------计算完毕,下面进行显示
      if (j % 4 == 0)  printf("\n");
      if (j % 32 == 0)      system("pause");
      printf("0x%02X: 0x%04x,  ", j, crc & 0xFFFF);
    }
}
//===============================================

上述程序执行后,显示的结果如下:

0x00: 0x0000,  0x01: 0x1021,  0x02: 0x2042,  0x03: 0x3063,
0x04: 0x4084,  0x05: 0x50a5,  0x06: 0x60c6,  0x07: 0x70e7,
0x08: 0x8108,  0x09: 0x9129,  0x0A: 0xa14a,  0x0B: 0xb16b,
0x0C: 0xc18c,  0x0D: 0xd1ad,  0x0E: 0xe1ce,  0x0F: 0xf1ef,
0x10: 0x1231,  0x11: 0x0210,  0x12: 0x3273,  0x13: 0x2252,
0x14: 0x52b5,  0x15: 0x4294,  0x16: 0x72f7,  0x17: 0x62d6,
0x18: 0x9339,  0x19: 0x8318,  0x1A: 0xb37b,  0x1B: 0xa35a,
0x1C: 0xd3bd,  0x1D: 0xc39c,  0x1E: 0xf3ff,  0x1F: 0xe3de,

0x20: 0x2462,  0x21: 0x3443,  0x22: 0x0420,  0x23: 0x1401,
0x24: 0x64e6,  0x25: 0x74c7,  0x26: 0x44a4,  0x27: 0x5485,
0x28: 0xa56a,  0x29: 0xb54b,  0x2A: 0x8528,  0x2B: 0x9509,
0x2C: 0xe5ee,  0x2D: 0xf5cf,  0x2E: 0xc5ac,  0x2F: 0xd58d,
0x30: 0x3653,  0x31: 0x2672,  0x32: 0x1611,  0x33: 0x0630,
0x34: 0x76d7,  0x35: 0x66f6,  0x36: 0x5695,  0x37: 0x46b4,
0x38: 0xb75b,  0x39: 0xa77a,  0x3A: 0x9719,  0x3B: 0x8738,
0x3C: 0xf7df,  0x3D: 0xe7fe,  0x3E: 0xd79d,  0x3F: 0xc7bc,

0x40: 0x48c4,  0x41: 0x58e5,  0x42: 0x6886,  0x43: 0x78a7,
0x44: 0x0840,  0x45: 0x1861,  0x46: 0x2802,  0x47: 0x3823,
0x48: 0xc9cc,  0x49: 0xd9ed,  0x4A: 0xe98e,  0x4B: 0xf9af,
0x4C: 0x8948,  0x4D: 0x9969,  0x4E: 0xa90a,  0x4F: 0xb92b,
0x50: 0x5af5,  0x51: 0x4ad4,  0x52: 0x7ab7,  0x53: 0x6a96,
0x54: 0x1a71,  0x55: 0x0a50,  0x56: 0x3a33,  0x57: 0x2a12,
0x58: 0xdbfd,  0x59: 0xcbdc,  0x5A: 0xfbbf,  0x5B: 0xeb9e,
0x5C: 0x9b79,  0x5D: 0x8b58,  0x5E: 0xbb3b,  0x5F: 0xab1a,

0x60: 0x6ca6,  0x61: 0x7c87,  0x62: 0x4ce4,  0x63: 0x5cc5,
0x64: 0x2c22,  0x65: 0x3c03,  0x66: 0x0c60,  0x67: 0x1c41,
0x68: 0xedae,  0x69: 0xfd8f,  0x6A: 0xcdec,  0x6B: 0xddcd,
0x6C: 0xad2a,  0x6D: 0xbd0b,  0x6E: 0x8d68,  0x6F: 0x9d49,
0x70: 0x7e97,  0x71: 0x6eb6,  0x72: 0x5ed5,  0x73: 0x4ef4,
0x74: 0x3e13,  0x75: 0x2e32,  0x76: 0x1e51,  0x77: 0x0e70,
0x78: 0xff9f,  0x79: 0xefbe,  0x7A: 0xdfdd,  0x7B: 0xcffc,
0x7C: 0xbf1b,  0x7D: 0xaf3a,  0x7E: 0x9f59,  0x7F: 0x8f78,

0x80: 0x9188,  0x81: 0x81a9,  0x82: 0xb1ca,  0x83: 0xa1eb,
0x84: 0xd10c,  0x85: 0xc12d,  0x86: 0xf14e,  0x87: 0xe16f,
0x88: 0x1080,  0x89: 0x00a1,  0x8A: 0x30c2,  0x8B: 0x20e3,
0x8C: 0x5004,  0x8D: 0x4025,  0x8E: 0x7046,  0x8F: 0x6067,
0x90: 0x83b9,  0x91: 0x9398,  0x92: 0xa3fb,  0x93: 0xb3da,
0x94: 0xc33d,  0x95: 0xd31c,  0x96: 0xe37f,  0x97: 0xf35e,
0x98: 0x02b1,  0x99: 0x1290,  0x9A: 0x22f3,  0x9B: 0x32d2,
0x9C: 0x4235,  0x9D: 0x5214,  0x9E: 0x6277,  0x9F: 0x7256,

0xA0: 0xb5ea,  0xA1: 0xa5cb,  0xA2: 0x95a8,  0xA3: 0x8589,
0xA4: 0xf56e,  0xA5: 0xe54f,  0xA6: 0xd52c,  0xA7: 0xc50d,
0xA8: 0x34e2,  0xA9: 0x24c3,  0xAA: 0x14a0,  0xAB: 0x0481,
0xAC: 0x7466,  0xAD: 0x6447,  0xAE: 0x5424,  0xAF: 0x4405,
0xB0: 0xa7db,  0xB1: 0xb7fa,  0xB2: 0x8799,  0xB3: 0x97b8,
0xB4: 0xe75f,  0xB5: 0xf77e,  0xB6: 0xc71d,  0xB7: 0xd73c,
0xB8: 0x26d3,  0xB9: 0x36f2,  0xBA: 0x0691,  0xBB: 0x16b0,
0xBC: 0x6657,  0xBD: 0x7676,  0xBE: 0x4615,  0xBF: 0x5634,

0xC0: 0xd94c,  0xC1: 0xc96d,  0xC2: 0xf90e,  0xC3: 0xe92f,
0xC4: 0x99c8,  0xC5: 0x89e9,  0xC6: 0xb98a,  0xC7: 0xa9ab,
0xC8: 0x5844,  0xC9: 0x4865,  0xCA: 0x7806,  0xCB: 0x6827,
0xCC: 0x18c0,  0xCD: 0x08e1,  0xCE: 0x3882,  0xCF: 0x28a3,
0xD0: 0xcb7d,  0xD1: 0xdb5c,  0xD2: 0xeb3f,  0xD3: 0xfb1e,
0xD4: 0x8bf9,  0xD5: 0x9bd8,  0xD6: 0xabbb,  0xD7: 0xbb9a,
0xD8: 0x4a75,  0xD9: 0x5a54,  0xDA: 0x6a37,  0xDB: 0x7a16,
0xDC: 0x0af1,  0xDD: 0x1ad0,  0xDE: 0x2ab3,  0xDF: 0x3a92,

0xE0: 0xfd2e,  0xE1: 0xed0f,  0xE2: 0xdd6c,  0xE3: 0xcd4d,
0xE4: 0xbdaa,  0xE5: 0xad8b,  0xE6: 0x9de8,  0xE7: 0x8dc9,
0xE8: 0x7c26,  0xE9: 0x6c07,  0xEA: 0x5c64,  0xEB: 0x4c45,
0xEC: 0x3ca2,  0xED: 0x2c83,  0xEE: 0x1ce0,  0xEF: 0x0cc1,
0xF0: 0xef1f,  0xF1: 0xff3e,  0xF2: 0xcf5d,  0xF3: 0xdf7c,
0xF4: 0xaf9b,  0xF5: 0xbfba,  0xF6: 0x8fd9,  0xF7: 0x9ff8,
0xF8: 0x6e17,  0xF9: 0x7e36,  0xFA: 0x4e55,  0xFB: 0x5e74,
0xFC: 0x2e93,  0xFD: 0x3eb2,  0xFE: 0x0ed1,  0xFF: 0x1ef0,

Press any key to continue
//===============================================

9.求取数组的 CRC

实际的数据传输,往往是要把几十个字节的信息数据,求出一个 16 位的 CRC 码,再把它附在信息的后面进行传送。

网上可以找到这样的 C 程序,做而论道加上了一些注释,如下所示:

//===============================================
unsigned char test[16] = {
    0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
    0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff};
unsigned char len = 16;
//----------------------------
unsigned int crc16l(unsigned char *ptr, unsigned char len)
{                              //ptr 为数据指针,len 为数据长度
    unsigned char i;
    unsigned int crc = 0;

    while(len--)   {           //按照数据的个数进行循环
      for(i = 0x80; i != 0; i >>= 1)   { //由高位到低位
        if((crc & 0x8000) != 0) { crc <<= 1;   crc ^= 0x1021; }
        else  crc <<= 1;
        if((*ptr & i) != 0 )  crc ^= 0x1021;//针对数据进行处理
      }
      ptr++;                   //下一个数据
    }
    return(crc);
}
//----------------------------
void main(void)
{
    printf("0x%04x", crc16l(test, len) & 0xFFFF);
}
//===============================================

程序运行后,将显示出来:0x1248,这就是数组中 16 个字节数据的 CRC 码。

10.在 51 单片机中的 CRC 应用

上述的 C 程序,也可以移植到单片机系统中,但是 C 程序的速度和占用空间就不敢恭维了。

针对同样一个字节(即0x4F)进行处理的时候,C 程序使用的时间是 263 T,大约是汇编程序的 2.5 倍 !

当然,也可以采用查表的方法,速度就可以提高几十倍,但是表格占用的空间,也是很可惜的。

所以,做而论道关于这方面的应用程序,都是用汇编语言编写的。

这种程序,虽然执行的效率较高,但是篇幅却较长。恐怕多数人都没有耐心看,所以做而论道的程序,也就不在此处公布了。

posted on 2014-01-21 14:45  诸葛瑞玙  阅读(1443)  评论(0编辑  收藏  举报

导航