crc校驗的描述:
1.CRC校驗原理
CRC校驗的原理的難易暫不評價,畢竟會者不難,難者不會么。
CRC校驗的根本思想是在要發送的幀之后附加一個數(CRC校驗值),生成一個新幀,然后發送給接收端。
當然,這個附加的數並不是隨意的,他要使新生成的幀能在發送端和接收端選定的某個特定的數整除。
當然,這個特定的數和整除的方法也不是隨意的,特定的數是經過多次論證選定的一些列數,這在之
后會述說,整除的方法是模2除法。
模2除法(crc原理最核心的思想)
模2除法與算術除法類似,都是除法,不同的是,除的時候既不向上借位,也不比較除數和被除數的大
小,只要以相同的位數進行相除即可。模2加法:1+1=0,0+1=1, 0+0=0,無進位也無借位;模2減法:
1-1=0, 0-1=1,0-0=0,也無進位,無借位,相當於二進制中的異或運算。即比較后,相同為0,相異
為1。如100101除以1110,結果的商為11,余數為1.
關於模2除法中除數的選擇,這個你可以自己隨意選擇,開玩笑的,隨意選擇的除數,會導致幀校驗的
正確率下降,這是不確定的,要看你選擇的除數。而我們一般的除數的選擇是直接去參照一些專家講過
多次試驗下來的一些除數,這些除法能極大的保證幀校驗的正確率。一般而言crc8校驗的錯誤率為1/256,
crc16校驗的錯誤率為1/65536,crc32校驗的錯誤率為1/(65536*65536).
那這里就有一個問題,我們傳送的是一串字節數據,而不是一個數據,怎么將一串數字變成一個數據呢?
這也很簡單,比如說2個字節B1,B2,那么對應的數就是(B1<<8)+B2;如果是3個字節B1,B2,B3,那么對
應的數就是((B1<<16)+(B2<<8)+B3),比如數字是0x01,0x02,0x03,那么對應的數字就是0x10203;依次類
推。如果字節數很多,那么對應的數就非常非常大,不過幸好CRC只需要得到余數,而不需要得到商。
從上面介紹的原理我們可以大致知道CRC校驗的准確率,在CRC8中出現了誤碼但沒發現的概率是1/256,
CRC16的概率是1/65536,而CRC32的概率則是1/2^32,那已經是非常小了,所以一般在數據不多的情況下
用CRC16校驗就可以了,而在整個文件的校驗中一般用CRC32校驗。
這里還有個問題,如果被除數比除數小,那么余數就是被除數本身,比如說只要傳一個字節,那么它的
CRC就是它自己,為避免這種情況,在做除法之前先將它移位,使它大於除數,那么移多少位呢?這就與
所選的固定除數有關了,左移位數比除數的位數少1,下面是常用標准中的除數:
CRC8:多項式是X8+X5+X4+1,對應的數字是0x131,左移8位
CRC12:多項式是X12+X11+X3+X2+1,對應的數字是0x180D,左移12位
CCITT CRC16:多項式是X16+X12+X5+1,對應的數字是0x11021,左移16位
ANSI CRC16:多項式是X16+X15+X2+1,對應的數字是0x18005,左移16位
CRC32:多項式是X32+X26+X23+X22+X16+X12+X11+X10+X8+X7+X5+X4+X2+X1+1,對應數字是0x104C11DB7,
左移32因此,在得到字節串對應的數字后,再將數字左移M位(比如ANSI-CRC16是左移16位),就得到了被
除數。好了,現在被除數和除數都有了,那么就要開始做除法求CRC校驗碼了。CRC除法的計算過程與我們筆
算除法類似,首先是被除數與除數高位對齊后,被除數減去除數,得到了差,除數再與差的最高位對齊,進
行減法,然后再對齊再減,直到差比除數小,這個差就是余數。不過和普通減法有差別的是,CRC的加(減)法
是不進(借)位的,比如10減01,它的結果是11,而不是借位減法得到的01,因此,實際上CRC的加法和減
法所得的結果是一樣的,比如10加01的結果是11,10減01的結果也是11,這其實就是異或操作。說了這么多
也不一定能說清楚,我們還是看一段CRC除法求余程序吧:
用crc16計算0x88的校驗碼:依照crc16的規則,先向左移動16位,得到0x880000,依照相除的方法,數據不動,移動除數
unsigned short crc16_div() { unsigned long data = 0x880000; unsigned long citt16 = 0x11021; unsigned long cmp_value = 0x10000; citt16 << = 7; //(為了讓data最高位能直接異或citt16的最高位) cmp_value << = 7; while(data > cmp_value) { if(data & cmp_value) { data ^= citt16; } citt16 >>= 1; cmp_value >>= 1; } return (data & 0xffff); }
好了,現在我們已經會計算0x88的CRC校驗碼了,它只是對0x880000做除法運算求余數而已,不過這只是求單字節的CRC校驗碼,
那如果有十多個字節怎么辦?我們的計算機也存不下那么大的數呀,看來我們還要對程序進行些改進,使它能對大數求除法了。
unsigned short crc16_div_2() { unsigned short data = 0x88; unsigned short citt16 = 0x1021; int i; data <<= 8; for(i=0;i<8;i++) /* 為什么要小於8呢?因為按照crc16的校驗,照理說需要左移16位,上面已經移了8位,所以還可以再移8位 */ { if(data & 0x8000) { data <<=1; data ^= citt16; } else data <<= 1; } return (data & 0xffff); }
對上述代碼進行優化:
unsigned short crc16_ccitt(unsigned char data, unsigned short crc) { unsigned short citt16 = 0x1021; int i; crc ^= (data << 8); //我們之前默認的是crc中沒有一個初值 for(i=0;i<8;i++) { if(crc & 0x8000) { crc <<= 1; crc ^= citt16; } else { crc <<=1; } } }
最后要說的是CRC的正序和反轉問題,比如前面ccitt-crc16的正序是0x1021,如果是反轉就是0x8408
(就是將0x1021倒過來低位變高位)為什么要反轉?這是因為數據傳輸可能是先傳低位再傳高位(比
如串口就是低位在前高位在后)。反轉的CRC算法與正序類似,只是需要注意移位的方向相反。
unsigned short crc16_ccitt_r(unsigned char data, unsigned short crc) { unsigned short ccitt16 = 0x8408; int i; crc ^= data; for (i=0; i<8; i++) { if (crc & 1) {
crc >>= 1; crc ^= ccitt16; } else { crc >>= 1; } } return crc;
參考:https://www.cnblogs.com/sparkbj/articles/6027670.html
https://blog.csdn.net/Ele_Dd/article/details/78193279