CRC演算法原理及C語言實現
摘 要 本文從理論上推導出CRC演算法實現原理,給出三種分別適應不同計算機或微控制器硬體環境的C語言程序。讀者更能根據本演算法原理,用不同的語言編寫出獨特風格更加實用的CRC計算程序。
關鍵詞 CRC 演算法 C語言
1 引言
循環冗餘碼CRC檢驗技術廣泛應用於測控及通信領域。CRC計算可以靠專用的硬體來實現,但是對於低成本的微控制器系統,在沒有硬體支持下實現CRC檢驗,關鍵的問題就是如何通過軟體來完成CRC計算,也就是CRC演算法的問題。
這里將提供三種演算法,它們稍有不同,一種適用於程序空間十分苛刻但CRC計算速度要求不高的微控制器系統,另一種適用於程序空間較大且CRC計算速度要求較高的計算機或微控制器系統,最後一種是適用於程序空間不太大,且CRC計算速度又不可以太慢的微控制器系統。
2 CRC簡介
CRC校驗的基本思想是利用線性編碼理論,在發送端根據要傳送的k位二進制碼序列,以一定的規則產生一個校驗用的監督碼(既CRC碼)r位,並附在信息後邊,構成一個新的二進制碼序列數共(k+r)位,最後發送出去。在接收端,則根據信息碼和CRC碼之間所遵循的規則進行檢驗,以確定傳送中是否出錯。
16位的CRC碼產生的規則是先將要發送的二進制序列數左移16位(既乘以 )後,再除以一個多項式,最後所得到的余數既是CRC碼,如式(2-1)式所示,其中B(X)表示n位的二進制序列數,G(X)為多項式,Q(X)為整數,R(X)是余數(既CRC碼)。
(2-1)
求CRC碼所採用模2加減運演算法則,既是不帶進位和借位的按位加減,這種加減運算實際上就是邏輯上的異或運算,加法和減法等價,乘法和除法運算與普通代數式的乘除法運算是一樣,符合同樣的規律。生成CRC碼的多項式如下,其中CRC-16和CRC-CCITT產生16位的CRC碼,而CRC-32則產生的是32位的CRC碼。本文不討論32位的CRC演算法,有興趣的朋友可以根據本文的思路自己去推導計算方法。
CRC-16:(美國二進制同步系統中採用)
CRC-CCITT:(由歐洲CCITT推薦)
CRC-32:
接收方將接收到的二進制序列數(包括信息碼和CRC碼)除以多項式,如果余數為0,則說明傳輸中無錯誤發生,否則說明傳輸有誤,關於其原理這里不再多述。用軟體計算CRC碼時,接收方可以將接收到的信息碼求CRC碼,比較結果和接收到的CRC碼是否相同。
3 按位計算CRC
對於一個二進制序列數可以表示為式(3-1):
(3-1)
求此二進制序列數的CRC碼時,先乘以 後(既左移16位),再除以多項式G(X),所得的余數既是所要求的CRC碼。如式(3-2)所示:
(3-2)
可以設: (3-3)
其中 為整數, 為16位二進制余數。將式(3-3)代入式(3-2)得:
(3-4)
再設: (3-5)
其中 為整數, 為16位二進制余數,將式(3-5)代入式(3-4),如上類推,最後得到:
(3-6)
根據CRC的定義,很顯然,十六位二進制數 既是我們要求的CRC碼。
式(3-5)是編程計算CRC的關鍵,它說明計算本位後的CRC碼等於上一位CRC碼乘以2後除以多項式,所得的余數再加上本位值除以多項式所得的余數。由此不難理解下面求CRC碼的C語言程序。*ptr指向發送緩沖區的首位元組,len是要發送的總位元組數,0x1021與多項式有關。
unsigned int cal_crc(unsigned char *ptr, unsigned char len) {
unsigned char i;
unsigned int crc=0;
while(len--!=0) {
for(i=0x80; i!=0; i/=2) {
if((crc&;0x8000)!=0) {crc*=2; crc^=0x1021;} /* 余式CRC乘以2再求CRC */
else crc*=2;
if((*ptr&;i)!=0) crc^=0x1021; /* 再加上本位的CRC */
}
ptr++;
}
return(crc);
}
按位計算CRC雖然代碼簡單,所佔用的內存比較少,但其最大的缺點就是一位一位地計算會佔用很多的處理器處理時間,尤其在高速通訊的場合,這個缺點更是不可容忍。因此下面再介紹一種按位元組查錶快速計算CRC的方法。
4 按位元組計算CRC
不難理解,對於一個二進制序列數可以按位元組表示為式(4-1),其中 為一個位元組(共8位)。
(4-1)
求此二進制序列數的CRC碼時,先乘以 後(既左移16位),再除以多項式G(X),所得的余數既是所要求的CRC碼。如式(4-2)所示:
(4-2)
可以設: (4-3)
其中 為整數, 為16位二進制余數。將式(4-3)代入式(4-2)得:
(4-4)
因為:
(4-5)
其中 是 的高八位, 是 的低八位。將式(4-5)代入式(4-4),經整理後得:
(4-6)
再設: (4-7)
其中 為整數, 為16位二進制余數。將式(4-7)代入式(4-6),如上類推,最後得:
(4-8)
很顯然,十六位二進制數 既是我們要求的CRC碼。
式(4-7)是編寫按位元組計算CRC程序的關鍵,它說明計算本位元組後的CRC碼等於上一位元組余式CRC碼的低8位左移8位後,再加上上一位元組CRC右移8位(也既取高8位)和本位元組之和後所求得的CRC碼,如果我們把8位二進制序列數的CRC全部計算出來,放如一個表裡,採用查表法,可以大大提高計算速度。由此不難理解下面按位元組求CRC碼的C語言程序。*ptr指向發送緩沖區的首位元組,len是要發送的總位元組數,CRC余式表是按0x11021多項式求出的。
unsigned int cal_crc(unsigned char *ptr, unsigned char len) {
unsigned int crc;
unsigned char da;
unsigned int crc_ta[256]={ /* CRC余式表 */
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
0x 1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
};
crc=0;
while(len--!=0) {
da=(uchar) (crc/256); /* 以8位二進制數的形式暫存CRC的高8位 */
crc<<=8; /* 左移8位,相當於CRC的低8位乘以 */
crc^=crc_ta[da^*ptr]; /* 高8位和當前位元組相加後再查表求CRC ,再加上以前的CRC */
ptr++;
}
return(crc);
}
很顯然,按位元組求CRC時,由於採用了查表法,大大提高了計算速度。但對於廣泛運用的8位微處理器,代碼空間有限,對於要求256個CRC余式表(共512位元組的內存)已經顯得捉襟見肘了,但CRC的計算速度又不可以太慢,因此再介紹下面一種按半位元組求CRC的演算法。
5 按半位元組計算CRC
同樣道理,對於一個二進制序列數可以按位元組表示為式(5-1),其中 為半個位元組(共4位)。
(5-1)
求此二進制序列數的CRC碼時,先乘以 後(既左移16位),再除以多項式G(X),所得的余數既是所要求的CRC碼。如式(4-2)所示:
(5-2)
可以設: (5-3)
其中 為整數, 為16位二進制余數。將式(5-3)代入式(5-2)得:
(5-4)
因為:
(5-5)
其中 是 的高4位, 是 的低12位。將式(5-5)代入式(5-4),經整理後得:
(5-6)
再設: (5-7)
其中 為整數, 為16位二進制余數。將式(5-7)代入式(5-6),如上類推,最後得:
(5-8)
很顯然,十六位二進制數 既是我們要求的CRC碼。
式(5-7)是編寫按位元組計算CRC程序的關鍵,它說明計算本位元組後的CRC碼等於上一位元組CRC碼的低12位左移4位後,再加上上一位元組余式CRC右移4位(也既取高4位)和本位元組之和後所求得的CRC碼,如果我們把4位二進制序列數的CRC全部計算出來,放在一個表裡,採用查表法,每個位元組算兩次(半位元組算一次),可以在速度和內存空間取得均衡。由此不難理解下面按半位元組求CRC碼的C語言程序。*ptr指向發送緩沖區的首位元組,len是要發送的總位元組數,CRC余式表是按0x11021多項式求出的。
unsigned cal_crc(unsigned char *ptr, unsigned char len) {
unsigned int crc;
unsigned char da;
unsigned int crc_ta[16]={ /* CRC余式表 */
0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7,
0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef,
}
crc=0;
while(len--!=0) {
da=((uchar)(crc/256))/16; /* 暫存CRC的高四位 */
crc<<=4; /* CRC右移4位,相當於取CRC的低12位)*/
crc^=crc_ta[da^(*ptr/16)]; /* CRC的高4位和本位元組的前半位元組相加後查表計算CRC,
然後加上上一次CRC的余數 */
da=((uchar)(crc/256))/16; /* 暫存CRC的高4位 */
crc<<=4; /* CRC右移4位, 相當於CRC的低12位) */
crc^=crc_ta[da^(*ptr&;0x0f)]; /* CRC的高4位和本位元組的後半位元組相加後查表計算CRC,
然後再加上上一次CRC的余數 */
ptr++;
}
return(crc);
}
2. CRC驗證可以用在單片機里嗎
可以是可以,不過這段程序在8位的51單片機里直接編譯的話效率太低。
一般都是用等價8位查表法來實現CRC16的。
3. 超聲波測距離
一種用於汽車倒車避撞的超聲波無線距離測量系統
Research of Ultrasonic Distance Measurement System
Abstract: A kind of ultrasonic distance measurement system used in the car is designed in this paper. The system includes the lower microcomputer system and the upper microcomputer system. The lower microcomputer system is mainly composed of ultrasonic transmitting circuit, receiving circuit ,wireless communicating mole and microcomputer. The data from the lower microcomputer system is transmitted to the upper microcomputer system by the wireless way. The design principle of ultrasonic distance measurement circuit is analyzed. The design method that the data is transmitted is also introced. The system is of the characteristics of measurement convenience, fast response and stability.
Key words : wireless communicating;microcomputer; ultrasonic;distance measurement;temperature compensation
摘 要:本文介紹一種用於汽車倒車避撞的超聲波無線距離測量系統。系統由下位機與上位機兩部分組成,下位機主要由超聲波發射電路、超聲波接收電路、無線收發模塊及單片機組成,上位機由單片機、無線收發模塊、顯示電路等組成,下位機與上位機之間通過無線收發模塊傳輸信息。文中分析了超聲波測距電路的設計方法,敘述了採用無線通信技術實現數據遠程傳輸的設計思路。該系統測量距離方便、靈活、穩定。
關鍵詞:無線通信;單片機;超聲波;距離測量;溫度補償
1. 引言
隨著經濟的發展,人們的生活水平越來越高。當今,對許多人來說,汽車進入家庭已不再是奢望,但隨之而來的事情就是如何保證汽車使用過程中的安全問題,特別是如何防止汽車與其他物體碰撞的事情發生。據初步調查統計,l5%的汽車事故是由汽車倒車「後視」不良造成的。因此,增強汽車的後視能力,對於提高行車安全,減輕司機的勞動強度和心理壓力,是十分重要的。如果車輛能適時檢測與周圍障礙物的距離並給出警告信息,使司機及早採取行動,可避免車輛相撞事故的發生。
隨著科學技術的發展,用超聲波進行無接觸測量得到了廣泛的應用。超聲波是由機械振動產生的,可在不同介質中以不同的速度傳播,它具有定向性好、能量集中、在傳輸過程中衰減較小,反射能力較強,在惡劣工作環境下具有一定的適應能力等優點。因此可用於液位測量、車輛自動導航[2]等領域。本文介紹一種基於無線數據傳輸方式的超聲波車輛倒車避撞預警系統。
2. 超聲波測距原理
發射的超聲波遇到障礙物時就會發生反射,反射波可由接收器接收,這樣只要測出超聲波從發送點到反射回來的時間間隔Δt,然後根據公式(1)即可求出超生波從發射處到障礙物之間的距離。
S=CΔt/2 (1)
式中:S—超生波發射處與障礙物間的距離
C—超聲波在介質中的傳播速度
由於超聲波是一種聲波,其聲速C受環境溫度的影響,關系如式(2),因此使用超生波測量距離時應該採用溫度補償的方法對式(1)中的聲速值加以校正。
C=331.4+ 0.61×T (2)
式中:T—環境溫度
3. 硬體電路設計
如圖1,硬體電路主要由單片機、超聲波感測器、溫度測量電路、無線收發模塊等組成。
系統中單片機均採用ATMEL公司的AT89S51作為核心控制晶元,它與MCS-51的指令和引腳兼容[1],並且具有ISP在線編程功能,便於系統的設計和調試。
超聲波感測器是超聲波測距電路中的重要元件,其性能優劣直接影響到測距准確度和可靠性。通常超聲波感測器有兩類:一類是發射電路和接收電路互相獨立的分體式超聲波感測器,此類感測器測距有效范圍比較大,但不具備防塵、防水性能。另一類是同時具有發射與接收功能的收發一體式超聲波感測器,此類超聲波測距有效范圍比較小,但防塵、防水性能好。該系統選擇分體式超聲波感測器。
考慮到超聲波具有指向性,本系統在汽車尾部左、右兩個部位各安裝一個超聲波傳
感器,適當調整安裝位置,可准確測量汽
車後部障礙物。
如圖1所示,下位機的P1.1、P1.2引腳分別用於控制兩路超聲波發射,INT0,INT1分別用於兩路超聲波信號檢測,P1.3用於溫度檢測,串口RXD、TXD分別連接無線收發模塊A的輸入、輸出端。同樣,上位機串口RXD、TXD分別連接無線收發模塊B的輸入、輸出端,當接收到下位機發送的測量數據時,下位機進行處理,然後顯示測量結果,當車輛離障礙物的距離超過安全警戒線時發出報警信號。
實際安裝時,該系統的下位機部分安裝在汽車的尾部,上位機部分安裝於駕駛室內。
3.1 超聲波發射電路
超聲波發射電路由超聲波換能器(或稱超聲波振頭)和超聲波發生器兩部分組成,電路如圖2所示。系統中,超聲波換能器的型號為CSB40T,它將超聲波發生器提供的電信號轉換為機械振動並發射出去。40KHz的超聲波信號是利用NE555時基電路振盪產生的,振盪頻率f ≈1.44/((R22+2×R23)×C21),通過R23調節信號頻率,使之與換能器的40KHz固有頻率一致。工作時,下位機通過P1.1口定時向超聲波發生電路發出控制信號,超聲波發生電路產生40KHz的調制脈沖,經換能器轉換為超聲波信號向前方空間發射。
3.2 超聲波接收電路
超聲波接收電路採用了集成電路CX20106A,CX20106A是日本索尼公司生產的紅外遙控信號接收集成電路,它由前置放大、自動偏壓控制、振幅放大、峰值檢波和整形電路組成。該集成電路紅外發射的頻率38KHZ,超聲波換能器的固有頻率是40KHz,適當設計CX20106A外圍電路參數,就可以將其用於超聲波的接收放大電路,如圖3所示,引腳1為CX20106A信號輸入端,引腳2為CX20106A的RC網路連接端,引腳3為CX20106A檢波電容連接端,
引腳4為CX20106A的接地端,引腳5為CX20106A帶通濾波器中心設置端,引腳6為CX20106A積分電容連接端,引腳7為CX20106A信號輸出端,引腳8為CX20106A供電電源端。
工作時,換能器CSB40T將所接收到的微弱聲波振動信號轉化成為電信號,送給CX20106A的輸入端1,當CX20106A接收到信號時,7腳就會輸出一個低電平,可用於下位機的中斷信號源。當下位機接收到中斷信號時,說明檢測到了反射回來的超聲波,下位機就進入中斷狀態,開始距離計算,並將計算結果發送給上位機。
3.3溫度檢測電路
溫度檢測電路採用DALLS公司的1-WIRE式匯流排器件DS18B20數字溫度感測器,電路連接非常簡單,但是必須保證時序與單片機嚴格同步。DS18B20具有9,10,11,和12位轉換精度,未編程時默認精度為12位,測量精度一般為0.5℃,軟體處理後可達0.1℃。溫度輸出以16位符號擴展的二進制數形式提供,低位在先,以0.0625℃/ LSB形式表達,高五位為擴展符號位。轉換周期與轉換精度設定有關,9位精度時,最大轉換時間為93.75ms;12位精度時,最大轉化時間為750ms。在本系統中採用默認的12位精度。關於DS18B20的使用方法可參考有關書籍。
3.4 數據無線收發模塊
為避免在車內鋪設電纜,系統的上位機部分與下位機部分採用無線的方式進行通信。
無線通信模塊採用PTR2000,它是收發一體的工作在國際通用數傳頻段433MHz的無線通信模塊,最高傳輸速率可以達到20Kbit/s,功耗低,待機狀態下僅為8μA,可以直接與單片機的串口連接使用。PTR2000的引腳定義如下:TXE是發送控制端;PWR是節能控制端;DI是數據輸入端;DO是數據輸出端;CS是頻道選擇端。
硬體連接時,由單片機3個通用I/O口分別控制TXE、PWR、CS,單片機的串口與DI,DO連接。TXE為1時,為發送狀態,TXE為0時,為接收狀態。狀態轉換需要5毫秒。PWR為0時,為節電待機狀態,此時模塊無法進行接收或者發送。
無線通信具有無需布線、便於安裝、靈活性強等諸多優點,但是數據在傳輸過程中難以避免的會產生誤碼,而且產生誤碼的幾率要遠遠大於有線網路,並且誤碼的產生與多方面的因素有關,因此有很大的不確定性。所以必須採用一種差錯控制機制,該系統採用停止等待協議來實現差錯控制。此外,還採用校驗機制以確定何時需要重傳,CRC校驗碼的檢錯能力很強,它除了能檢查出離散傳輸錯誤外,還能檢查出突發傳輸錯誤。考慮到硬體和傳輸的開銷問題,使用CRC16校驗碼。
PTR2000靈敏性很高,在無載波的情況下在接收端會產生隨機的數據,因此需制定傳輸協議,格式如表1所示。通信協議中,必須在有效數據前加上兩個或多個固定的前導字元作為同步信號,使得接收端能夠辨別出有效數據的開始。
前導字元採用0xAA、0xAA、0xFF、0x00共4位元組,其中前兩個位元組為同步信號,後兩個位元組為幀開始標志,接收端只要能夠接收到0xAA、0xAA、0xFF與0x00,就可以認為新的一幀開始了。幀類型分為數據幀、有序數據幀、控制命令幀、確認幀等多種幀類型。幀編號為可選項,與幀類型相關,只有幀類型是有序數據幀時才有效。校驗為2位元組CRC16校驗碼。幀結束標志:為0x00。
4.軟體設計
4.1下位機程序設計
下位機程序主要由數據通信程序、距離計算程序、溫度補償程序等組成。
距離計算程序流程圖如圖4所示。
溫度補償通常有兩種方法:一種方法是每次按照公式C=331.4+ 0.61×T計算當前聲速C,進行溫度補償。其特點是:根據當時的溫度得到精確聲速,從而計算得到的距離值也比較精確,但程序中牽涉到浮點數運算,由微處理器系統實現,難度較大。另一種方法是將溫度與聲速的對應關系列成溫度---聲速二維表,固化到系統中。溫度補償時,根據溫度---聲速表,查取最接近當前溫度的那個溫度所對應的聲速值,此聲速值即作為當前聲速。其特點是:避開了復雜的浮點運算和浮點運算後各位元組的提取操作,這樣既保證了一定的精度要求,又可以避免浮點運算。因此本系統採用方法二進行溫度補償。程序流程圖略。
4.2 上位機主程序設計
上位機與下位機通信時,上位機按照通信協議格式將開始測量命令發送給下位機,下位機接收到命令後就開始測量汽車離障礙物的距離,然後將測量結果發送給上位機,上位機先判斷前導字元來確定是否為有效數據,若是有效數據,則解開封包進行相應操作,否則丟棄該數據包,上位機再按照同樣的方式繼續發命令、接收數據,直到接收到正確的數據為止。程序流程圖如圖5所示。
5 結束語
通過對系統硬體電路和軟體的合理設計,本系統能在-20℃到50℃之間正常工作,三位數碼管以厘米為單位顯示距離,能准確判斷距離汽車1.5米內的物體並及時報警,提高了汽車倒車的安全性。本文的創新點是在汽車防撞系統中採用了數據無線通信策略,減少了車內布線。
參考文獻:
[1]MCS-51系列單片機應用系統設計,何立民,北京航空航天大學出版社,1990年.
[2]高准確度超聲波測距儀的研製,趙珂,向瑛等,感測器技術,2003年第2期.
[3]無線通信在嵌入式系統中的應用,曹玲芝,石軍等,微計算機信息,2005年第11期
作者簡介:
曹玲芝,女,1965年生,碩士,副教授,主要從事遠程測控技術研究。
聯系方式:鄭州輕工業學院電氣信息工程學院辦公室 郵編 450002
Email: [email protected]
任亞萍,女,1973年生,碩士生,主要從事計算機技術研究.
4. 用單片機匯編語言或C51語言或實現CRC校驗碼
Uint16 Crc16(unsigned char *puchMsg, int usDataLen)
{
unsigned int uchCRCHi = 0xFF ; /* 高CRC位元組初始化 */
unsigned int uchCRCLo = 0xFF ; /* 低CRC 位元組初始化 */
unsigned int temp16;
Uint32 uIndex ; /* CRC循環中的索引 */
while (usDataLen--) /* 傳輸消息緩沖區 */
{
temp16=*puchMsg++;
uIndex = uchCRCHi ^ temp16 ; /* 計算CRC */
uchCRCHi = uchCRCLo ^ auchCRCHi[uIndex] ;
uchCRCLo = auchCRCLo[uIndex] ;
}
return (uchCRCHi << 8 | uchCRCLo) ;
}
5. 幾種CRC16演算法
一. CRC16演算法首先在源文件頭文件加入表值:[c] view plain ////////////////////////////////////////////////////////////////////////// // CRC16碼表 static WORD const wCRC16Table[256] = { 0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241, 0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440, 0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40, 0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841, 0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40, 0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41, 0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641, 0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040, 0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240, 0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441, 0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41, 0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840, 0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41, 0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40, 0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640, 0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041, 0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240, 0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441, 0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41, 0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840, 0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41, 0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40, 0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640, 0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041, 0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241, 0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440, 0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40, 0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841, 0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40, 0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41, 0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641, 0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040}; 然後在文件中加入下列函數:[c] view plain ////////////////////////////////////////////////////////////////////////// // 函數功能: CRC16效驗 // 輸入參數: pDataIn: 數據地址 // iLenIn: 數據長度 // 輸出參數: pCRCOut: 2位元組校驗值 void CCRCDlg::CRC16(const CHAR* pDataIn, int iLenIn, WORD* pCRCOut) { WORD wResult = 0; WORD wTableNo = 0; for(int i = 0; i < iLenIn; i++) { wTableNo = ((wResult & 0xff) ^ (pDataIn[i] & 0xff)); wResult = ((wResult >> 8) & 0xff) ^ wCRC16Table[wTableNo]; } *pCRCOut = wResult; } 二.CRC16(MODBUS)[c] view plain ////////////////////////////////////////////////////////////////////////// // CRC MODBUS 效驗 // 輸入參數: pDataIn: 數據地址 // iLenIn: 數據長度 // 輸出參數: pCRCOut: 2位元組校驗值 void CCRCDlg::CheckCRCModBus(const CHAR* pDataIn, int iLenIn, WORD* pCRCOut) { WORD wHi = 0; WORD wLo = 0; WORD wCRC; wCRC = 0xFFFF; for (int i = 0; i < iLenIn; i++) { wCRC = CalcCRCModBus(*pDataIn, wCRC); pDataIn++; } wHi = wCRC / 256; wLo = wCRC % 256; wCRC = (wHi > 1; wCRCIn = wCRCIn & 0x7fff; if(wCheck == 1) { wCRCIn = wCRCIn ^ 0xa001; } wCRCIn = wCRCIn & 0xffff; } return wCRCIn; } 三.CRC16(CCITT的0XFFFF)[c] view plain ////////////////////////////////////////////////////////////////////////// // 函數功能: CRC16效驗(CCITT的0XFFFF效驗) // 輸入參數: pDataIn: 數據地址 // iLenIn: 數據長度 // 輸出參數: pCRCOut: 2位元組校驗值 void CCRCDlg::CRCCCITT(const CHAR* pDataIn, int iLenIn, WORD* pCRCOut) { WORD wTemp = 0; WORD wCRC = 0xffff; for(int i = 0; i < iLenIn; i++) { for(int j = 0; j < 8; j++) { wTemp = ((pDataIn[i] > 8); wCRC
6. crc16校驗的c語言程序
下面我們以CRC-16為例來說明任意長度數據流的CRC校驗碼生成過程。我們採用將數據流分成若干個8bit字元,並由低位元組到高位元組傳送的並行方法來求CRC校驗碼。具體計算過程為:用一個16bit的寄存器來存放CRC校驗值,且設定其初值為0x0000;將數據流的第一個8bit與16bit的CRC寄存器的高位元組相異或,並將結果存入CRC寄存器高位元組;CRC寄存器左移一位,最低1bit補零,同時檢查移出的最高1bit,若移出的最高1bit為0,則繼續按上述過程左移,若最高1bit為1,則將CRC寄存器中的值與生成多項式碼相異或,結果存入CRC寄存器值;繼續左移並重復上述處理方法,直到將8bit數據處理完為止,則此時CRC寄存器中的值就是第一個8bit數據對應的CRC校驗碼;然後將此時CRC寄存器的值作為初值,用同樣的處理方法重復上述步驟來處理下一個8bit數據流,直到將所有的8bit字元都處理完後,此刻CRC寄存器中的值即為整個數據流對應的CRC校驗碼。
下面示出了其計算過程的流程圖:
在用C語言編寫CRC校驗碼的實現程序時我們應該注意,生成多項式 對應的十六進制數為0x18005,由於CRC寄存器左移過程中,移出的最高位為1時與 相異或,所以與16bit的CRC寄存器對應的生成多項式的十六進制數可用0x8005表示。下面給出並行處理8bit數據流的C源程序:
unsigned short crc_dsp(unsigned short reg, unsigned char data_crc)
//reg為crc寄存器, data_crc為將要處理的8bit數據流
{
unsigned short msb; //crc寄存器將移出的最高1bit
unsigned short data;
unsigned short gx = 0x8005, i = 0; //i為左移次數, gx為生成多項式
data = (unsigned short)data_crc;
data = data << 8;
reg = reg ^ data;
do
{
msb = reg & 0x8000;
reg = reg << 1;
if(msb == 0x8000)
{
reg = reg ^ gx;
}
i++;
}
while(i < 8);
return (reg);
}
以上為處理每一個8bit數據流的子程序,在計算整個數據流的CRC校驗碼時,我們只需將CRC_reg的初值置為0x0000,求第一個8bit的CRC值,之後,即可將上次求得的CRC值和本次將要處理的8bit數據作為函數實參傳遞給上述子程序的形參進行處理即可,最終返回的reg值便是我們所想得到的整個數據流的CRC校驗值。
7. 求教 C#語言編寫 的CRC16的校驗程序 (多項式為:CRC-16/X25 x16+x12+x5+1)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConvertToCRC16
{
public static class CRC16Util
{
// CRC高位位元組表
private static readonly byte[] m_CRCHighOrderByteTable = new byte[]
{
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40
};
// CRC低位位元組表
private static readonly byte[] m_CRCLowOrderByteTable = new byte[]
{
0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06,
0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD,
0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A,
0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4,
0xD5, 0x15, 0xD7, 0x17, 0x16, 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, 0xFE, 0xFA, 0x3A,
0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29,
0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED,
0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60,
0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67,
0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68,
0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E,
0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71,
0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92,
0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B,
0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B,
0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42,
0x43, 0x83, 0x41, 0x81, 0x80, 0x40
};
/// <summary>
/// 獲得CRC16效驗碼
/// </summary>
/// <param name="buffer"></param>
/// <returns></returns>
public static void CalculateCrc16(byte[] buffer, out byte crcHighOrderByte, out byte crcLowOrderByte)
{
// CRC高位位元組/低位位元組
crcHighOrderByte = crcLowOrderByte = 0xff;
for (int i = 0; i < buffer.Length; i++)
{
// 計算CRC查找索引
int crcIndex = crcHighOrderByte ^ buffer[i];
crcHighOrderByte = (byte)(crcLowOrderByte ^ m_CRCHighOrderByteTable[crcIndex]);
crcLowOrderByte = (byte)m_CRCLowOrderByteTable[crcIndex];
}
}
}
}
// 工具函數如上