飞道的博客

CRC校验详解(附代码示例)

3284人阅读  评论(0)

目录

1.CRC校验原理

2.生成多项式

3.以CRC-16校验为例讲解编程实现

3.3.1 完全按照CRC原理实现校验

3.3.2 工程中常用CRC校验过程

3.3.3 改进的CRC校验过程

4.以CRC-8校验为例讲解查表法

5.以CRC-16校验为例讲解查表法

5.1.生成表格

5.2.查表法实现

6.代码链接


CRC校验即循环冗余校验(Cyclic Redundancy Check),是基于数据计算一组效验码,用于核对数据传输过程中是否被更改或传输错误。首先看两个概念,后续会用到。

  • 模2除法:也叫模2运算,就是结果除以2后取余数。模2除法每一位除的结果不影响其它位,即不向上一位借位,所以实际上就是异或。在CRC计算中有应用到模2除法。
  • 多项式与二进制:二进制可表示成多项式的形式,比如二进制1101表示为: x3+x2+x0;1011表示为:x3+x1+x0。

1.CRC校验原理

CRC校验本质上是选取一个合适的除数,要进行校验的数据是被除数,然后做模2除法,得到的余数就是CRC校验值。

下面用具体的例子做讲解:给定一组数据A:10110011(二进制),选取除数B:11001。

  1. 首先需要在被除数A后加4个比特位0(具体加几个0是根据除数B的位数决定的,比如这里B是5位,那么A后面加4个0;如果B选6位,则A后面加5个0,总之加的0的个数比除数B的个数少1位。后面还会提到怎么添加)。
  2. 进行模2除法运算。注意每次都是模2运算,即异或。
  3. 最后得到余数C就是CRC校验值。注意余数位数必须比除数少1位,如果不够前面加0补齐。运算如下图所示

                   

2.生成多项式

第1章讲解了CRC校验的基本原理,通常我们把选取的除数称之为“生成多项式”。事实上,生成多项式的选取是由一定标准的,如果选的不好,那么检出错误的概率就会低很多。好在这个问题已经被专家们研究了很长一段时间了,对于我们这些使用者来说,只要把现成的成果拿来用就行了。下表是一些标准的CRC生成多项式,可以直接使用。

标准CRC生成多项式
名称 生成多项式 简记式
CRC-4 x4+x+1 0x03
CRC-8 x8+x5+x4+1 0x31
CRC-8 x8+x2+x1+1  0x07
CRC-8 x8+x6+x4+x3+x2+x1 0x5E
CRC-12 x12+x11+x3+x+1 0x080F
CRC-16 x16+x15+x2+1  0x8005
CRC16-CCITT x16+x12+x5+1 0x1021
CRC-32 x32+x26+x23+...+x2+x+1 0x04C11DB7

更多标准CRC生成式请参考https://en.wikipedia.org/wiki/Cyclic_redundancy_check

有一点要特别注意,文献中提到的生成多项式经常会说到多项式的位宽(Width,简记为W),这个位宽不是多项式对应的二进制数的位数,而是位数减1。比如CRC8中用到的位宽为8的生成多项式,其实对应得二进制数有九位:100110001。另外一点,多项式表示和二进制表示都很繁琐,交流起来不方便,因此,文献中多用16进制简写法来表示,因为生成多项式的最高位肯定为1,最高位的位置由位宽可知,故在简记式中,将最高的1统一去掉了,如CRC32的生成多项式简记为04C11DB7实际上表示的是104C11DB7。当然,这样简记除了方便外,在编程计算时也有它的用处。所以在第一章中提到的在被除数后增加0的位数就是位宽,计算出的CRC校验值长度也是位宽。

3.以CRC-16校验为例讲解编程实现

3.3.1 完全按照CRC原理实现校验

