当前位置: 首页>編程日記>正文

CRC8/CRC16/CRC32最全总结

CRC8/CRC16/CRC32最全总结

CRC8/CRC16/CRC32最全总结

本文首发于“嵌入式软件实战派”。

循环冗余校验(英语:Cyclic redundancy check,通称“CRC”)是一种根据网络数据包或电脑文件等数据产生简短固定位数校验码的一种散列函数,主要用来检测或校验数据传输或者保存后可能出现的错误。

Wikipedia

一句话:CRC是将数据计算出散列的方式,一般用于校验数据的完整性。它具有简单、执行效率高等特点。当然,你可以类比于Checksum,但比Checksum复杂些,防碰撞性更好些。


▍CRC的原理

CRC是基于除法的。实际的输入数据会被解释为一个长二进制位流(除数),再将其除以另一个固定二进制数(除数,即多项式)。该除法的其余部分就是校验值。

但是,现实要复杂一些。二进制数(除数和除数)不被视为普通整数值,而是被视为二进制多项式,其中实际位用作系数。

输入数据,看做一串二进制流,用多项式的方式表示为g(x),而除数是国际标准上的多项式,用h(x)表示。通过g(x)和h(x)做除法,即两者的异或运算,得出的结果,就是我们所说的CRC运算检验结果。

那么,这里有两个疑问:

问题1:多项式和二进制是什么关系?

例如,1x3 + 0x2 + 1x + 1可以表示为1011b,或者1011b表示为1x3 + 0x2 + 1x + 1,其中是0的位,即以0为系数乘以该项。

问题2:除法怎么做?
本质就是在做异或运算

我们来看看一个数据串11010011101100除以x3 + x + 1是怎样进行的?

注意,根据CRC长度,需要将这个数据串补0

整个运算过程是这样的:

实际上就是逐个bit移位做异或。

详见:https://en.wikipedia.org/wiki/Cyclic_redundancy_check


▍CRC的概念

实际上,人们根据不同的需要,做了很多种计算方式,主要差别在于CRC长度、多项式、初始值、结果是否需要异或、是否需要翻转等等。

首先,来看看几个概念:

  • Length: CRC的长度(按bit算,如8,16,32)

  • Name: CRC的名字,让人一看就知道这是哪种CRC

  • Polinomial: 多项式,通过该多项式来计算CRC

  • InitialValue: CRC的初始值

  • FinalXorValue: CRC结果做异或运算的值

  • InputReflected: 指示输出是否需要翻转

  • OutputReflected: 指示输出是否需要翻转

我将网上搜到的所有的CRC标准分类做了个汇总:

从上面的CRC名称可以看出,不同的算法是有不同用途的,这是国际常规的用法定义,其实如果是用户自己用也没特别要求,可以自由点。


▍CRC的实现

1. 根据多项式二进制数,逐位做异或

按照前面章节的“CRC原理”来做C语言实现,就通过数据移位和异或算得。以CRC8为例,代码如下

#define CRC_POLYNOMIAL_8    0x0C
uint8 crc_8(uint8 crc, uint8* pdata, uint32 len)
{for (uint32 i = 0; i < len; i++){crc ^= pdata[i];for (uint8 j = 0; j < 8; j++){if ((crc & 0x80u) > 0){crc = ( (uint8)(crc << 1u) ) ^ CRC_POLYNOMIAL_8;}else{crc <<= 1u;}}}return crc;
}

这个代码中有个if ((crc & 0x80u) > 0)要解释下,因为任意数与0做异或,结果是其本身。所以,数据为0时,不做异或。同理,CRC16、CRC32也可以按这种方法做计算,只是多项式和数据位数不一样而已,在此不累述了。

按移位做异或的方法,有个缺点,就是用了两层循环,效率不是那么的高,在嵌入式软件中,不是那么受欢迎。

那需要怎么优化算法呢?那就将多项式预先算个表出来,查表吧。

2. 将多项式(Polynomial)生成一个256长度的表,用查表法实现

2.1 多项式生成的表

在开始这个话题之前,我们得回个头来看看那几个概念中有个InputReflectedOutputReflected。干啥呢,吃饱没事干啊,算就算,还要翻转?呵呵!

其实,通过多项式生成表就可以生成正序和逆序,就是为了应付这个“翻转”的。举个例子以CRC8_ITU(Polynomial=0x07)看看这个表:

正序表为

const unsigned char crc8_itu__table[] = 
{0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D, 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D, // ...... 省略部分0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83, 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3, 
};
 

逆序表为

