CRC冗余校验算法(当初数电课睡觉,现在来补课了)

Posted shiqi17

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CRC冗余校验算法(当初数电课睡觉,现在来补课了)相关的知识,希望对你有一定的参考价值。

  1. CRC校验原理
    CRC校验原理看起来比较复杂,好难懂,因为大多数书上基本上是以二进制的多项式形式来说明的。其实很简单的问题,其根本思想就是先在要发送的帧后面附加一个数(这个就是用来校验的校验码,但要注意,这里的数也是二进制序列的,下同),生成一个新帧发送给接收端。当然,这个附加的数不是随意的,它要使所生成的新帧能与发送端和接收端共同选定的某个特定数整除(注意,这里不是直接采用二进制除法,而是采用一种称之为“模2除法”)。到达接收端后,再把接收到的新帧除以(同样采用“模2除法”)这个选定的除数。因为在发送端发送数据帧之前就已通过附加一个数,做了“去余”处理(也就已经能整除了),所以结果应该是没有余数。如果有余数,则表明该帧在传输过程中出现了差错。

【说明】“模2除法”与“算术除法”类似,但它既不向上位借位,也不比较除数和被除数的相同位数值的大小,只要以相同位数进行相除即可。模2加法运算为:1+1=0,0+1=1,0+0=0,无进位,也无借位;模2减法运算为:1-1=0,0-1=1,1-0=1,0-0=0,也无进位,无借位。相当于二进制中的逻辑异或运算。也就是比较后,两者对应位相同则结果为“0”,不同则结果为“1”。如100101除以1110,结果得到商为11,余数为1,如图5-9左图所示。如11×11=101,如图图所示。

技术图片

具体来说,CRC校验原理就是以下几个步骤:

(1)先选择(可以随机选择,也可按标准选择,具体在后面介绍)一个用于在接收端进行校验时,对接收的帧进行除法运算的除数(是二进制比较特串,通常是以多项方式表示,所以CRC又称多项式编码方法,这个多项式也称之为“生成多项式”)。

(2)看所选定的除数二进制位数(假设为k位),然后在要发送的数据帧(假设为m位)后面加上k-1位“0”,然后以这个加了k-1个“0“的新帧(一共是m+k-1位)以“模2除法”方式除以上面这个除数,所得到的余数(也是二进制的比特串)就是该帧的CRC校验码,也称之为FCS(帧校验序列)。但要注意的是,余数的位数一定要是比除数位数只能少一位,哪怕前面位是0,甚至是全为0(附带好整除时)也都不能省略。

(3)再把这个校验码附加在原数据帧(就是m位的帧,注意不是在后面形成的m+k-1位的帧)后面,构建一个新帧发送到接收端;最后在接收端再把这个新帧以“模2除法”方式除以前面选择的除数,如果没有余数,则表明该帧在传输过程中没出错,否则出现了差错。

通过以上介绍,大家一定可以理解CRC校验的原理,并且不再认为很复杂吧。

从上面可以看出,CRC校验中有两个关键点:一是要预先确定一个发送端和接收端都用来作为除数的二进制比特串(或多项式);二是把原始帧与上面选定的除进行二进制除法运算,计算出FCS。前者可以随机选择,也可按国际上通行的标准选择,但最高位和最低位必须均为“1”,如在IBM的SDLC(同步数据链路控制)规程中使用的CRC-16(也就是这个除数一共是17位)生成多项式g(x)= x16 + x15 + x2 +1(对应二进制比特串为:11000000000000101);而在ISO HDLC(高级数据链路控制)规程、ITU的SDLC、X.25、V.34、V.41、V.42等中使用CCITT-16生成多项式g(x)= x16 + x15 + x5 +1(对应二进制比特串为:11000000000100001)。
  1. CRC校验码的计算示例
    由以上分析可知,既然除数是随机,或者按标准选定的,所以CRC校验的关键是如何求出余数,也就是校验码(CRC校验码)。
下面以一个例子来具体说明整个过程。现假设选择的CRC生成多项式为G(X) = X4 + X3 + 1,要求出二进制序列10110011的CRC校验码。下面是具体的计算过程:

