本文本文主要說兩件事,一是對(duì)于網(wǎng)上一些Demo的解釋,借用網(wǎng)友思路的Demo,如有雷同純屬巧合。二是關(guān)于數(shù)據(jù)反轉(zhuǎn)的問題。
在《CRC原理——為什么算出來的CRC校驗(yàn)碼結(jié)果總不一樣?》原文鏈接(http://www.itdecent.cn/p/2551ea7dbb14)中解釋了關(guān)于CRC的一些基本概念,這里談?wù)勍ㄟ^高級(jí)語言編程的實(shí)現(xiàn)思路。
CRC的實(shí)現(xiàn)步驟(以crc16為例,寬度為16位):
(1)確定模型標(biāo)準(zhǔn)
(2)將待發(fā)送數(shù)據(jù)與預(yù)定初始值進(jìn)行異或得到新的數(shù)據(jù)M
(2)在M后面(右側(cè))加上16個(gè)0得到數(shù)據(jù)N
(3)將N與模型標(biāo)準(zhǔn)除數(shù)進(jìn)行異或,得到余數(shù)
(4)將余數(shù)與結(jié)果異或值進(jìn)行異或,得到目標(biāo)值
這里不含輸入輸出值的正反轉(zhuǎn)。我們很容易會(huì)發(fā)現(xiàn)一些問題,當(dāng)所處理的數(shù)據(jù)量不大時(shí),這樣的方法是沒有問題的,但實(shí)際上我們一般傳遞的數(shù)據(jù)都是非常巨大的,如果通過上述方案,恐怕計(jì)算機(jī)的空間都不夠我們使用的,所以我們需要用另一種方法,這里引用網(wǎng)友的思路(不知道誰的,僅適用于常用的8位數(shù)據(jù)傳輸,校驗(yàn)寬度為16位,無反轉(zhuǎn)):
(1)預(yù)制一個(gè)16位的存儲(chǔ)空間CRC,并賦初始值
(2)將要發(fā)送的數(shù)據(jù)打包成一個(gè)Byte數(shù)組(將數(shù)據(jù)分成多個(gè)Byte存儲(chǔ))
(3)將第一個(gè)數(shù)據(jù)左移8位并與CRC當(dāng)前值進(jìn)行異或,結(jié)果放入CRC
(4)判斷當(dāng)前CRC的最高位(MSB)是否為1,若為1,則左移一位,將MSB移出,并在LSB(最低位)補(bǔ)0,將新的數(shù)據(jù)與簡記式Poly進(jìn)行異或,結(jié)果存入CRC;
若MSB為0則只進(jìn)行左移操作。
(5)重復(fù)步驟3-4直至8個(gè)數(shù)據(jù)移動(dòng)完畢,此時(shí)CRC中的值就是我們要的校驗(yàn)碼。
以下附上一段網(wǎng)上https://blog.csdn.net/u012923751/article/details/80352325的C代碼
這里我再解釋以下變量,如果采用的是左移方式的計(jì)算,多項(xiàng)式定為G(X)=X16+X15+X2+1(16#18005),則POLY采用的值應(yīng)該是16#8005(簡記式),其實(shí)我們可以看得到這個(gè)算法本身就是手工除法的做法了,不過還是需要驗(yàn)證一下。
我這里選用數(shù)據(jù)為16#AA,多項(xiàng)式沿用16#18005,初始值為0,結(jié)果異或值為0,不反轉(zhuǎn),其手工計(jì)算方法如下:

接下來我們驗(yàn)證一下C語言算法:

結(jié)果是一致的。
這種算法的好處是,解決了之前說的數(shù)據(jù)量的問題,每次只處理8個(gè)位,等到所有數(shù)據(jù)處理完后就能得到我們要的數(shù)值,其中應(yīng)用了模2除法交換特性就不多說了。
這是最直接的計(jì)算方法,也非常容易理解,但是實(shí)際運(yùn)用過程中,我們還會(huì)遇到數(shù)據(jù)反轉(zhuǎn)的情況,這里以CRC_16 Modbus為例,其二項(xiàng)式為16#18005,初始值16#FFFF,結(jié)果異或值16#0000,輸入值輸出值均反轉(zhuǎn)。
出現(xiàn)這種情況的原因其實(shí)就是因?yàn)镸odbus在傳輸數(shù)據(jù)的時(shí)候是低位優(yōu)先的,也就是它是從0位出去的,然后接收端是從0位接收的,因此,如果我們要進(jìn)行計(jì)算,那就要重寫一個(gè)算法,先把原始數(shù)據(jù)反轉(zhuǎn)過來,這就增加了程序的工作量。因此,程序員發(fā)明了另一個(gè)方法,使用反轉(zhuǎn)二項(xiàng)式來解決這個(gè)問題。
先用一個(gè)實(shí)例來說明一下,后面我們解釋反轉(zhuǎn)算法的合理性。
以下實(shí)例采用CRC16 Modbus 輸入輸出反轉(zhuǎn),初始值16#FFFF,多項(xiàng)式16#18005
反轉(zhuǎn)算法的思路是:
(1)預(yù)制一個(gè)16位的存儲(chǔ)空間Preset,并賦初始值(如16#FFFF)
(2)將要發(fā)送的數(shù)據(jù)打包成一個(gè)Byte數(shù)組(將數(shù)據(jù)分成多個(gè)Byte存儲(chǔ))
(3)將第一個(gè)數(shù)據(jù)左移8位并與CRC當(dāng)前值進(jìn)行異或,結(jié)果放入Preset
(4)判斷當(dāng)前CRC的最低位(LSB)是否為1,若為1,則右移一位,將LSB移出,并在MSB(最高位)補(bǔ)0,將新的數(shù)據(jù)與簡記式16#A001(16#8005反轉(zhuǎn))進(jìn)行異或,結(jié)果存入Preset;
若LSB為0則只進(jìn)行右移操作。
(5)重復(fù)步驟3-4直至8個(gè)數(shù)據(jù)移動(dòng)完畢,此時(shí)CRC中的值就是我們要的校驗(yàn)碼。
以下為SCL源碼:
反轉(zhuǎn)計(jì)算其實(shí)是一種反向除法,利用的是模2除法不借位特性,但為什么這么做呢?
先做一個(gè)理論分析,假設(shè)我們發(fā)送數(shù)據(jù)16#AB(1010 1011),這是正向發(fā)送,但由于modbus的LSB優(yōu)先傳輸特點(diǎn),接收方實(shí)際收到的數(shù)據(jù)為1101 0101,但它還是會(huì)使用16#18005來進(jìn)行計(jì)算,因此,兩邊其實(shí)是一個(gè)鏡像關(guān)系。
語言能力有限,接下來就以16#AB(1010 1011)來驗(yàn)證一下算法的正確性,這里初始值設(shè)為0,方便計(jì)算,不影響程序:
首先是發(fā)送方:

然后再看接收方,接收方收到的數(shù)據(jù)應(yīng)該為1101 0101 1000+0010 1111 1101
驗(yàn)算如下:

接收到的驗(yàn)算無余數(shù),證明結(jié)果正確,至于反轉(zhuǎn)為什么是16#A001而不是16#14001,與16#8005的結(jié)果是一樣的,在這套算法中,首位的1被舍去了,反轉(zhuǎn)算法則應(yīng)該舍去末尾的1。