const unsigned char crc8_itu_reversed_table[] = 
{0x00, 0x91, 0xE3, 0x72, 0x07, 0x96, 0xE4, 0x75, 0x0E, 0x9F, 0xED, 0x7C, 0x09, 0x98, 0xEA, 0x7B, 0x1C, 0x8D, 0xFF, 0x6E, 0x1B, 0x8A, 0xF8, 0x69, 0x12, 0x83, 0xF1, 0x60, 0x15, 0x84, 0xF6, 0x67, // ...... 省略部分0xA8, 0x39, 0x4B, 0xDA, 0xAF, 0x3E, 0x4C, 0xDD, 0xA6, 0x37, 0x45, 0xD4, 0xA1, 0x30, 0x42, 0xD3, 0xB4, 0x25, 0x57, 0xC6, 0xB3, 0x22, 0x50, 0xC1, 0xBA, 0x2B, 0x59, 0xC8, 0xBD, 0x2C, 0x5E, 0xCF, 
};

2.2 通用查表法

网上有很多种查表发计算CRC,不尽相同。于是我做了个通用版,满足各种算法,其中用了宏定义的奇技淫巧。直接上代码:

#define DEF_CRC_FUNC(func, width, crcTable)                                                         \
uint##width func(const uint8 *pdata, int len,                                                       \
uint##width initial, uint##width finalXor,                                                          \
BOOL inputReflected, BOOL resultReflected)                                                          \
{                                                                                                   \uint##width crc = initial;                                                                      \uint##width temp1 = 0, temp2 = 0, pos = 0;                                                      \const uint##width *pTable = crcTable;                                                           \for(int i = 0; i < len; i++)                                                                    \{                                                                                               \uint##width curByte = pdata[i];                                                             \if(inputReflected)                                                                          \{                                                                                           \curByte = Reflect8(pdata[i]);                                                             \}                                                                                           \/* update the MSB of crc value with next input byte */                                      \temp1 = (crc ^ (curByte << (width - 8)));                                                   \/* this MSB byte value is the index into the lookup table */                                \pos = (temp1 >> (width - 8)) & 0xFF;                                                        \/* shift out this index */                                                                  \temp2 = (temp1 << 8);                                                                       \/* XOR-in remainder from lookup table using the calculated index */                         \crc = (temp2 ^ pTable[pos]);                                                                \}                                                                                               \if(resultReflected)                                                                             \{                                                                                               \crc = Reflect##width(crc);                                                                  \}                                                                                               \return (crc ^ finalXor);                                                                        \
}

怎么用?

先定义,即定义对于位数算法的函数,通过预编译生成:

DEF_CRC_FUNC(gen_crc8, 8, crc8__table);
DEF_CRC_FUNC(gen_crc16_maxim, 16, crc16_maxim__table);
DEF_CRC_FUNC(gen_crc16_a, 16, crc16_a__table);
DEF_CRC_FUNC(gen_crc32_jamcrc, 32, crc32_jamcrc__table);

再调用,测试可以在main函数调用:

int main(void)
{const uint8 buf[6] = "123456";uint8 crc8 = gen_crc8(buf, 6, 0x00, 0x00, 0, 0);uint16 crc16_maxim = gen_crc16_maxim(buf, 6, 0x0000, 0xFFFF, 1, 1);uint16 crc16_a = gen_crc16_a(buf, 6, 0xC6C6, 0x0000, 1, 1);uint32 crc32_jamcrc = gen_crc32_jamcrc(buf, 6, 0xFFFFFFFF, 0x00000000, 1, 1);printf("crc8: %X\n", crc8);printf("crc16_maxim: %X\n", crc16_maxim);printf("crc16_a: %X\n", crc16_a);printf("crc32_jamcrc: %X\n", crc32_jamcrc);
}

这里还是有个问题,就那个“翻转”的问题,每个byte都做翻转,很浪费CPU资源啊。作为“优秀的”嵌入式软件开发人员,我们是追求卓越的算法,于是乎,就有了以下方式:

(1)对于InputReflectedOutputReflected都是False的,我们用正序表,以CRC8_ITU为例:

/*
CRC Info:
Name                 Polynomial Initial    FinalXor   InputReflected ResultReflected
CRC8_ITU             0x07       0x00       0x55       false          false    
*/
unsigned char crc8_itu(unsigned char crc, const unsigned char* buf, unsigned int len)
{for(unsigned int i = 0; i < len; i++){crc = crc8_itu__table[crc ^ buf[i]];}return crc^0x55;
}

(2)对于InputReflectedOutputReflected都是True的,我们用逆序表,以CRC8_EBU为例:

/*
CRC Info:
Name                 Polynomial Initial    FinalXor   InputReflected ResultReflected
CRC8_EBU             0x1D       0xFF       0x00       true           true           
*/
unsigned int crc8_ebu(unsigned int crc, const unsigned char* buf, unsigned int len)
{for(unsigned int i = 0; i < len; i++){crc = crc8_ebu_reversed_table[crc ^ buf[i]];}return crc^0x00;
}

▍CRC的源码