实际工程中多使用CRC-16校验,即选取生成多项式为0x8005。按照前面提到的CRC校验原理,编程实现步骤如下:(注意实际编程时并不用这种直接的方法,如不想看可直接跳到3.3.2

  1. 预置1个16位的变量为CRC,此值存放CRC校验值,赋初值为0;
  2. 将需要校验的字符串str后面添加16个0;
  3. 如果变量CRC最高位为1,变量CRC与0x8005异或,然后将变量CRC左移1位,最低位补入1比特新的数据(来自需要校验的字符串str);
  4. 如果变量CRC最高位为0,直接将变量CRC左移1位,最低位补入1比特新的数据(来自需要校验的字符串str);
  5. 重复2-3步,直到字符串str最后1位补入变量CRC中;
  6. 此时得到的余数就是CRC校验值。

这种直接的方法有一个弊端,那就是在字符串前面加0,并不影响校验值,这就不符合我们的预期了。比如,我们想校验的1字节1001 1100,现在在前面补1字节0,变成2字节0000 0000 1001 1100,结果两个得到的校验值是一样的。所以在实际应用中,CRC校验过程做了一些改变:增加了“余数初始值”、“结果异或值”、“输入数据反转”和“输出数据反转”四个概念。

3.3.2 工程中常用CRC校验过程

  • 余数初始值:即在计算开始前,先给变量CRC赋的初值。
  • 结果异或值:即在计算结束后,得到的变量CRC与这个值进行异或操作,就得到了最终的校验值。
  • 输入数据反转:即在计算开始前,将需要校验的数据反转,如数据位1011,反转后为1101。
  • 输出数据反转:即在计算结束后,与结果异或值异或之前,计算值反转,如计算结果为1011,反转后为1101。

实际应用中,生成多项式、余数初始值、结果异或值、输入数据反转和输出数据反转是有对应关系的,这些对应关系是大家都遵守的标准,如下表所示:

表3-1 常见CRC参数模型
CRC算法名称 多项式公式 宽度 多项式(16进制) 初始值(16进制) 结果异或值(16进制) 输入值反转 输出值反转
CRC-4/ITU x4 + x + 1 4 03 00 00 true true
CRC-5/EPC x4 + x3 + 1 5 09 09 00 false false
CRC-5/ITU x5 + x4 + x2 + 1 5 15 00 00 true true
CRC-5/USB x5 + x2 + 1 5 05 1F 1F true true
CRC-6/ITU x6 + x + 1 6 03 00 00 true true
CRC-7/MMC x7 + x3 + 1 7 09 00 00 false false
CRC-8 x8 + x2 + x + 1 8 07 00 00 false false
CRC-8/ITU x8 + x2 + x + 1 8 07 00 55 false false
CRC-8/ROHC x8 + x2 + x + 1 8 07 FF 00 true true
CRC-8/MAXIM x8 + x5 + x4 + 1 8 31 00 00 true true
CRC-16/IBM x16 + x15 + x2 + 1 16 8005 0000 0000 true true
CRC-16/MAXIM x16 + x15 + x2 + 1 16 8005 0000 FFFF true true
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-16/CCITT x16 + x12 + x5 + 1 16 1021 0000 0000 true true
CRC-16/CCITT-FALSE x16 + x12 + x5 + 1 16 1021 FFFF 0000 false false
CRC-16/x5 x16 + x12 + x5 + 1 16 1021 FFFF FFFF true true
CRC-16/XMODEM x16 + x12 + x5 + 1 16 1021 0000 0000 false false
CRC-16/DNP x16 + x13 + x12 + x11 + x10 + x8 + x6 + x5 + x2 + 1 16 3D65 0000 FFFF 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
CRC-32/MPEG-2 x32 + x26 + x23 + x22 + x16 + x12 + x11 + x10 + x8 + x7 + x5 + x4 + x2 + x + 1 32 04C11DB7 FFFFFFFF 00000000 false false

接下来以CRC-16/IBM校验为例,讲解工程中使用的CRC校验编程实现。具体实现时,以字节为单位进行计算。

  1. 预置1个16位的变量CRC,存放校验值,首先根据表3-1赋初值0x0000;
  2. 将第1个字节按照表3-1看是否需要反转,若需要,则按反转,若不需要,直接进入第3步。这里需要反转;
  3. 把第1个字节按照步骤2处理后,与16位的变量CRC的高8位相异或,把结果放于变量CRC,低8位数据不变;
  4. 把变量CRC的内容左移1位(朝高位)用0填补最低位,并检查左移后的移出位;
  5. 如果移出位为0:重复第4步(再次左移一位);如果移出位为1,变量CRC与多项式8005(1000 0000 0000 0101)进行异或;
  6. 重复步骤4和5,直到左移8次,这样整个8位数据全部进行了处理;
  7. 重复步骤2到步骤6,进行通讯信息帧下一个字节的处理;
  8. 将该通讯信息帧所有字节按上述步骤计算完成后,将得到的16位变量CRC按照表3-1看是否需要反转,这里需要反转;
  9. 最后,与结果异或值异或,得到的变量CRC即为CRC校验值;

我在这里按照如上方法整理了一个通用代码,包含CRC-8,CRC-16和CRC-32。代码如下:

type.h


  
  1. #ifndef __TYPE_H__
  2. #define __TYPE_H__
  3. #include <stdio.h>
  4. typedef unsigned char u8;
  5. typedef unsigned short u16;
  6. typedef unsigned int u32;
  7. typedef unsigned long long u64;
  8. typedef unsigned char BOOL;
  9. #define FALSE 0
  10. #define TRUE 1
  11. #endif

crc.h文件


  
  1. #ifndef __CRC_H__
  2. #define __CRC_H__
  3. #include "type.h"
  4. typedef struct
  5. {
  6. u8 poly; //多项式
  7. u8 InitValue; //初始值
  8. u8 xor; //结果异或值
  9. BOOL InputReverse;
  10. BOOL OutputReverse;
  11. }CRC_8;
  12. typedef struct
  13. {
  14. u16 poly; //多项式
  15. u16 InitValue; //初始值
  16. u16 xor; //结果异或值
  17. BOOL InputReverse;
  18. BOOL OutputReverse;
  19. }CRC_16;
  20. typedef struct
  21. {
  22. u32 poly; //多项式
  23. u32 InitValue; //初始值
  24. u32 xor; //结果异或值
  25. BOOL InputReverse;
  26. BOOL OutputReverse;
  27. }CRC_32;
  28. const CRC_8 crc_8;
  29. const CRC_8 crc_8_ITU;
  30. const CRC_8 crc_8_ROHC;
  31. const CRC_8 crc_8_MAXIM;
  32. const CRC_16 crc_16_IBM;
  33. const CRC_16 crc_16_MAXIM;
  34. const CRC_16 crc_16_USB;
  35. const CRC_16 crc_16_MODBUS;
  36. const CRC_16 crc_16_CCITT;
  37. const CRC_16 crc_16_CCITT_FALSE;
  38. const CRC_16 crc_16_X5;
  39. const CRC_16 crc_16_XMODEM;
  40. const CRC_16 crc_16_DNP;
  41. const CRC_32 crc_32;
  42. const CRC_32 crc_32_MPEG2;
  43. u8 crc8(u8 *addr, int num,CRC_8 type) ;
  44. u16 crc16(u8 *addr, int num,CRC_16 type) ;
  45. u32 crc32(u8 *addr, int num,CRC_32 type) ;
  46. #endif

crc.c文件


  
  1. #include <stdio.h>
  2. #include "type.h"
  3. #include "CRC.h"
  4. const CRC_8 crc_8 = { 0x07, 0x00, 0x00,FALSE,FALSE};
  5. const CRC_8 crc_8_ITU = { 0x07, 0x00, 0x55,FALSE,FALSE};
  6. const CRC_8 crc_8_ROHC = { 0x07, 0xff, 0x00,TRUE,TRUE};
  7. const CRC_8 crc_8_MAXIM = { 0x31, 0x00, 0x00,TRUE,TRUE};
  8. const CRC_16 crc_16_IBM = { 0x8005, 0x0000, 0x0000,TRUE,TRUE};
  9. const CRC_16 crc_16_MAXIM = { 0x8005, 0x0000, 0xffff,TRUE,TRUE};
  10. const CRC_16 crc_16_USB = { 0x8005, 0xffff, 0xffff,TRUE,TRUE};
  11. const CRC_16 crc_16_MODBUS = { 0x8005, 0xffff, 0x0000,TRUE,TRUE};
  12. const CRC_16 crc_16_CCITT = { 0x1021, 0x0000, 0x0000,TRUE,TRUE};
  13. const CRC_16 crc_16_CCITT_FALSE = { 0x1021, 0xffff, 0x0000,FALSE,FALSE};
  14. const CRC_16 crc_16_X5 = { 0x1021, 0xffff, 0xffff,TRUE,TRUE};
  15. const CRC_16 crc_16_XMODEM = { 0x1021, 0x0000, 0x0000,FALSE,FALSE};
  16. const CRC_16 crc_16_DNP = { 0x3d65, 0x0000, 0xffff,TRUE,TRUE};
  17. const CRC_32 crc_32 = { 0x04c11db7, 0xffffffff, 0xffffffff,TRUE,TRUE};
  18. const CRC_32 crc_32_MPEG2 = { 0x04c11db7, 0xffffffff, 0x00000000,FALSE,FALSE};
  19. /*****************************************************************************
  20. *function name:reverse8
  21. *function: 字节反转,如1100 0101 反转后为1010 0011
  22. *input:1字节
  23. *output:反转后字节
  24. ******************************************************************************/
  25. u8 reverse8(u8 data)
  26. {
  27. u8 i;
  28. u8 temp= 0;
  29. for(i= 0;i< 8;i++) //字节反转
  30. temp |= ((data>>i) & 0x01)<<( 7-i);
  31. return temp;
  32. }
  33. /*****************************************************************************
  34. *function name:reverse16
  35. *function: 双字节反转,如1100 0101 1110 0101反转后为1010 0111 1010 0011
  36. *input:双字节
  37. *output:反转后双字节
  38. ******************************************************************************/
  39. u16 reverse16(u16 data)
  40. {
  41. u8 i;
  42. u16 temp= 0;
  43. for(i= 0;i< 16;i++) //反转
  44. temp |= ((data>>i) & 0x0001)<<( 15-i);
  45. return temp;
  46. }
  47. /*****************************************************************************
  48. *function name:reverse32
  49. *function: 32bit字反转
  50. *input:32bit字
  51. *output:反转后32bit字
  52. ******************************************************************************/
  53. u32 reverse32(u32 data)
  54. {
  55. u8 i;
  56. u32 temp= 0;
  57. for(i= 0;i< 32;i++) //反转
  58. temp |= ((data>>i) & 0x01)<<( 31-i);
  59. return temp;
  60. }
  61. /*****************************************************************************
  62. *function name:crc8
  63. *function: CRC校验,校验值为8位
  64. *input:addr-数据首地址;num-数据长度(字节);type-CRC8的算法类型
  65. *output:8位校验值
  66. ******************************************************************************/
  67. u8 crc8(u8 *addr, int num,CRC_8 type)
  68. {
  69. u8 data;
  70. u8 crc = type.InitValue; //初始值
  71. int i;
  72. for (; num > 0; num--)
  73. {
  74. data = *addr++;
  75. if(type.InputReverse == TRUE)
  76. data = reverse8(data); //字节反转
  77. crc = crc ^ data ; //与crc初始值异或
  78. for (i = 0; i < 8; i++) //循环8位
  79. {
  80. if (crc & 0x80) //左移移出的位为1,左移后与多项式异或
  81. crc = (crc << 1) ^ type.poly;
  82. else //否则直接左移
  83. crc <<= 1;
  84. }
  85. }
  86. if(type.OutputReverse == TRUE) //满足条件,反转
  87. crc = reverse8(crc);
  88. crc = crc^type. xor; //最后返与结果异或值异或
  89. return(crc); //返回最终校验值
  90. }
  91. /*****************************************************************************
  92. *function name:crc16
  93. *function: CRC校验,校验值为16位
  94. *input:addr-数据首地址;num-数据长度(字节);type-CRC16的算法类型
  95. *output:16位校验值
  96. ******************************************************************************/
  97. u16 crc16(u8 *addr, int num,CRC_16 type)
  98. {
  99. u8 data;
  100. u16 crc = type.InitValue; //初始值
  101. int i;
  102. for (; num > 0; num--)
  103. {
  104. data = *addr++;
  105. if(type.InputReverse == TRUE)
  106. data = reverse8(data); //字节反转
  107. crc = crc ^ (data<< 8) ; //与crc初始值高8位异或
  108. for (i = 0; i < 8; i++) //循环8位
  109. {
  110. if (crc & 0x8000) //左移移出的位为1,左移后与多项式异或
  111. crc = (crc << 1) ^ type.poly;
  112. else //否则直接左移
  113. crc <<= 1;
  114. }
  115. }
  116. if(type.OutputReverse == TRUE) //满足条件,反转
  117. crc = reverse16(crc);
  118. crc = crc^type. xor; //最后返与结果异或值异或
  119. return(crc); //返回最终校验值
  120. }
  121. /*****************************************************************************
  122. *function name:crc32
  123. *function: CRC校验,校验值为32位
  124. *input:addr-数据首地址;num-数据长度(字节);type-CRC32的算法类型
  125. *output:32位校验值
  126. ******************************************************************************/
  127. u32 crc32(u8 *addr, int num,CRC_32 type)
  128. {
  129. u8 data;
  130. u32 crc = type.InitValue; //初始值
  131. int i;
  132. for (; num > 0; num--)
  133. {
  134. data = *addr++;
  135. if(type.InputReverse == TRUE)
  136. data = reverse8(data); //字节反转
  137. crc = crc ^ (data<< 24) ; //与crc初始值高8位异或
  138. for (i = 0; i < 8; i++) //循环8位
  139. {
  140. if (crc & 0x80000000) //左移移出的位为1,左移后与多项式异或
  141. crc = (crc << 1) ^ type.poly;
  142. else //否则直接左移
  143. crc <<= 1;
  144. }
  145. }
  146. if(type.OutputReverse == TRUE) //满足条件,反转
  147. crc = reverse32(crc);
  148. crc = crc^type. xor; //最后返与结果异或值异或
  149. return(crc); //返回最终校验值
  150. }

调用时,只需传入相应的参数即可。经验证全部正确。如有疑问请评论留言。

3.3.3 改进的CRC校验过程

3.3.2中的代码具有通用性,但是可以看到效率不高。以crc16函数为例,需要判断字节是否需要反转,结束时,也需要判断是否需要反转,这都会耗费时间,如果需要反转,那么反转函数要花费更多时间。如何能提高效率呢?实际中我们常用某一种具体的校验方法,所以可以写单独的代码而非通用的,这样就可以省去两次判断反转的时间。以crc16/MAXIM为例,开始和结束都需要反转,改进后可以省略,具体操作如下:


  
  1. / **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *
  2. *function name:crc16 _MAXIM
  3. *function: CRC校验,校验值为16位
  4. *input:addr-数据首地址;num-数据长度(字节)
  5. *output:16位校验值
  6. ******************************************************************************/
  7. u16 crc16_MAXIM(u8 *addr, int num)
  8. {
  9. u8 data;
  10. u16 crc = 0x0000;//初始值
  11. int i;
  12. for (; num > 0; num--)
  13. {
  14. crc = crc ^ (*addr++) ; //低8位异或
  15. for (i = 0; i < 8; i++)
  16. {
  17. if (crc & 0x0001) //由于前面和后面省去了反转,所以这里是右移,且异或的值为多项式的反转值
  18. crc = (crc >> 1) ^ 0xA001;//右移后与多项式反转后异或
  19. else //否则直接右移
  20. crc >>= 1;
  21. }
  22. }
  23. return(crc^0xffff); //返回校验值
  24. }

读者可对比通用代码中crc16函数和crc16_MAXIM函数的区别。

4.以CRC-8校验为例讲解查表法

查表法速度快,但是表要占一部分内存,这是以空间换时间。

为了便于理解查表法,首先看3.3.2中CRC.c中u8 crc8(u8 *addr, int num,CRC_8 type)函数。为了方便观察,我将此段函数单独放在下面:


  
  1. / **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *
  2. *function name:crc8
  3. *function: CRC校验,校验值为8位
  4. *input:addr-数据首地址;num-数据长度(字节);type-CRC8的算法类型
  5. *output:8位校验值
  6. **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **/
  7. u8 crc8(u8 *addr, int num,CRC_8 type)
  8. {
  9. u8 data;
  10. u8 crc = type.InitValue; //初始值
  11. int i;
  12. for (; num > 0; num--)
  13. {
  14. data = *addr++;
  15. if(type.InputReverse == TRUE)
  16. data = reverse8(data); //字节反转
  17. crc = crc ^ data ; //与crc初始值异或
  18. for (i = 0; i < 8; i++) //循环8
  19. {
  20. if (crc & 0x80) //左移移出的位为1,左移后与多项式异或
  21. crc = (crc << 1) ^ type.poly;
  22. else //否则直接左移
  23. crc <<= 1;
  24. }
  25. }
  26. if(type.OutputReverse == TRUE) //满足条件,反转
  27. crc = reverse8(crc);
  28. crc = crc^type.xor; //最后返与结果异或值异或
  29. return(crc); //返回最终校验值
  30. }

观察上面代码,仔细发现18-24行的for循环可以生成一个表。进入for循环的变量crc大小为1个字节,即变量crc的大小可以为0-255之间的任何一个值,并且也只能是0-255之间的一个值。因此,可以实现将0-255都经过这个for循环,生成对应的值,生成的值也是0-255,这些生成的值就是crc要查的表,而传入for循环的变量crc就是表的索引值

以下代码是生成8位crc表的代码(多项式为0x07,核心的代码为11-17行for循环):


  
  1. void GenerateCrc8Table(u8 *crc8Table)
  2. {
  3. u8 crc= 0;
  4. u16 i,j;
  5. for(j = 0;j< 256;j++)
  6. {
  7. if(!(j% 16)) //16个数为1行
  8. printf( "\r\n");
  9. crc = (u8)j;
  10. for (i = 0; i < 8; i++)
  11. {
  12. if (crc & 0x80) //最高位为1
  13. crc = (crc << 1) ^ 0x07; //左移后与多项式异或
  14. else //否则直接左移
  15. crc <<= 1;
  16. }
  17. crc8Table[j] = crc; //取低字节
  18. printf( "%2x ",crc);
  19. }
  20. printf( "\r\n");
  21. }

生成的表如下:

需要注意的是,查表法所用的表根据多项式的不同而不同,所以再用不同多项式时,一定要重新生成对应的表。所以多项式为0x07的u8 crc8函数用查表法可以改写为如下:


  
  1. / **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *
  2. *function name:crc8withTable
  3. *function: CRC校验,校验值为8位
  4. *input:addr-数据首地址;len-数据长度(字节);crc8Table-CRC8表
  5. *output:8位校验值
  6. **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **/
  7. u8 crc8withTable(u8 *addr, int len,u8 *crc8Table)
  8. {
  9. u8 data;
  10. u8 crc = 00; //初始值
  11. int i;
  12. for (; len > 0; len--)
  13. {
  14. data = *addr++;
  15. crc = crc ^ data ; //与crc初始值异或
  16. crc = crc8Table[crc]; //替换下面for循环
  17. // for (i = 0; i < 8; i++) //循环8
  18. // {
  19. // if (crc & 0x80) //左移移出的位为1,左移后与多项式异或
  20. // crc = (crc << 1) ^ 0x07;
  21. // else //否则直接左移
  22. // crc <<= 1;
  23. // }
  24. }
  25. crc = crc^0x00; //最后返与结果异或值异或
  26. return(crc); //返回最终校验值
  27. }

可以看到第16行代码替换了for循环,直接从表中取值,速度快了很多。实际在用时,需要将表写成数组放在代码前面,这样代码就可以查表了。

5.以CRC-16校验为例讲解查表法

crc16查表法和crc8查表法类似,也是首先是生成一个表,然后再用查表代替for循环。

5.1.生成表格

以3.3.3中的CRC16代码为例,首先生成一个表,此表每个元素都是2字节,一共256个元素。但是需要将这个表拆分成两个表,其中高字节放在crcLowTable,低字节放在crcHighTable(我也不理解为什么这样做,但是按照这样的方法确实能实现查表法)。生成表代码如下:


  
  1. / **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *
  2. *function name:GenerateCrc16Table
  3. *function: 生成crc16查表法用的表格
  4. *input: crcHighTable,crcLowTable:256大小的数组,即生成的表格首地址
  5. *output:无
  6. **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **/
  7. void GenerateCrc16Table(u8 *crcHighTable,u8 *crcLowTable)
  8. {
  9. u16 crc=0;
  10. u16 i,j;
  11. for(j = 0;j<256;j++)
  12. {
  13. if(!(j%8))
  14. printf("\r\n");
  15. crc = j;
  16. for (i = 0; i < 8; i++)
  17. {
  18. if (crc & 0x0001) //由于前面和后面省去了反转,所以这里是右移,且异或的值为多项式的反转值
  19. crc = (crc >> 1) ^ 0xA001;//右移后与多项式反转后异或
  20. else //否则直接右移
  21. crc >>= 1;
  22. }
  23. crcHighTable[j] = (u8)(crc&0xff);//取低字节
  24. crcLowTable[j] = (u8)((crc>>8)&0xff);//取高字节
  25. printf("%4x ",crc);
  26. }
  27. printf("\r\n");
  28. }

生成表如下:

整体表

      

拆分后低字节 crcHighTable          拆分后高字节crcLowTable

5.2.查表法实现

有了表格后,就可以实现查表法了(为什么用15-17行代码我也不清楚,这里可能涉及到推倒,欢迎大神们在评论区指导),代码如下:


  
  1. / **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** *
  2. *function name:Crc16withTable
  3. *function: 用查表法计算CRC
  4. *input: addr:字符串起始地址;len :字符串长度;table:用到的表格
  5. *output:无
  6. **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **** **/
  7. u16 Crc16withTable(u8 *addr, int len,u8 *crcHighTable,u8 *crcLowTable)
  8. {
  9. u8 crcHi = 0x00;
  10. u8 crcLo = 0x00;
  11. u8 index;
  12. u16 crc;
  13. for (;len>0;len--)
  14. {
  15. index = crcLo ^ *(addr++);//低8位异或,得到表格索引值
  16. crcLo = crcHi ^ crcHighTable[index];
  17. crcHi = crcLowTable[index];
  18. }
  19. crc = (u16)(crcHi<<8 | crcLo);
  20. return(crc^0xffff);//返回校验值
  21. }

大家可以自行验证,将3.3.3代码和5.2代码计算结果作比较。结果是一样的。实际在用时,需要将表写成数组放在代码前面,这样代码就可以查表了。

6.代码链接

https://download.csdn.net/download/u013073067/13138335

编译环境:VS2010

语言:C

如有疑问,欢迎大家在评论区留言讨论。

参考文献:

[1]https://www.cnblogs.com/sinferwu/p/7904279.html

[2]https://en.wikipedia.org/wiki/Cyclic_redundancy_check

[3]https://www.cnblogs.com/94cool/p/3559585.html


转载:https://blog.csdn.net/u013073067/article/details/86621770
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场