CRC校驗的C語言實現
文章轉自 循環冗余校驗(CRC)算法入門引導 - Ivan 的專欄 - 博客頻道 - CSDN.NET
http://blog.csdn.net/liyuanbhu/article/details/7882789
一、原理部分
CRC 算法的基本思想是將傳輸的數據當做一個位數很長的數,將這個數除以另一個數,得到的余數作為校驗數據附加到原數據後面。除法采用正常的多項式乘除法,而加減法都采用模2運算。模2運算就是結果除以2後取余數,如3 mod 2 = 1,在計算機中就是異或運算:
例如:
要傳輸的數據為:1101011011
除數設為:10011
在計算前先將原始數據後面填上4個0:11010110110000,之所以要補0,補0的個數就是得到的余數位數。
采用了模2的加減法後,不需要考慮借位的問題。最後得到的余數就是CRC 校驗字。為了進行CRC運算,也就是這種特殊的除法運算,必須要指定個被除數,在CRC算法中,這個被除數有一個專有名稱叫做“生成多項式”。文獻中提到的生成多項式經常會說到多項式的位寬(Width,簡記為W),這個位寬不是多項式對應的二進制數的位數,而是位數減1。比如CRC8中用到的位寬為8的生成多項式,其實對應得二進制數有九位:100110001。
二、編程實現
假設我們的生成多項式為:100110001(簡記為0x31),也就是CRC-8
則計算步驟如下:
(1) 將CRC寄存器(8-bits,比生成多項式少1bit)賦初值0
(2) 在待傳輸信息流後面加入8個0
(3) While (數據未處理完)
(4) Begin
(5) If (CRC寄存器首位是1)
(6) reg = reg XOR 0x31
(7) CRC寄存器左移一位,讀入一個新的數據於CRC寄存器的0 bit的位置。
(8) End
(9) CRC寄存器就是我們所要求的余數。
實際上,真正的CRC 計算通常與上面描述的還有些出入。這是因為這種最基本的CRC除法有個很明顯的缺陷,就是數據流的開頭添加一些0並不影響最後校驗字的結果。因此真正應用的CRC 算法基本都在原始的CRC算法的基礎上做了些小的改動。
所謂的改動,也就是增加了兩個概念,第一個是“余數初始值”,第二個是“結果異或值”。
所謂的“余數初始值”就是在計算CRC值的開始,給CRC寄存器一個初始值。“結果異或值”是在其余計算完成後將CRC寄存器的值在與這個值進行一下異或操作作為最後的校驗值。
常見的三種CRC 標準用到個各個參數如下表。
|
CCITT |
CRC16 |
CRC32 |
校驗和位寬W |
16 |
16 |
32 |
生成多項式 |
x16+x12+x5+1 |
x16+x15+x2+1 |
x32+x26+x23+x22+x16+ x12+x11+x10+x8+x7+x5+ x4+x2+x1+1 |
除數(多項式) |
0x1021 |
0x8005 |
0x04C11DB7 |
余數初始值 |
0xFFFF |
0x0000 |
0xFFFFFFFF |
結果異或值 |
0x0000 |
0x0000 |
0xFFFFFFFF |
加入這些變形後,常見的算法描述形式就成了這個樣子了:
(1) 設置CRC寄存器,並給其賦值為“余數初始值”。
while(數據未處理完)
begin(8_bit)
(2) 將數據的第一個8-bit字符與CRC寄存器進行異或,並把結果存入CRC寄存器。
(3) CRC寄存器向右移一位,MSB補零,移出並檢查LSB。
(4) 如果LSB為0,重復第三步;若LSB為1,CRC寄存器與0x31相異或。
(5) 重復第3與第4步直到8次移位全部完成。此時一個8-bit數據處理完畢。
end(8-bit)
(6) 重復第2至第5步直到所有數據全部處理完成。
(7) 最終CRC寄存器的內容與“結果異或值”進行或非操作後即為CRC值。
示例性的C代碼如下所示,因為效率很低,項目中如對計算時間有要求應該避免采用這樣的代碼。這個代碼有一個crc的參數,可以將上次計算的crc結果傳入函數中作為這次計算的初始值,這對大數據塊的CRC計算是很有用的,不需要一次將所有數據讀入內存,而是讀一部分算一次,全讀完後就計算完了。這對內存受限系統還是很有用的。
[cpp] view plain copy- #define POLY 0x1021
- /**
- * Calculating CRC-16 in ‘C‘
- * @para addr, start of data
- * @para num, length of data
- * @para crc, incoming CRC
- */
- uint16_t crc16(unsigned char *addr, int num, uint16_t crc)
- {
- int i;
- for (; num > 0; num--) /* Step through bytes in memory */
- {
- crc = crc ^ (*addr++ << 8); /* Fetch byte from memory, XOR into CRC top byte*/
- for (i = 0; i < 8; i++) /* Prepare to rotate 8 bits */
- {
- if (crc & 0x8000) /* b15 is set... */
- crc = (crc << 1) ^ POLY; /* rotate and XOR with polynomic */
- else /* b15 is clear... */
- crc <<= 1; /* just rotate */
- } /* Loop for 8 bits */
- crc &= 0xFFFF; /* Ensure CRC remains 16-bit value */
- } /* Loop until num=0 */
- return(crc); /* Return updated CRC */
- }
上面的算法對數據流逐位進行計算,效率很低。實際上仔細分析CRC計算的數學性質後我們可以多位多位計算,最常用的是一種按字節查表的快速算法。該算法基於這樣一個事實:計算本字節後的CRC碼,等於上一字節余式CRC碼的低8位左移8位,加上上一字節CRC右移 8位和本字節之和後所求得的CRC碼。如果我們把8位二進制序列數的CRC(共256個)全部計算出來,放在一個表裏,編碼時只要從表中查找對應的值進行處理即可。
按照這個方法,可以有如下的代碼(這個代碼來自Micbael Barr的書“Programming Embedded Systems in C and C++” ):
[cpp] view plain copy
- /*
- crc.h
- */
- #ifndef CRC_H_INCLUDED
- #define CRC_H_INCLUDED
- /*
- * The CRC parameters. Currently configured for CCITT.
- * Simply modify these to switch to another CRC Standard.
- */
- /*
- #define POLYNOMIAL 0x8005
- #define INITIAL_REMAINDER 0x0000
- #define FINAL_XOR_VALUE 0x0000
- */
- #define POLYNOMIAL 0x1021
- #define INITIAL_REMAINDER 0xFFFF
- #define FINAL_XOR_VALUE 0x0000
- /*
- #define POLYNOMIAL 0x1021
- #define POLYNOMIAL 0xA001
- #define INITIAL_REMAINDER 0xFFFF
- #define FINAL_XOR_VALUE 0x0000
- */
- /*
- * The width of the CRC calculation and result.
- * Modify the typedef for an 8 or 32-bit CRC standard.
- */
- typedef unsigned short width_t;
- #define WIDTH (8 * sizeof(width_t))
- #define TOPBIT (1 << (WIDTH - 1))
- /**
- * Initialize the CRC lookup table.
- * This table is used by crcCompute() to make CRC computation faster.
- */
- void crcInit(void);
- /**
- * Compute the CRC checksum of a binary message block.
- * @para message, 用來計算的數據
- * @para nBytes, 數據的長度
- * @note This function expects that crcInit() has been called
- * first to initialize the CRC lookup table.
- */
- width_t crcCompute(unsigned char * message, unsigned int nBytes);
- #endif // CRC_H_INCLUDED
[cpp] view plain copy
- /*
- *crc.c
- */
- #include "crc.h"
- /*
- * An array containing the pre-computed intermediate result for each
- * possible byte of input. This is used to speed up the computation.
- */
- static width_t crcTable[256];
- /**
- * Initialize the CRC lookup table.
- * This table is used by crcCompute() to make CRC computation faster.
- */
- void crcInit(void)
- {
- width_t remainder;
- width_t dividend;
- int bit;
- /* Perform binary long division, a bit at a time. */
- for(dividend = 0; dividend < 256; dividend++)
- {
- /* Initialize the remainder. */
- remainder = dividend << (WIDTH - 8);
- /* Shift and XOR with the polynomial. */
- for(bit = 0; bit < 8; bit++)
- {
- /* Try to divide the current data bit. */
- if(remainder & TOPBIT)
- {
- remainder = (remainder << 1) ^ POLYNOMIAL;
- }
- else
- {
- remainder = remainder << 1;
- }
- }
- /* Save the result in the table. */
- crcTable[dividend] = remainder;
- }
- } /* crcInit() */
- /**
- * Compute the CRC checksum of a binary message block.
- * @para message, 用來計算的數據
- * @para nBytes, 數據的長度
- * @note This function expects that crcInit() has been called
- * first to initialize the CRC lookup table.
- */
- width_t crcCompute(unsigned char * message, unsigned int nBytes)
- {
- unsigned int offset;
- unsigned char byte;
- width_t remainder = INITIAL_REMAINDER;
- /* Divide the message by the polynomial, a byte at a time. */
- for( offset = 0; offset < nBytes; offset++)
- {
- byte = (remainder >> (WIDTH - 8)) ^ message[offset];
- remainder = crcTable[byte] ^ (remainder << 8);
- }
- /* The final remainder is the CRC result. */
- return (remainder ^ FINAL_XOR_VALUE);
- } /* crcCompute() */
上面代碼中crcInit() 函數用來計算crcTable,因此在調用 crcCompute 前必須先調用 crcInit()。
CRC校驗的C語言實現