關於CRC8/CRC16/CRC32,你要找的全部在這

2021-01-19 嵌入式軟體實戰派

↑點擊上方藍色字體,關注「嵌入式軟體實戰派」獲得更多精品乾貨。


循環冗餘校驗(英語:Cyclic redundancy check,通稱「CRC」)是一種根據網絡數據包或電腦文件等數據產生簡短固定位數校驗碼的一種散列函數,主要用來檢測或校驗數據傳輸或者保存後可能出現的錯誤。

一句話:CRC是將數據計算出散列的方式,一般用於校驗數據的完整性。它具有簡單、執行效率高等特點。當然,你可以類比於Checksum,但比Checksum複雜些,防碰撞性更好些。

▍CRC的原理
CRC是基於除法的。實際的輸入數據會被解釋為一個長二進位位流(除數),再將其除以另一個固定二進位數(除數,即多項式)。該除法的其餘部分就是校驗值。但是,現實要複雜一些。二進位數(除數和除數)不被視為普通整數值,而是被視為二進位多項式,其中實際位用作係數。輸入數據,看做一串二進位流,用多項式的方式表示為g(x),而除數是國際標準上的多項式,用h(x)表示。通過g(x)和h(x)做除法,即兩者的異或運算,得出的結果,就是我們所說的CRC運算檢驗結果。例如,1x3 + 0x2 + 1x + 1可以表示為1011b,或者1011b表示為1x3 + 0x2 + 1x + 1,其中是0的位,即以0為係數乘以該項。我們來看看一個數據串11010011101100除以x3 + x + 1是怎樣進行的?

整個運算過程是這樣的:

實際上就是逐個bit移位做異或。

詳見:https://en.wikipedia.org/wiki/Cyclic_redundancy_check

▍CRC的概念

實際上,人們根據不同的需要,做了很多種計算方式,主要差別在於CRC長度、多項式、初始值、結果是否需要異或、是否需要翻轉等等。

首先,來看看幾個概念:

Length: CRC的長度(按bit算,如8,16,32)Name: CRC的名字,讓人一看就知道這是哪種CRCPolinomial: 多項式,通過該多項式來計算CRCFinalXorValue: CRC結果做異或運算的值InputReflected: 指示輸出是否需要翻轉OutputReflected: 指示輸出是否需要翻轉

從上面的CRC名稱可以看出,不同的算法是有不同用途的,這是國際常規的用法定義,其實如果是用戶自己用也沒特別要求,可以自由點。

▍CRC的實現
按照前面章節的「CRC原理」來做C語言實現,就通過數據移位和異或算得。以CRC8為例,代碼如下:
#define CRC_POLYNOMIAL_8    0x0Cuint8 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]);                                                             \        }                                                                                           \                                              \        temp1 = (crc ^ (curByte << (width - 8)));                                                   \                                        \        pos = (temp1 >> (width - 8)) & 0xFF;                                                        \                                                                          \        temp2 = (temp1 << 8);                                                                       \                                 \        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為例:

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 ResultReflectedCRC8_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.

我將上面表格中提到的所有CRC算法,都coding了,就像下圖的這些

舉個例子,CRC32_BZIP2.h文件是的算法是這樣的:

真以為我一個一個寫的嗎,哈哈哈,不是。但我肯定不是在網上一個一個抄的,網上也找不到這麼多。
我找到了個規律,寫了個腳本生成的,多項式的正序表、逆序表是生成的,連代碼,我都是用腳本生成的。
這裡篇幅有限,就不將代碼全部貼在這了,如果你對這些算法感興趣,關注「嵌入式軟體實戰派」,聊天界面輸入並發送「CRC」獲得下載連結。後續適當的時候,我還會將這個生成源文件的腳本也分享給大家。
為了驗證其可靠性,我還做了個測試代碼(篇幅有限,下面截取的代碼已省略了部分):
    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/如果你喜歡我的文章,請掃碼關注「嵌入式軟體實戰派」,我會分享更多技術乾貨。還有哦,點擊「在看」,讓更多有需要的人參與學習,我的分享毫無保留。


往期精彩內容推薦>>>

我在馬路上遇到一個死鎖問題

我硬生生地把C代碼塞進了Python和Ruby

C語言的奇技淫巧之四

SREC、Hex、Bin等燒錄文件格式完全解讀


