CRC算法

CRC(Cycle Redundancy Check):循环冗余校验,在链路层被广泛使用的检错技术。

CRC原理(通俗讲)

1.发送端

1.1 在发送端先将数据分组,每组k个数据。假定要传送的数据是M。

1.2 在数据M后面添加供差错检测的n位冗余码,然后构成一帧发送出去,一共发送(k+n)位。

虽然添加n位冗余码增大了数据传送的开销,但是可以进行差错检测,当传输可能出现差错时,付出这种代价是值得的。

1.3 冗余码可以用下面的方法得出:

1.3.1 用二进制模2运算进行2^n*M(相当于M左移n位)的运算。意思就是在M后面补n个0.现在M就变成了k+n位。

1.3.2 用M除以收发双方事先商定的长度n+1的除数P

1.3.3 得到的余数R,这个R就是FCS(帧校验序列)。将这个FCS序列加到M上然后发出去。

2.接收端

2.1 在接收端把接收到的数据以帧为单位进行CRC校验

2.2 把收到的每一个帧都除以同样的除数P,然后检查余数R

2.3 如果余数R为0,那么在传输过程中没有差错

2.4 如果出现误码,那么余数R为零的概率是非常小的

总结:在接收端对接收到的每一帧进行CRC校验后,若余数R为0,则表示这个帧没有错,就接受。如果不为0,则判定这个帧出错,就丢弃。

例:M=101001, P=1101, n=3

在发送端:

1. M=(2^n*M);  则M=101001000

2. 用M除以P

3.得到余数R也就是FCS,将FCS加到M上,就得到了要发送的帧。

M=101001000+FCS=101001001

在接收端:

接收到的每一帧都要进行差错检验,假设收到101001001,P=1101

最后余数R=0,则判定这个帧没有出错。

CRC原理介绍(专业讲)

在K位信息码后再拼接R位的校验码,整个编码长度为N位,因此,这种编码也叫(N, K)码。对于一个给定的(N, K)码,可以证明存在一个最高次幂为N-K=R的多项式G(x)。根据G(x)可以生成K位信息的校验码,而G(x)叫做这个CRC码的生成多项式。校验码的具体生成过程为:假设要发送的信息用多项式C(x)表示,将C(x)左移R位(可表示成C(x)*2R),这样C(x)的右边就会空出R位,这就是校验码的位置。用 C(x)*2R 除以生成多项式G(x)得到的余数就是校验码。

任意一个由二进制位串组成的代码都可以和一个系数仅为‘0’和‘1’取值的多项式一一对应。例如:代码1010111对应的多项式为x6+x4+x2+x+1,而多项式为x5+x3+x2+x+1对应的代码101111。

这里还有个问题,如果被除数比除数小,那么余数就是被除数本身,比如说只要传一个字节,那么他的CRC就是他自己,为避免这种情况发生,在做除法之前先将他移位,使他大于除数,那么移位多少位呢?这是所选的固定除数有关,左移位数比除数的位数少1,下面是常用标准中的除数

CRC8:多项式是X8+X5+X4+1,对应的数字是0x131,左移8位
CRC12:多项式是X12+X11+X3+X2+1,对应的数字是0x180D,左移12位
CCITT CRC16:多项式是X16+X12+X5+1,对应的数字是0x11021,左移16位
ANSI CRC16:多项式是X16+X15+X2+1,对应的数字是0x18005,左移16位
CRC32:多项式是X32+X26+X23+X22+X16+X12+X11+X10+X8+X7+X5+X4+X2+X1+1,对应数字是0x104C11DB7,左移32

因此,在得到字节串对应的数字后,再将数字左移M位(比如ANSI-CRC16是左移16位),就得到了被除数。

好了,现在被除数和除数都有了,那么就要开始做除法求CRC校验码了。CRC除法的计算过程与我们笔算除法类似,首先是被除数与除数高位对齐后,被除数减去除数,得到了差,除数再与差的最高位对齐,进行减法,然后再对齐再减,直到差比除数小,这个差就是余数。不过和普通减法有差别的是,CRC的加(减)法是不进(借)位的,比如10减01,它的结果是11,而不是借位减法得到的01,因此,实际上CRC的加法和减法所得的结果是一样的,比如10加01的结果是11,10减01的结果也是11,这其实就是异或操作。虽然说了这么多也不一定能说清楚,我们还是看一段CRC除法求余程序吧:

unsigend short crc16_div()
{
    unsigned long data = 0x880000;
    unsigned long ccitt16 = 0x11021;
    unsigned long cmp_value = 0x10000;
    int i;
    ccitt16 <<= 7;
    cmp_value <<=7;

    while(cmp_value >= 0x10000)
    {
        if((data & cmp_value) != 0)
        {
            data ^= ccitt16;  //^为按位运算符:异或
        }
        else
        {
            
        }
        ccitt16 >>= 1;
        cmp_value >>= 1;
    }
    return (data & 0xFFFF);
}

好了,现在我们已经会计算0x88的CRC校验码了,它只是对0x880000做除法运算求余数而已,不过这只是求单字节的CRC校验码,那如果有十多个字节怎么办?我们的计算机也存不下那么大的数呀,看来我们还要对程序进行些改进,使它能对大数求除法了。

