收藏 分销(赏)

crc校验原理.doc

上传人:xrp****65 文档编号:7681589 上传时间:2025-01-12 格式:DOC 页数:21 大小:142KB
下载 相关 举报
crc校验原理.doc_第1页
第1页 / 共21页
crc校验原理.doc_第2页
第2页 / 共21页
点击查看更多>>
资源描述
校验原理 1、循环校验码(CRC码):是数据通信领域中最常用的一种差错校验码,其特征是信息字段和校验字段的长度可以任意选定。 2、生成CRC码的基本原理:任意一个由二进制位串组成的代码都可以和一个系数仅为‘0’和‘1’取值的多项式一一对应。例如:代码1010111对应的多项式为x6+x4+x2+x+1,而多项式为x5+x3+x2+x+1对应的代码101111。 3、CRC码集选择的原则:若设码字长度为N,信息字段为K位,校验字段为R位(N=K+R),则对于CRC码集中的任一码字,存在且仅存在一个R次多项式g(x),使得 V(x)=A(x)g(x)=xRm(x)+r(x); 其中:    m(x)为K次信息多项式, r(x)为R-1次校验多项式,          g(x)称为生成多项式: g(x)=g0+g1x+ g2x2+...+g(R-1)x(R-1)+gRxR 发送方通过指定的g(x)产生CRC码字,接收方则通过该g(x)来验证收到的CRC码字。 4、CRC校验码软件生成方法:     借助于多项式除法,其余数为校验字段。 例如:信息字段代码为: 1011001;对应m(x)=x6+x4+x3+1        假设生成多项式为:g(x)=x4+x3+1;则对应g(x)的代码为: 11001       x4m(x)=x10+x8+x7+x4 对应的代码记为:10110010000; 采用多项式除法:  得余数为: 1010     (即校验字段为:1010) 发送方:发出的传输字段为:  1 0 1 1 0 0 1 1 0 10                           信息字段       校验字段 接收方:使用相同的生成码进行校验:接收到的字段/生成码(二进制除法)                   如果能够除尽,则正确, CRC校验源码分析 这两天做项目,需要用到 CRC 校验。以前没搞过这东东,以为挺简单的。结果看看别人提供的汇编源程序,居然看不懂。花了两天时间研究了一下 CRC 校验,希望我写的这点东西能够帮助和我有同样困惑的朋友节省点时间。     先是在网上下了一堆乱七八遭的资料下来,感觉都是一个模样,全都是从 CRC 的数学原理开始,一长串的表达式看的我头晕。第一次接触还真难以理解。这些东西不想在这里讲,随便找一下都是一大把。我想根据源代码来分析会比较好懂一些。      费了老大功夫,才搞清楚 CRC 根据”权”(即多项表达式)的不同而相应的源代码也有稍许不同。以下是各种常用的权。   CRC8=X8+X5+X4+1   CRC-CCITT=X16+X12+X5+1   CRC16=X16+X15+X5+1             CRC12=X12+X11+X3+X2+1   CRC32=X32+X26+X23+X22+X16+X12+X11+X10+X8+X7+X5+X4+X2+X1+1       以下的源程序全部以 CCITT 为例。其实本质都是一样,搞明白一种,其他的都是小菜。   图 1,图 2 说明了 CRC 校验中 CRC 值是如何计算出来的,体现的多项式正是 X16+X12+X5+1。 Serial Data 即是需要校验的数据。从把数据移位开始计算,将数据位(从最低的数据位开始)逐位移入反向耦合移位寄存器(这个名词我也不懂,觉得蛮酷的,就这样写了,嘿)。当所有数据位都这样操作后,计算结束。此时,16 位移位寄存器中的内容就是 CRC 码。     图中进行 XOR 运算的位与多项式的表达相对应。 X5 代表 Bit5,X12 代表 Bit12,1 自然是代表 Bit0,X16 比较特别,是指移位寄存器移出的数据,即图中的DATA OUT。可以这样理解,与数据位做XOR运算的是上次 CRC值的 Bit15。 根据以上说明,可以依葫芦画瓢的写出以下程序。(程序都是在 keil C 7.10 下调试的)   typedef    unsigned char     uchar; typedef    unsigned int      uint;   code uchar crcbuff [] = { 0x00,0x00,0x00,0x00,0x06,0x0d,0xd2,0xe3}; uint crc;                  // CRC 码 void main(void) {   uchar *ptr;   crc = 0;                // CRC  初值   ptr = crcbuff;              //  指向第一个 Byte 数据   crc = crc16l(ptr,8);              while(1); }   uint crc16l(uchar *ptr,uchar len)        // ptr 为数据指针,len 为数据长度 {   uchar i;   while(len--)   {       for(i=0x80; i!=0; i>>=1)     {         if((crc&0x8000)!=0) {crc<<=1; crc^=0x1021;}        1-1             else crc<<=1;                     1-2       if((*ptr&i)!=0) crc^=0x1021;                       1-3       }     ptr++;   }   return(crc); }   执行结果 crc = 0xdbc0; 程序 1-1,1-2,1-3 可以理解成移位前 crc  的 Bit15 与数据对应的 Bit(*ptr&i)做 XOR运算,根据此结果来决定是否执行 crc^=0x1021。只要明白两次异或运算与原值相同,就不难理解这个程序。   很多资料上都写了查表法来计算,当时是怎么也没想通。其实蛮简单的。假设通过移位处理了 8 个 bit 的数据,相当于把之前的 CRC 码的高字节(8bit)全部移出,与一个 byte 的数据做XOR 运算,根据运算结果来选择一个值(称为余式),与原来的 CRC 码再做一次 XOR 运算,就可以得到新的 CRC 码。   不难看出,余式有 256 种可能的值,实际上就是 0~255 以 X16+X12+X5+1 为权得到的 CRC码,可以通过函数 crc16l来计算。以1 为例。   code test[]={0x01}; crc = 0; ptr = test; crc = crc16l(ptr,1);   执行结果 crc = 1021,这就是1 对应的余式。   进一步修改函数,我这里就懒得写了,可得到 X16+X12+X5+1 的余式表。   code uint crc_ta[256]={                // X16+X12+X5+1  余式表     0x0000, 0x1021,  0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,   0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,     0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,     0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,     0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,     0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,     0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,     0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,     0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,     0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,     0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,     0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,     0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,     0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,      0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,     0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,     0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,     0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,     0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,     0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,     0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,     0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,     0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,     0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,     0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,     0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,     0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,      0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,     0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,     0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,      0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,      0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 };   根据这个思路,可以写出以下程序: uint table_crc(uchar *ptr,uchar len)      //  字节查表法求 CRC {   uchar da;     while(len--!=0)   {       da=(uchar) (crc/256);        // 以 8 位二进制数暂存 CRC 的高 8 位         crc<<=8;                 //  左移 8 位       crc^=crc_ta[da^*ptr];        //  高字节和当前数据 XOR 再查表       ptr++;   }     return(crc); }   本质上 CRC 计算的就是移位和异或。所以一次处理移动几位都没有关系,只要做相应的处理就好了。 下面给出半字节查表的处理程序。其实和全字节是一回事。   code uint crc_ba[16]={      0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,   0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, };   uint ban_crc(uchar *ptr,uchar len) {   uchar da;   while(len--!=0)   {     da = ((uchar)(crc/256))/16;     crc <<= 4;     crc ^=crc_ba[da^(*ptr/16)];     da = ((uchar)(crc/256)/16);     crc <<= 4;     crc ^=crc_ba[da^(*ptr&0x0f)];     ptr++;   }   return(crc); }   crc_ba[16]和crc_ta[256]的前 16 个余式是一样的。   其实讲到这里,就已经差不多了。反正当时我以为自己是懂了。结果去看别人的源代码的时候,也是说采用 CCITT,但是是反相的。如图 3   反过来,一切都那么陌生,faint.吐血,吐血。 仔细分析一下,也可以很容易写出按位异或的程序。只不过由左移变成右移。   uint crc16r(unsigned char *ptr, unsigned char len) {   unsigned char i;   while(len--!=0)   {     for(i=0x01;i!=0;i <<= 1)     {       if((crc&0x0001)!=0) {crc >>= 1; crc ^= 0x8408;}       else crc >>= 1;       if((*ptr&i)!=0) crc ^= 0x8408;     }       ptr++;   }   return(crc); }   0x8408 就是 CCITT 的反转多项式。 套用别人资料上的话 “反转多项式是指在数据通讯时,信息字节先传送或接收低位字节,如重新排位影响 CRC计算速度,故设反转多项式。”   如   code uchar crcbuff [] = { 0x00,0x00,0x00,0x00,0x06,0x0d,0xd2,0xe3}; 反过来就是   code uchar crcbuff_fan[] = {0xe3,0xd2,0x0d,0x06,0x00,0x00,0x00,0x00};     crc = 0;   ptr = crcbuff_fan;   crc = crc16r(ptr,8);     执行结果 crc = 0x5f1d;     如想验证是否正确,可改   code uchar crcbuff_fan_result[] = {0xe3,0xd2,0x0d,0x06,0x00,0x00,0x00,0x00,0x1d,0x5f};   ptr = crcbuff_fan_result;   crc = crc16r(ptr,10);     执行结果 crc = 0;  符合 CRC 校验的原理。   请注意 0x5f1d 在数组中的排列中低位在前,正是反相运算的特点。不过当时是把我搞的晕头转向。     在用半字节查表法进行反相运算要特别注意一点,因为是右移,所以 CRC 移出的 4Bit与数据 XOR 的操作是在 CRC 的高位端。因此余式表的产生是要以下列数组通过修改函数crc16r 产生。   code uchar ban_fan[]= {0,0x10,0x20,0x30,0x40,0x50,0x60,0x70,0x80,0x90,0xa0,0xb0,0xc0,0xd0,0xe0,0xf0};     得出余式表 code uint fan_yushi[16]={     0x0000, 0x1081, 0x2102, 0x3183,     0x4204, 0x5285, 0x6306, 0x7387,     0x8408, 0x9489, 0xa50a, 0xb58b,     0xc60c, 0xd68d, 0xe70e, 0xf78f };   uint ban_fan_crc(uchar *ptr,uchar len) {   uchar da;   while(len--!=0)   {     da = (uchar)(crc&0x000f);     crc >>= 4;     crc ^= fan_yushi [da^(*ptr&0x0f)];     da = (uchar)(crc&0x000f);     crc >>= 4;     crc ^= fan_yushi [da^(*ptr/16)];     ptr++;     }   return(crc); }   主程序中   crc = 0;   ptr = crcbuff_fan;   crc = ban_fan_crc(ptr,8);      执行结果 crc = 0x5f1d;   反相运算的算法简单实现crc校验  前一段时间做协议转换器的时间用到CRC-16校验,查了不少资料发现都不理想。查表法要建表太麻烦,而计算法觉得那些例子太罗嗦。最后只好自己写了,最后发现原来挺简单嘛:) 两个子程序搞定。这里用的多项式为: CRC-16    = X16 + X12 + X5 + X0 = 2^0+2^5+2^12+2^16=0x11021 因最高位一定为“1”,故略去计算只采用0x1021即可 CRC_Byte:计算单字节的CRC值 CRC_Data:计算一帧数据的CRC值 CRC_High  CRC_Low:存放单字节CRC值 CRC16_High  CRC16_Low:存放帧数据CRC值 ;<>------------------------------------------------------------- ;      Function:       CRC one byte ;      Input:             CRCByte ;      Output:           CRC_High CRC_Low ;<>------------------------------------------------------------- CRC_Byte:        clrf         CRC_Low        clrf         CRC_High        movlw           09H        movwf           v_Loop1        movf              CRCByte, w        movwf           CRC_High CRC:        decfsz            v_Loop1                              ;8次循环,每一位相应计算        goto        CRC10        goto        CRCend CRC10        bcf                STATUS, C        rlf                  CRC_Low        rlf                  CRC_High                btfss              STATUS, C        goto        CRC                                          ;为0不需计算        movlw           10H                                    ;若多项式改变,这里作相应变化        xorwf            CRC_High, f        movlw           21H                                    ;若多项式改变,这里作相应变化        xorwf            CRC_Low, f        goto        CRC CRCend:        nop        nop        return ;<>------------------------------------------------------------- ;      CRC one byte end ;<>------------------------------------------------------------- ;<>------------------------------------------------------------- ;      Function:       CRC date ;      Input:             BufStart(A,B,C)(一帧数据的起始地址) v_Count (要做CRC的字节数) ;      Output:           CRC16_High CRC16_Low(结果) ;<>------------------------------------------------------------- CRC_Data:        clrf         CRC16_High        clrf         CRC16_Low CRC_Data10        movf              INDF, w        xorwf            CRC16_High,w        movwf           CRCByte        call         CRC_Byte        incf         FSR        decf        v_Count                       ;需计算的字节数                movf              CRC_High, w        xorwf            CRC16_Low, w        movwf           CRC16_High        movf              CRC_Low, w        movwf           CRC16_Low        movf              v_Count, w                                          ;计算结束?        btfss              STATUS, Z        goto        CRC_Data10        return ;<>------------------------------------------------------------- ;             CRC date end ;<>------------------------------------------------------------- 说明: CRC 的计算原理如下(一个字节的简单例子)     11011000 00000000 00000000  <- 一个字节数据, 左移 16b    ^10001000 00010000 1         <- CRC-CCITT 多项式, 17b     --------------------------      1010000 00010000 10        <- 中间余数     ^1000100 00001000 01      -------------------------        10100 00011000 1100       ^10001 00000010 0001        -----------------------          101 00011010 110100         ^100 01000000 100001          ---------------------            1 01011010 01010100           ^1 00010000 00100001            -------------------              01001010 01110101  <- 16b CRC 仿此,可推出两个字节数据计算如下:d 为数据,p 为项式,a 为余数     dddddddd dddddddd 00000000 00000000 <- 数据 D ( D1, D0, 0, 0 )    ^pppppppp pppppppp p                 <- 多项式 P     -----------------------------------     ...              aaaaaaaa aaaaaaaa 0        <- 第一次的余数 A’ ( A’1, A’0 )             ^pppppppp pppppppp p              --------------------------              ...                       aaaaaaaa aaaaaaaa <- 结果 A ( A1, A0 ) 由此与一字节的情况比较,将两个字节分开计算如下: 先算高字节:     dddddddd 00000000 00000000 00000000 <- D1, 0, 0, 0    ^pppppppp pppppppp p                 <- P     -----------------------------------     ...              aaaaaaaa aaaaaaaa          <- 高字节部分余数 PHA1, PHA0 此处的部分余数与前面两字节算法中的第一次余数有如下关系,即 A’1 = PHA1 ^ D0, A’0 = PHA0:              aaaaaaaa aaaaaaaa          <- PHA1, PHA0             ^dddddddd                   <- D0              -----------------              aaaaaaaa aaaaaaaa          <- A’1, A’0 低字节的计算:              aaaaaaaa 00000000 00000000 <- A’1, 0, 0             ^pppppppp pppppppp p        <- P              --------------------------              ...                       aaaaaaaa aaaaaaaa <- 低字节部分余数 PLA1, PLA0                      ^aaaaaaaa          <- A’0 , 即 PHA0                       -----------------                       aaaaaaaa aaaaaaaa <- 最后的 CRC ( A1, A0 ) 总结以上内容可得规律如下: 设部分余数函数     PA = f( d ) 其中 d 为一个字节的数据(注意,除非 n = 0 ,否则就不是原始数据,见下文) 第 n 次的部分余数     PA( n ) = ( PA( n - 1 ) << 8 ) ^ f( d ) 其中的     d = ( PA( n - 1 ) >> 8 ) ^ D( n ) 其中的 D( n ) 才是一个字节的原始数据。 公式如下:     PA( n ) = ( PA( n - 1 ) << 8 ) ^ f( ( PA( n - 1 ) >> 8 ) ^ D( n ) ) 可以注意到函数 f( d ) 的参数 d 为一个字节,对一个确定的多项式 P, f( d ) 的返回值 是与 d 一一对应的,总数为 256 项,将这些数据预先算出保存在表里,f( d )就转换为一 个查表的过程,速度也就可以大幅提高,这也就是查表法计算 CRC 的原理。 再来看 CRC 表是如何计算出来的,即函数 f( d ) 的实现方法。分析前面一个字节数据的 计算过程可发现,d 对结果的影响只表现为对 P 的移位异或,看计算过程中的三个 8 位 的列中只低两个字节的最后结果是余数,而数据所在的高 8 位列最后都被消去了,因其 中的运算均为异或,不产生进位或借位,故每一位数据只影响本列的结果,即 d 并不直接 影响结果。再将前例变化一下重列如下:     11011000     --------------------------     10001000 00010000 1        // P    ^ 1000100 00001000 01       // P    ^  000000 00000000 000      // 0    ^   10001 00000010 0001     // P    ^    0000 00000000 00000    // 0    ^     100 01000000 100001   // P    ^      00 00000000 0000000  // 0    ^       1 00010000 00100001 // P            -------------------              01001010 01110101 现在的问题就是如何根据 d 来对 P 移位异或了,从上面的例子看,也可以理解为每步 移位,但根据 d 决定中间余数是否与 P 异或。从前面原来的例子可以看出,决定的条件是中间余数的最高位为0,因为 P 的最高位一定为1,即当中间余数与 d 相应位异或的最高位为1时,中间余数移位就要和 P 异或,否则只需移位即可。其方法如下例(上例的变形,注意其中空格的移动表现了 d 的影响如何被排除在结果之外):     d --------a--------     1 00000000 00000000 <- HSB = 1       0000000 000000000 <- a <<= 1       0001000 000100001 <-不含最高位的 1       -----------------     1 0001000 000100001       001000 0001000010       000100 0000100001       -----------------     0 001100 0001100011 <- HSB = 0       01100 00011000110       -----------------     1 01100 00011000110 <- HSB = 1       1100 000110001100       0001 000000100001       -----------------     1 1101 000110101101 <- HSB = 0       101 0001101011010       -----------------     0 101 0001101011010 <- HSB = 1       01 00011010110100       00 01000000100001       -----------------     0 01 01011010010101 <- HSB = 0       1 010110100101010       -----------------
展开阅读全文

开通  VIP会员、SVIP会员  优惠大
下载10份以上建议开通VIP会员
下载20份以上建议开通SVIP会员


开通VIP      成为共赢上传
相似文档                                   自信AI助手自信AI助手

当前位置:首页 > 教育专区 > 其他

移动网页_全站_页脚广告1

关于我们      便捷服务       自信AI       AI导航        抽奖活动

©2010-2025 宁波自信网络信息技术有限公司  版权所有

客服电话:4009-655-100  投诉/维权电话:18658249818

gongan.png浙公网安备33021202000488号   

icp.png浙ICP备2021020529号-1  |  浙B2-20240490  

关注我们 :微信公众号    抖音    微博    LOFTER 

客服