在学习mdobus过程中,曾遇到过CRC校验,之前一直不是很明白其原理,现在利用一点闲暇时间学习下。
不同别的校验方式,想弄明白CRC校验的原理以及程序实现过程还真得有点耐心,琢磨一下数学公式。
1. 什么是CRC校验
一句话总结:将需要传递的数据块看成一个信息多项式M(x),收发双方约定一个生成多项式G(x),最高阶数为r,利用模2除法计算M(x)左移r位后与G(x)两者的余数就是CRC校验数据R(x)(或Thebasic idea of CRC algorithms is simply to treat the message as an enormousbinary number, to divide it by another fixed binary number, and to make theremainder from this division the checksum)。
这样将M(x)左移r位后与CRC校验位拼接起来就是发送信息T(x)。
用数学公式来表达就是T(x) = M(x)• + R(x) 其中M(x)•= G(x)•Q(x)+ R(x)
可以看出,对于发送多项式T(x),一定是可以整除G(x)的,因为补了余数嘛。
是不是够明白了。
2. 计算步骤
(1). Choose a width W, and a poly G (of width W).
(2). Append W zero bits to the message. Call this M'.
(3). Divide M' by G using CRC MOD2 arithmetic. The remainder is thechecksum.
3. 什么是模2运算
一般数据的加减法是基于10进制计算,有进位和借位,与之不同的是MOD2 运算时是不考虑进位和借位的,这样本位计算对前后位都没有影响,这样MOD2运算位加减法就是“相同为0,相异为1”,而乘除法可以用加减法去表示,所以MOD2运算就等同于异或运算。
4. CRC校验可以检测什么样的错误?
正常情况下,发送多项式T(x)mod G(x)=0,如果传输过程中发送错误,发送多项式变为T(x)+ E,T(x)mod G(x)=E,如果E是G(x)的倍数,这样的错误是检测不出来的,其他的错误都可以检测出来。这样就要找一个G(x)使其与系统噪声的相似程度尽量低。那么如何寻找一个合适的生产多项式使传输错误尽可能多的被检测出来就很重要了。
5. 常用生成多项式的选择
这里只需记住三个,主要是CRC16
CRC-16(美国): G(x) =x16+x15+x2+1
CRC-CCITT(欧洲): G(x) =x16+x12+x5+1
CRC-32 (32,26,23,22,16,12,11,10,8,7,5,4,2,1,0) 主要用于以太网校验。
取16或32bit主要是为了适用于微型计算机的字长,方便计算。
6. 计算方法
CRC单字节计算方法就不多说了,按照定义即可,下面说说数据块的CRC生成过程。
CRC校验有三种计算方法,一是按位计算,二是按字节计算,三是按半字节计算。
①按位计算方法,又叫直接计算法。直接给出,虽然这个算法因为效率低实用性不高,但是对于CRC计算的理解还是很有帮助的。直接计算法就是实现CRC模2除法,并不是普通的模10除法。
1.Loadthe register with zero bits.(如果计算CRC-M,则选择一个寄存器长度为M)
2.Augmentthe message by appending W zero bits to the end of it. (将信息多项式右移M位,补M个0)
While(more message bits)
Begin
Shiftthe register left by one bit, reading the next bit of the
augmentedmessage into register bit position 0.
If(a 1 bit popped out of the register during step 3)
Register= Register XOR Poly.
(如果有1被移除就与生产多项式异或一次)
End
The register now containsthe remainder.
(最终寄存器中包含的余数就是CRC计算值)
可以使用以下C语言程序去解释:CRC-4,计算一个字节
#define CRC_WIDTH 4
#define CRC_POLY 3
//load data
data = 0x35; //加载信息数据
//append W zeros to the data
data <<= CRC_WIDTH; //数据右移4位
//initial the regs
regs = 0; //移位寄存器
//processing
for(shift_bit=DATA_WIDTH+CRC_WIDTH; shift_bit>0;shift_bit--){
// shift
regs = (regs<<1)| ((data>>(shift_bit-1))&0x1); //将新数据的最高位移入寄存器
// xor
if(regs>>CRC_WIDTH) regs = regs ^ CRC_POLY; //如果寄存器最高位为1,则进行异或计算,计算完成后regs中就是CRC4的值
}
②查表法
然而在实际应用中多使用查表法,推导过程很复杂,数学不好的话建议直接记住结论,也就是:本字节的CRC码等于上一字节的余式的CRC码的低 8位左移8位后,再加上上一字节CRC右移8位(也就是取高8位)和本字节之和(异或)所求的CRC码,可以表示为
unsigned short do_crc_table(unsignedchar *ptr,int len)
{
}
以上算法实现了按字节进行计算校验值。在使用的时候,把计算出来的校验值放在最后两个字节里,将其发送出去,接收端对所有的数据进行相同的校验,如校验值为0我们则认为其数据没有出错。这个是按高位到低位的发送顺序时使用的校验方法。
在实际应用中,还有一种按低位到高位的发送方法(比如串口数据就是先发低位后高位),这样 ,就要进行反相的校验,但如果把数据反相,显然加大计算量,故可以使用相应的查表算法(数学推导)来进行计算。可以由以下算法实现(摘自linux)
u16crc16(u16 crc, u8 const *buffer, size_t len){
while (len--)
crc = (crc >> 8) ^crc16_table[(crc ^ *buffer++) & 0xff];
retrun crc;
}