说了这么多,以上都是举例而已(其实直接将上面代码copy过去验证也是木有问题的,不要怀疑),上代码啊

Talk is cheap. Show me the code.

Linus Torvalds

我将上面表格中提到的所有CRC算法,都coding了,就像下图的这些

Name                 Polynomial Initial    FinalXor   InputReflected ResultReflected
CRC8                 0x07       0x00       0x00       false          false
CRC8_SAE_J1850       0x1D       0xFF       0xFF       false          false
CRC8_SAE_J1850_ZERO  0x1D       0x00       0x00       false          false
CRC8_8H2F            0x2F       0xFF       0xFF       false          false
CRC8_CDMA2000        0x9B       0xFF       0x00       false          false
CRC8_DARC            0x39       0x00       0x00       true           true
CRC8_DVB_S2          0xD5       0x00       0x00       false          false
CRC8_EBU             0x1D       0xFF       0x00       true           true
CRC8_ICODE           0x1D       0xFD       0x00       false          false
CRC8_ITU             0x07       0x00       0x55       false          false
CRC8_MAXIM           0x31       0x00       0x00       true           true
CRC8_ROHC            0x07       0xFF       0x00       true           true
CRC8_WCDMA           0x9B       0x00       0x00       true           true
CRC16_CCIT_ZERO      0x1021     0x0000     0x0000     false          false
CRC16_ARC            0x8005     0x0000     0x0000     true           true
CRC16_AUG_CCITT      0x1021     0x1D0F     0x0000     false          false
CRC16_BUYPASS        0x8005     0x0000     0x0000     false          false
CRC16_CCITT_FALSE    0x1021     0xFFFF     0x0000     false          false
CRC16_CDMA2000       0xC867     0xFFFF     0x0000     false          false
CRC16_DDS_110        0x8005     0x800D     0x0000     false          false
CRC16_DECT_R         0x0589     0x0000     0x0001     false          false
CRC16_DECT_X         0x0589     0x0000     0x0000     false          false
CRC16_DNP            0x3D65     0x0000     0xFFFF     true           true
CRC16_EN_13757       0x3D65     0x0000     0xFFFF     false          false
CRC16_GENIBUS        0x1021     0xFFFF     0xFFFF     false          false
CRC16_MAXIM          0x8005     0x0000     0xFFFF     true           true
CRC16_MCRF4XX        0x1021     0xFFFF     0x0000     true           true
CRC16_RIELLO         0x1021     0xB2AA     0x0000     true           true
CRC16_T10_DIF        0x8BB7     0x0000     0x0000     false          false
CRC16_TELEDISK       0xA097     0x0000     0x0000     false          false
CRC16_TMS37157       0x1021     0x89EC     0x0000     true           true
CRC16_USB            0x8005     0xFFFF     0xFFFF     true           true
CRC16_A              0x1021     0xC6C6     0x0000     true           true
CRC16_KERMIT         0x1021     0x0000     0x0000     true           true
CRC16_MODBUS         0x8005     0xFFFF     0x0000     true           true
CRC16_X_25           0x1021     0xFFFF     0xFFFF     true           true
CRC16_XMODEM         0x1021     0x0000     0x0000     false          false
CRC32                0x04C11DB7 0xFFFFFFFF 0xFFFFFFFF true           true
CRC32_BZIP2          0x04C11DB7 0xFFFFFFFF 0xFFFFFFFF false          false
CRC32_C              0x1EDC6F41 0xFFFFFFFF 0xFFFFFFFF true           true
CRC32_D              0xA833982B 0xFFFFFFFF 0xFFFFFFFF true           true
CRC32_MPEG2          0x04C11DB7 0xFFFFFFFF 0x00000000 false          false
CRC32_POSIX          0x04C11DB7 0x00000000 0xFFFFFFFF false          false
CRC32_Q              0x814141AB 0x00000000 0x00000000 false          false
CRC32_JAMCRC         0x04C11DB7 0xFFFFFFFF 0x00000000 true           true
CRC32_XFER           0x000000AF 0x00000000 0x00000000 false          false

举个例子,CRC32_BZIP2.h文件是的算法是这样的:

真以为我一个一个写的吗,哈哈哈,不是。但我肯定不是在网上一个一个抄的,网上也找不到这么多。

还是那句话:机器能干的事,为啥还要人来干!

我找到了个规律,写了个脚本生成的,多项式的正序表、逆序表是生成的,连代码,我都是用脚本生成的。

这里篇幅有限,就不将代码全部贴在这了,如果你对这些算法感兴趣,关注“嵌入式软件实战派”,聊天界面输入并发送“CRC”获得下载链接,或者点击[链接]下载。后续适当的时候,我还会将这个生成源文件的脚本也分享给大家。

为了验证其可靠性,我还做了个测试代码(篇幅有限,下面截取的代码已省略了部分):