(1)首先把生成多项式转换成二进制数,由G(X) = X4 + X3 + 1可以知道(,它一共是5位(总位数等于最高位的幂次加1,即4+1=5),然后根据多项式各项的含义(多项式只列出二进制值为1的位,也就是这个二进制的第4位、第3位、第0位的二进制均为1,其它位均为0)很快就可得到它的二进制比特串为11001。

(2)因为生成多项式的位数为5,根据前面的介绍,得知CRC校验码的位数为4(校验码的位数比生成多项式的位数少1)。因为原数据帧10110011,在它后面再加4个0,得到101100110000,然后把这个数以“模2除法”方式除以生成多项式,得到的余数(即CRC码)为0100,如图5-10所示。注意参考前面介绍的“模2除法”运算法则。

技术图片

(3)把上步计算得到的CRC校验0100替换原始帧101100110000后面的四个“0”,得到新帧101100110100。再把这个新帧发送到接收端。

(4)当以上新帧到达接收端后,接收端会把这个新帧再用上面选定的除数11001以“模2除法”方式去除,验证余数是否为0,如果为0,则证明该帧数据在传输过程中没有出现差错,否则出现了差错。

通过以上CRC校验原理的剖析和CRC校验码的计算示例的介绍,大家应该对这种看似很复杂的CRC校验原理和计算方法应该比较清楚了。
Python实现

技术图片

"""
简单的CRC16计算示例:
    CRC16校验码计算方法如下:
    1、初始化CRC值为0xFFFF
    2、被检验数的第1字节跟CRC的低8位异或,将结果放入CRC的低8位(CRC的高8位不变)
    3、CRC右移1位,若移出的为0继续右移;为1CRC寄存器跟生成多项式(倒序后的值)异或(X16+X15+X2+1 <--> 0x8005 >>> 0xA0001)
    4、重复步骤3,8次第1字字数据处理完成
    5、取被检验数据的下1字节,进行2、3、4步;至到整个数据检验完成
    6、将最后的CRC值,高低字节交换
"""

class crc16(object):
    # CRC16生成多项式:X16+X15+X2+1
    GENERATOR_POLYNOMIAL = 0x8005
    # CRC初始值为:0xFFFF
    CRC = 0xFFFF
    def calculateCRC(dataarray):
        """
        检查输入数据是否合法
        :param dataarray: 需要生成CRC校验的数据
        :return: 合法的数据元组
        """

        datalist = None   #以空格分割字符串得到对应字符串列表
        #print(u'输入的字符串序列:{0}'.format(datalist))
        if(dataarray is bytes):
            datalist=dataarray
        else:
            datalist = dataarray.split()

        index = 0
        try:
            # 将输入的字符串按不同进制数转化为数据序列
            for index, item in enumerate(datalist):
                # print index,item
                if '0x'in item.lower().strip():
                    datalist[index] = int(item, 16)
                elif '0o' in item.lower().strip():
                    datalist[index] = int(item,8)
                elif '0b' in item.lower().strip():
                    datalist[index] = int(item,2)
                else:
                    datalist[index] = int(item)
            #print (u'成功转换后的对应的序列:{0}'.format(datalist))
            # 处理第1个字节数据
            temp = crc16.calculateonebyte(datalist.pop(0), 0xFFFF)
            # 循环处理其它字节数据
            for data in datalist:
                temp = crc16.calculateonebyte(data,temp)
            newValue=str(hex(temp))
            newValue='0x'+newValue[2:].zfill(4)
            newValue=newValue[4:].zfill(2)+' '+newValue[2:4].zfill(2)
            return newValue.upper()

        except ValueError as err:
            print(u'第{0}个数据{1}输入有误'.format(index,datalist[index]).encode('utf-8'))
            print(err)
        # finally:
        #     print('当前datalist:{0} '.format(datalist))
    def calculateonebyte(databyte, tempcrc):
        """
        计算1字节数据的CRC值
        :param databyte: 需计算的字节数据
        :param tempcrc: 当前的CRC值
        :return: 当道新的CRC值
        """
        # databyte必须为字节数据
        # assert 0x00 <= databyte <= 0xFF
        # 同上字节数据检查
        if not 0x00 <= databyte <= 0xFF:
            raise Exception((u'数据:0x{0:<02X}不是字节数据[0x00-0xFF]'.format(databyte)).encode('utf-8'))

        # 把字节数据根CRC当前值的低8位相异或
        low_byte = (databyte ^ tempcrc) & 0x00FF
        # 当前CRC的高8位值不变
        resultCRC = (tempcrc & 0xFF00) | low_byte

        # 循环计算8位数据
        for index in range(8):
            # 若最低为1:CRC当前值跟生成多项式异或;为0继续
            if resultCRC & 0x0001 == 1:
                #print("[%d]: 0x%4X ^^^^ 0x%4X" % (index,resultCRC>>1,resultCRC^GENERATOR_POLYNOMIAL))
                resultCRC >>= 1
                resultCRC ^= 0xA001 # 0xA001是0x8005循环右移16位的值
            else:
                # print ("[{0}]: 0x{1:X} >>>> 0x{2:X}".format(index,resultCRC,resultCRC>>1))
                resultCRC >>= 1


        return resultCRC

以上是关于CRC冗余校验算法(当初数电课睡觉,现在来补课了)的主要内容,如果未能解决你的问题,请参考以下文章

算法CRC 循环冗余校验

循环冗余校验(CRC)算法原理分析及实战

CRC(循环冗余校验)

写给嵌入式程序员的循环冗余校验(CRC)算法入门引导

求助大神还都有哪些16位冗余校验计算方法,试过常见的几种CRC16都不对

数电课设