unsigned short crc16_div_2()
{
    unsigned short data = 0x88;
    unsigned short ccitt16 = 0x1021;
    int i;
    data <<= 8;
    for (i = 0; i < 8; i++){
        if (data & 0x8000){
            data <<= 1;
            data ^= ccitt16;
        }
        else{
            data <<= 1;
        }
    }
    return data;
}

现在对单字节0x88求CRC16,我们只需要两字节short型的整数就行了,而不需要象以前必须用long型0x880000,其实不管多少字节,只用short型就能够计算它的CRC16。下面说说怎么求多字节的样验码:

当我们求得一个字节(比如0x88)的CRC校验码后,如果这时又有一个字节(比如0x23)加入,需要求校验码,该怎么办呢?以前得到的是0x880000除以0x11021的余数,但现在需要得到的是0x88230000除以0x11021的余数,以前求得的校验码是不是白费了?不是的,因为当我们得到0x880000除以0x11021的余数(这里余数是0x1080)后,需要求0x88230000除以0x11021的余数,只要将原来的余数0x1080左移8位除以0x11021就得到了0x98000000除以0x11021的余数(想一想为什么),再加上0x230000除以0x11021的余数。这其实就是求0x108000+0x230000除以0x11021的余数.。因此根据这个方法,我们就可以写出求多个字节的CRC算法:
 

unsigned short crc16_ccitt(unsigned char data, unsigned short crc)
{
        unsigned short ccitt16 = 0x1021;
        int i;
        crc ^= (data<<8);
        for (i=0; i<8; i++)
        {
                  if (crc & 0x8000)
                  {
                           crc <<= 1;
                           crc ^= ccitt16;
                  }
                  else
                  {
                           crc <<= 1;
                  }
        }

        return crc;
}


void main()
{
        int i;
        unsigned short crc;
        char data[5] = { 0x71, 0x88, 0x93,0xa5, 0x13 };
        crc = 0;
        for (i=0; i<5; i++)
        {
                  crc = crc16_ccitt(data[i], crc);
        }
        printf("crc is %x", crc);
}

最后要说的是CRC的正序和反转问题,比如前面ccitt-crc16的正序是0x1021,如果是反转就是0x8408(就是将0x1021倒过来低位变高位)为什么要反转?这是因为数据传输可能是先传低位再传高位(比如串口就是低位在前高位在后)。反转的CRC算法与正序类似,只是需要注意移位的方向相反。
 

unsigned short crc16_ccitt_r(unsigned char data, unsigned short crc)
{
        unsigned short ccitt16 = 0x8408;
        int i;
        crc ^= data;
        for (i=0; i<8; i++)
        {
                  if (crc & 1)
                  {
                           crc >>= 1;
                           crc ^= ccitt16;
                  }
                  else
                  {
                           crc >>= 1;
                  }
        }
        return crc;
}


unsigned short crc16_ccitt_r2(unsigned char data, unsigned short crc)
{
        unsigned char c;

        c = crc & 0xff;
        crc >>= 8;
        crc ^= CRC16_CCITT_R_TABLE[data ^c];
        return crc;
}


参考博客:https://blog.youkuaiyun.com/xinyuan510214/article/details/80104356 

### CRC算法概述 循环冗余校验(Cyclic Redundancy Check, CRC)是一种基于多项式除法的数据校验技术,广泛应用于数据传输和存储系统的错误检测中[^1]。 #### 生成多项式 CRC算法的核心在于选择合适的生成多项式。该多项式的系数决定了最终计算得到的校验码长度以及其纠错能力。通常情况下,不同的应用场景会采用不同标准定义好的生成多项式来满足特定需求。 #### CRC运算过程 ##### 基本概念 在执行CRC运算之前,发送方会在待发送的消息后面附加一定数量的零位作为占位符;接收端则利用相同的生成多项式对接收到的信息进行同样的处理并比较两者之间的差异以判断是否存在传输错误。 ##### 运算实例 对于给定的一组二进制序列`M(x)`与选定的生成多项式`G(x)`,通过模2除法可以得出对应的余数即为消息的实际CRC值。此过程中不涉及借位操作,仅当被除项最高次幂大于等于除数时才做减法运算。 ```c // C语言实现CRC-8简单版本 uint8_t crc8(uint8_t *data, size_t length){ uint8_t crc = 0; while (length--) { crc ^= *data++; for(int i = 0 ;i<8;i++){ if(crc & 0x80) crc = (crc << 1) ^ 0x07; // 使用预设的生成多项式 else crc <<= 1; } } return crc; } ``` #### 零填充方法下的C代码及其实现逻辑 为了简化硬件电路设计,在某些场景下会对输入数据流先实施左移补零再参与后续的异或运算。这种方法虽然增加了额外开销但是便于理解和编程实现,并且适合于FPGA等可编程器件上部署。 #### 不需零填充的方法及其优化策略 另一种方式是在每次迭代前动态调整当前字节位置而不必预先添加多余的零位。这种方式减少了不必要的内存占用同时也提高了效率。特别是针对较长的数据包而言优势明显。此外还提供了严格的数学归纳证明确保了算法正确性。 #### 总结与其他变体 综上所述,无论是哪种具体的实现形式都遵循着相同的基本原则——借助精心挑选过的生成多项式完成高效可靠的差错控制机制构建工作。除此之外还有诸如快速查表法这样的高级技巧能够进一步提升性能表现适用于更复杂的应用环境当中。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值