// TEST:Caculate the string "123456", result list below with different crc models:crc_assert(0xfd ==  crc8                 (0x00, "123456", 6), "crc8"                  );// ...... 省略部分crc_assert(0x57 ==  crc8_rohc            (0xFF, "123456", 6), "crc8_rohc"             );crc_assert(0xab ==  crc8_wcdma           (0x00, "123456", 6), "crc8_wcdma"            );crc_assert(0x20e4 ==  crc16_ccit_zero   (0x0000, "123456", 6), "crc16_ccit_zero"    );crc_assert(0x29e4 ==  crc16_arc         (0x0000, "123456", 6), "crc16_arc"          );// ...... 省略部分crc_assert(0x32e4 ==  crc16_modbus      (0xFFFF, "123456", 6), "crc16_modbus"       );crc_assert(0xe672 ==  crc16_x_25        (0xFFFF, "123456", 6), "crc16_x_25"         );crc_assert(0x20e4 ==  crc16_xmodem      (0x0000, "123456", 6), "crc16_xmodem"       );crc_assert(0x0972d361 ==  crc32         (0xFFFFFFFF, "123456", 6), "crc32"          );// ...... 省略部分crc_assert(0xf036f1c2 ==  crc32_xfer    (0x00000000, "123456", 6), "crc32_xfer"     );

如果你怀疑我“监守自盗”,你还可以在网上找个在线计算的方式来验证算法的正确性,如:https://crccalc.com/

如果有其他想法和建议,请留言,我们一同探讨。

如果你喜欢我的文章,请扫码关注“嵌入式软件实战派”,我会分享更多技术干货。


https://www.fengoutiyan.com/post/15569.html

相关文章:

  • crc8和crc16区别
  • crc32和crc16选择
  • CRC16_CCITT和crc16区别
  • crc16计算公式
  • crc16 crc32
  • 口腔车针最全知识总结
  • crc32算法原理
  • CRC CRA
  • 鏡像模式如何設置在哪,圖片鏡像操作
  • 什么軟件可以把圖片鏡像翻轉,C#圖片處理 解決左右鏡像相反(旋轉圖片)
  • 手機照片鏡像翻轉,C#圖像鏡像
  • 視頻鏡像翻轉軟件,python圖片鏡像翻轉_python中鏡像實現方法
  • 什么軟件可以把圖片鏡像翻轉,利用PS實現圖片的鏡像處理
  • 照片鏡像翻轉app,java實現圖片鏡像翻轉
  • 什么軟件可以把圖片鏡像翻轉,python圖片鏡像翻轉_python圖像處理之鏡像實現方法
  • matlab下載,matlab如何鏡像處理圖片,matlab實現圖像鏡像
  • 圖片鏡像翻轉,MATLAB:鏡像圖片
  • 鏡像翻轉圖片的軟件,圖像處理:實現圖片鏡像(基于python)
  • canvas可畫,JavaScript - canvas - 鏡像圖片
  • 圖片鏡像翻轉,UGUI優化:使用鏡像圖片
  • Codeforces,CodeForces 1253C
  • MySQL下載安裝,Mysql ERROR: 1253 解決方法
  • 勝利大逃亡英雄逃亡方案,HDU - 1253 勝利大逃亡 BFS
  • 大一c語言期末考試試題及答案匯總,電大計算機C語言1253,1253《C語言程序設計》電大期末精彩試題及其問題詳解
  • lu求解線性方程組,P1253 [yLOI2018] 扶蘇的問題 (線段樹)
  • c語言程序設計基礎題庫,1253號C語言程序設計試題,2016年1月試卷號1253C語言程序設計A.pdf
  • 信奧賽一本通官網,【信奧賽一本通】1253:抓住那頭牛(詳細代碼)
  • c語言程序設計1253,1253c語言程序設計a(2010年1月)
  • 勝利大逃亡英雄逃亡方案,BFS——1253 勝利大逃亡
  • 直流電壓測量模塊,IM1253B交直流電能計量模塊(艾銳達光電)
  • c語言程序設計第三版課后答案,【渝粵題庫】國家開放大學2021春1253C語言程序設計答案
  • 18轉換為二進制,1253. 將數字轉換為16進制
  • light-emitting diode,LightOJ-1253 Misere Nim
  • masterroyale魔改版,1253 Dungeon Master
  • codeformer官網中文版,codeforces.1253 B
  • c語言程序設計考研真題及答案,2020C語言程序設計1253,1253計算機科學與技術專業C語言程序設計A科目2020年09月國家開 放大學(中央廣播電視大學)
  • c語言程序設計基礎題庫,1253本科2016c語言程序設計試題,1253電大《C語言程序設計A》試題和答案200901
  • 肇事逃逸車輛無法聯系到車主怎么辦,1253尋找肇事司機