相關焦點

  • CRC校驗源碼學習
    本文引用地址:http://www.eepw.com.cn/article/151634.htm  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
  • 解讀CRC的校驗原理
    根據應用環境與習慣的不同,CRC又可分為以下幾種標準:   ① CRC-12碼;② CRC-16碼;   ③ CRC-CCITT碼;④ CRC-32碼。   CRC-12碼通常用來傳送6-bit字符串。?CRC-16?及CRC-CCITT碼則是用來傳送8-b it字符,其中CRC-16為美國採用,而CRC-CCITT為歐洲國家所採用。
  • CRC校驗---之avrbootloader
    先看一下要用到的函數。#include 本文引用地址:http://www.eepw.com.cn/article/201611/316387.htm這是計算單個字節的CRC(舊crc與data生成)static __inline__ uint16_t _crc_xmodem_update(uint16_t __crc, uint8_t __data);多項式Polynomial
  • CRC算法及工作原理
    因而,在數據存儲和數據通訊領域,CRC無處不在:著名的通訊協議X.25的FCS(幀檢錯序列)採用的是CRC-CCITT,WinRAR  、NERO、ARJ、LHA等壓縮工具軟體採用的是CRC32,磁碟驅動器的讀寫採用了CRC16,通用的圖像存儲格式GIF、TIFF等也都用CRC作為檢錯手段。  CRC的本質是模-2除法的餘數,採用的除數不同,CRC的類型也就不一樣。
  • modbus的CRC校驗程序
    , 0xD6, 0xD2, 0x12, 0x13, 0xD3,0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3,0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4,0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E
  • 單片機通信中的CRC算法原理及程序設計
    單片機之間有/無線載波電路進行單播(點對點)通信,或通過專用程控交換機連接單片機組成的有/無線區域網進行單播或廣播(多點對多點)通信,都要實現報文的快速交換。一個最關鍵的問題就是要解決傳輸報文的誤碼問題。常用的方法是設計有效的硬體驅動電路和編制相應的監控軟體。
  • 敏矽微電子Cortex-M0學習筆記13-硬體CRC實例
    CRC 可以完成下列任務:CRC-CCITT、 CRC-16 、 CRC-32。2、硬體CRC寄存器ME32F030的CRC寄存器使用起來十分簡便,相對應的寄存器也較少。支持的CRC類型如下所示:uint16_t CRC_CCITT(uint8_t * str, uint16_t strlen,uint32_t crcseed);uint16_t CRC_16(uint8_t * str, uint16_t strlen,uint32_t crcseed);uint32_t CRC_32(uint8_t * str, uint16_t
  • CRC的校驗原理及其軟體實現
    MODBUS傳輸時,CRC低位在前,crc%256求低位;  高位在後,crc/256求高位。*************************************************/  uint crc16(uchar *str,uint num) //CRC計算子程序,  {  uchar i;  //uint crc;  crc=0xffff;  for (i=0; i     {
  • 關於crc面試,這幾點你需要注意
    準備中文自我介紹(可從自己為什麼適合這項工作展開)如果有必要準備一個簡短的英文自我介紹你對薪資的要求?你認為自己哪方面能勝任這份工作?你為什麼離職?為什麼選CRC這份工作?你還有什麼問題要問?關於我們公司,你還有什麼想要了解的?你未來的職業規劃?工作過程中,如果研究者不配合你打算怎麼辦?
  • 用C語言實現CRC校驗計算
    首先介紹其原理,如果每次參與CRC計算的信息為一個字節,該信息字節加到16位的累加器中去時,只有累加器的高8位或低8位與信息字節相互作用(異或),相互作用(異或)的結果記為組合值,那麼累加器中的新值等於組合值加上(按模2異或)累加器中未改變的那一半即為新的CRC值。
  • Redis 作者 Antirez 與 Contributor Mattsta 關於 CRC 的 Battle
    昨天表弟說有個學妹問他 Redis 為什麼要用 CRC16(key) mod 16384 來計算 key 所處槽的位置,我想這 CRC 一般都是用來校驗的,通過多項式轉換成二進位再通過模2除法得到餘數,這裡用來做個 Hash 函數好像用的也沒啥毛病(對於CRC不太了解的同學可以先去查查)。
  • CRC校驗原理及其實現
    例如乙太網中使用的是CRC-32校驗,曼徹斯特編碼方式。本篇文章介紹CRC校驗的原理和實現方法。CRC多項式,調用不同的CRC計算函數,得到的結果卻不一樣,而且和手算的結果也不一樣,這就涉及到CRC的參數模型了。
  • 在IAR下如何為工程開啟CRC完整性校驗功能?
    ielftool --fill="0xFF;__checksum_begin–__checksum_end" --checksum="__checksum:4,crc32:p,0xffffffff;__checksum_begin-__checksum_end" sourceFile.out
  • 關於2020年強網杯-misc-miscstudy的賽題解析
    = int(s, 16)targets[s] = crc_t initial_crc32 = 0xffffffff;uint32_t next_crc32(uint32_t c, char b) {_t crc) {if (crcs[stk.length()].count(crc ^ mask_crc32)) {