【基础知识】CRC(循环冗余校验)直接计算和查表法

CRC概述

校验

校验是什么,个人理解就是经过一个算法,使用大量数据(几MB的数据)生成较小长度的一串信息(如16Bit),并切要做到

  1. 原数据不同时,生成的信息大概率不同(不是加密算法不考虑刻意造数据的情况)
  2. 原数据中任意一个或几个数据出现错误时,生成的信息不同(所有的原信息都需要被囊括进生成数据中,也就是说每一个数据都必须参与校验的计算过程并且能对其产生影响

CRC名词解释

宽度 (WIDTH)

即最后生成的数据长度

可以为任意长度

一般使用16bit或32bit

0xFFFF是16bit的校验和

0xFFFFFFFF是32bit的校验和

多项式 (POLY)

这个多项式的每项的系数为0或1,最高系数是和宽度相等,因为有0次幂存在,因此总多项式为

宽度+1项

例如,16bit的CRC16-MODBUS的生成多项式为

x^{16} + x^{15} + x^2 + 1,用HEX表示为0x1 8005

要求最高幂(这里是x的16次方)系数为1,最低次幂(永远是x的0次方)系数为1

因为最高次一定为1,因此可以将其省略,所有生成多项式的值为0x8005

注意:多项式的其他位可以是任意的,但是检验效果并不完全相同,建议使用某些协议规定的多项式

初始值 (INIT)

因为使用程序进行CRC计算时使用一个宽度 (WIDTH)为长度的寄存器进行

我们需要给这个寄存器写入初始值,写入的初始值即为这个值

一般为全是0或者全是1

如16bit为0xFFFF或0x0000

结果异或值 (XOROUT)

计算完成后输出之前需要于此值进行异或操作

一般为全0或全1,长度和宽度 (WIDTH)相等

输入数据反转(REFIN)

CRC计算时是以字节(Byte)为单位输入寄存器中的

这个参数是说明输入是从这个数据的

直接输入寄存器则是高位到低位

经过反转输入则是低位到高位

具体输入到寄存器的哪个位置之后再说

反转的例子

0x80(0b1000 0000)反转之后就是0x01(0b0000 0001)

0x50(0b0101 0000)反转之后就是0x0A (0b0000 1010)

输出数据反转(REFOUT)

输出数据反转和输入反转类似

只不过是以宽度 (WIDTH)为基准反转而已

0x8000(0b1000 0000 0000 0000)反转是0x0001 (0b0000 0000 0000 0001)

0xA000(0b1010 0000 0000 0000)反转是0x0005 (0b0000 0000 0000 0101)

使用CRC的协议会将这些都规定好,需要时查询即可

CRC手算过程

这只是帮助理解所使用的过程,应用中一般人不会手算CRC吧,这里先忽略初值,选取简单的数据和多项式

模二加减(异或运算(XOR))

这三个说法是等价的,即运算的两个算子相同为0,不同为1,c语言中为

^

例子:0b0^0b1=0b1  0b1100^0b1110=0b0010

运算过程

  1. 在数据最后补0(位数与宽度相等)
  2. 将多项式(完整的)的最高项与数据的第一个1对齐(其他位当作0即可(也就是不改变数据))
  3. 整体进行异或操作
  4. 重复上述操作,直到所有数据均参与了计算(参与有2种,作为0被掠过也算参与)

例子

 

所以计算值为0b1110(0xE) 

进入在线计算网站验证如下图

 直接计算

输入数据不反转

 从刚才的手算过程中可以看出,如果每一个数据均需要被遍历,如果为0则掠过,如果为1则进行异或操作

很明显需要使用循环来进行操作

因为是按照字节(Byte)来传输数据

因此需要嵌套循环,外层改变传入的数据,内层遍历8个位(bit)

  1. 首先建立寄存器(CRC),这里以16位为例,则需要数据类型为u16
  2. 填入初值,这里是0xFFFF
  3. 因为要向左移位,因此将数据放到寄存器的高8位(32位就是最高的8位)中(与高8位异或放入)
  4. 如果最高位为1,则先左移1位后寄存器与多项式异或,如果为0则仅仅左移1位
  5. 重复直到所有数据都参与
  6. 寄存器中剩下的便是CRC结果

为什么要放到高8位呢

因为异或具有交换律和结合律,这里不证明,请查百度

因此后8位本质上是将高8位的数据所计算的异或值(本应该与下一个字节进行计算的)先一步进行运算

在往后只需要将下一个字节的数据与移位后的高8位(上一个字节提前计算的)异或即可了

代码例子(C语言写的)

u16 CRC16(u8 * Dat, int Len)
{
	u16 Poly = 0x8005;
	u16 CRC = 0xffff;
	u8 Zj = 0;
	
	for (int i = 0; i < Len; i++)
	{
//在这里加输入反转
		Zj = (Dat[i]);
		CRC ^= (Zj<< 8);
		for (int j = 0; j < 8; j++)
		{
			if (CRC & 0x8000)
			{
				CRC <<= 1;
				CRC ^= Poly;
			}
			else
			{
				CRC <<= 1;
			}
		}
	}
//在这里加输出反转
	return ((CRC)^0x0000);
}

如果需要反转输出,则在输出异或之前加上反转部分的代码即可

反转 的实现

u16 CRC_Reverse_16(u16 Dat)
{
	u16 Res = 0;
	u16 zj = Dat;
	for (int i = 0; i < 16; i++)
	{
		if (zj & 0x8000)
			Res += 1<<i;
		zj <<= 1;
	}
	return Res;
}
u8 CRC_Reverse_8(u8 Dat)
{
	u8 Res = 0;
	u8 zj = Dat;
	for (int i = 0; i < 8; i++)
	{
		if (zj & 0x80)
			Res += 1 << i;
		zj <<= 1;
	}
	return Res;
}

输入数据反转

方法1

可以直接在输入之前加上反转的代码即可,即上面加注释的位置

优点:编程简单,无需改架构

缺点:运算量大,会占用更多时间

方法2 

将上述代码改为向右移动,这样做之后输出也会自动反转,如需正向则需反转回来(这个运算量小)

注意:需要与寄存器的低8位进行异或(32位就是最低的8位)

例子代码

u16 CRC16_ModBus(u8 * Dat, int Len)
{
	u16 Poly = CRC_Reverse_16(0x8005);
	u16 CRC = 0xffff;
	u8 Zj = 0;

	for (int i = 0; i < Len; i++)
	{
		CRC ^= Dat[i];
		for (int j = 0; j < 8; j++)
		{
			if (CRC & 0x0001)
			{
				CRC >>= 1;
				CRC ^= Poly;
			}
			else
			{
				CRC >>= 1;
			}
		}
	}
	return (CRC ^ 0x0000);
}

这个是用来计算MODBUS的CRC校验的

即输入输出均反转

查表法

上述计算过程运算量相等较大,因为每移动一位均需要计算一次异或操作,适合上位机使用

下位机还是建议使用查表法,如果将1Byte的数据做成表,只需要之前的1/8的计算量

原理

异或操作的交换律和结合律

将1Byte数据直接进行消除,直接计算的原理类似,也是将异或值提前计算,只是在程序开跑之前就计算了,这个计算值就是表

表的生成

使用表是以空间换时间的操作方式

为了便于计算出表,我们将初始值设为0(也可以不为全0,只不过需要多一步异或而已,与输入的那Byte数据进行异或的部分必须为0),输入0-0xFF的1Byte数据进行CRC,输出值为表

生成MODBUS的表的例程↓

 

#include "stdio.h"
typedef unsigned short int u16;
typedef unsigned char u8;
u16 CRC_Reverse_16(u16 Dat)
{
    u16 Res = 0;
    u16 zj = Dat;
    for (int i = 0; i < 16; i++)
    {
        if (zj & 0x8000)
            Res += 1 << i;
        zj <<= 1;
    }
    return Res;
}
u16 CRC16_ModBus1(u8 Dat)
{
    u16 Poly = CRC_Reverse_16(0x8005);
    u16 CRC = 0x0000;
    u8 Zj = 0;
    Zj = (Dat);
    CRC ^= (Zj);
    for (int j = 0; j < 8; j++)
    {
        if (CRC & 0x0001)
        {
            CRC >>= 1;
            CRC ^= Poly;
        }
        else
        {
            CRC >>= 1;
        }
    }
    return (CRC ^ 0x0000);
}
int main()
{
    u16 i = 0;
    for (i = 0; i <= 0xFF; i++)
    {
        printf("0x%04X,", CRC16_ModBus1(i));
    }
}

 此代码对应输入翻转↓

#include "stdio.h"
typedef unsigned short int u16;
typedef unsigned char u8;
u16 CRC_Reverse_16(u16 Dat)
{
    u16 Res = 0;
    u16 zj = Dat;
    for (int i = 0; i < 16; i++)
    {
        if (zj & 0x8000)
            Res += 1 << i;
        zj <<= 1;
    }
    return Res;
}
u16 CRC16_ModBus1(u8 Dat)
{
    u16 Poly = CRC_Reverse_16(0x8005);
    u16 CRC = 0x0;
    u8 Zj = 0;
    Zj = (Dat);
    CRC ^= (Zj << 8);
    for (int j = 0; j < 8; j++)
    {
        if (CRC & 0x8000)
        {
            CRC <<= 1;
            CRC ^= Poly;
        }
        else
        {
            CRC <<= 1;
        }
    }
    return (CRC ^ 0x0000);
}
int main()
{
    u16 i = 0;
    for (i = 0; i <= 0xFF; i++)
    {
        printf("0x%04X,", CRC16_ModBus1(i));
    }
}

查表法代码

这是生成的表

const u16 T[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
};

查表的代码和直接计算类似,只需要注意两点

  1. 每次移动8位
  2. 移动之前要与数据进行异或操作来生成表的序号(移动异或之后的低位就相当于直接计算的一个循环走完,重新输入数据了,这里是将8位数据直接消除,因此需要生成标号,这个标号仅使用即可,无需放入寄存器中,因为它马上就因为移位丢失了)

MODBUS的查表法代码

u16 CRC16_ModBus2(u8 *Dat, int Len)
{
	u16 CRC = 0xffff;//初始值
	u8 Zj;
	for (int i = 0; i < Len ; i++)
	{
		Zj = (CRC & 0xFF)^ Dat[i];//生成标号,一次将低8位数据消除
		CRC >>= 8;
		CRC ^= T[Zj];
	}
	return (CRC);
}

输入不反转的查表代码

uint16_t CRC16_CHP(uint8_t *Dat, int Len)
{
    uint16_t CRC_Res = 0x0000;
    uint8_t Zj;
    for (int i = 0; i < Len; i++)
    {
        Zj = ((CRC_Res & 0xFF00) >> 8) ^ Dat[i];
        CRC_Res <<= 8;
        CRC_Res ^= CRC16_ModBus_Table[Zj];
    }
    return (CRC_Res);
}

成品

typedef unsigned          char u8;
typedef unsigned short     int u16;

u16 CRC_Reverse_16(u16 Dat)
{
	u16 Res = 0;
	u16 zj = Dat;
	for (int i = 0; i < 16; i++)
	{
		if (zj & 0x8000)
			Res += 1<<i;
		zj <<= 1;
	}
	return Res;
}
u8 CRC_Reverse_8(u8 Dat)
{
	u8 Res = 0;
	u8 zj = Dat;
	for (int i = 0; i < 8; i++)
	{
		if (zj & 0x80)
			Res += 1 << i;
		zj <<= 1;
	}
	return Res;
}
u16 CRC16_ModBus_Forward(u8 * Dat, int Len)
{
	u16 Poly = 0x8005;
	u16 CRC = 0xffff;
	u8 Zj = 0;
	
	for (int i = 0; i < Len; i++)
	{
		Zj = CRC_Reverse_8(Dat[i]);
		CRC ^= (Zj<< 8);
		for (int j = 0; j < 8; j++)
		{
			if (CRC & 0x8000)
			{
				CRC <<= 1;
				CRC ^= Poly;
			}
			else
			{
				CRC <<= 1;
			}
		}
	}
	return (CRC_Reverse_16(CRC)^0x0000);
}
u16 CRC16_ModBus_Reverse(u8 * Dat, int Len)
{
	u16 Poly = CRC_Reverse_16(0x8005);
	u16 CRC = 0xffff;
	u8 Zj = 0;

	for (int i = 0; i < Len; i++)
	{
		CRC ^= Dat[i];
		for (int j = 0; j < 8; j++)
		{
			if (CRC & 0x0001)
			{
				CRC >>= 1;
				CRC ^= Poly;
			}
			else
			{
				CRC >>= 1;
			}
		}
	}
	return (CRC ^ 0x0000);
}
const u16 T[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
};
u16 CRC16_ModBus_Table(u8 *Dat, int Len)
{
	u16 CRC = 0xffff;
	u8 Zj;
	for (int i = 0; i < Len ; i++)
	{
		Zj = (CRC & 0xFF)^ Dat[i];
		CRC >>= 8;
		CRC ^= T[Zj];
	}
	return (CRC);
}

  • 26
    点赞
  • 134
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: CRC循环冗余校验)是一种常用的错误检测技术。CRC主要通过附加一个固定长度的校验码到数据中,来检测数据在传输过程中是否发生了错误。在CRC中,校验码是通过对数据进行除运算得到的。 具体而言,给定一个二进制的数据块D,CRC会附加一个n位的校验码C到数据末尾,形成一个新的数据块D'C。校验码C被设计为使得D'C能够被一个特定的生成多项式G整除,如果在传输过程中发生了错误,这个多项式无整除D'C,因此可以通过检查余数来判断是否发生了错误。 CRC的关键是选择适当的生成多项式G。常见的生成多项式包括CRC-8、CRC-16和CRC-32等。不同的生成多项式会产生不同长度的校验码,长度越长,检测到错误的可能性越高。 在进行CRC运算时,接收方会将接收到的数据块D'C除以生成多项式G。如果余数为0,则表明没有错误;如果余数不为0,则表明发生了错误。在这种情况下,接收方可以向发送方请求重新发送数据,以确保数据的正确性。 总而言之,CRC是一种通过附加校验码到数据中的方,可以有效地检测传输过程中的错误。通过选择适当的生成多项式,可以提高CRC的检测能力。 ### 回答2: CRC循环冗余校验)是一种常见的错误检测技术,用于检测数据传输过程中是否发生了错误。它使用多项式除的原理,将输入数据与生成多项式进行模2除运算,得到校验码,然后将校验码附加到原始数据中进行传输,接收方再次进行CRC运算并检查校验码是否匹配,从而判断数据是否出现错误。 CRC主要基于封闭性和循环性的原则,它将传输的数据看作二进制数,使用一个生成多项式进行除运算。该生成多项式在CRC中是固定的,不同的生成多项式对应不同的CRC校验码。一般情况下,生成多项式选取低于数据位数的最高次项为1,其余项为0。 CRC过程中,首先需要进行数据的比特填充,即将数据位数按照生成多项式的次数进行扩展,扩展的位数是生成多项式的位数减1。然后进行模2除运算,逐位比较输入数据和生成多项式的对应位,若两位相同,则结果为0,否则为1。运算结束后得到CRC校验码,然后将其附加到原始数据后面,发送给接收方。 接收方收到数据后,同样经过除运算得到接收到的校验码。如果接收到的校验码与发送方计算的校验码相同,说明数据传输过程中没有发生错误。如果两个校验码不匹配,则表示数据存在错误,需要重传或进行其他处理。 CRC循环冗余校验是一种简单、高效且广泛应用的错误检测技术,它能够检测发生在数据传输过程中的大多数错误,并且具有较低的错误漏检率和错误检测率。在网络通信、存储系统、计算机硬件等领域广泛应用,能够确保数据的完整性和可靠性。 ### 回答3: CRC循环冗余校验)是一种常用的数据校验方。在计算机通信和存储中,为了确保数据的完整性和准确性,我们经常需要对数据进行校验。CRC是通过对数据进行除运算来实现的。 CRC校验的基本原理是,将数据看作二进制多项式,然后利用除运算来计算其余数。具体步骤如下: 1. 将待校验的数据表示为二进制形式,并在最高位补充k个0,其中k为CRC校验码的位数。 2. 选择一个固定的生成多项式G(x),作为除数。该多项式的系数即为CRC校验码的系数。 3. 将生成多项式G(x)左移k位,与待校验数据相异或。 4. 重复步骤3,直到所有数据位都被处理完。 5. 最后所得结果就是校验码,可以将其附加在原始数据后面发送。 6. 接收方根据接收到的数据,再次进行CRC校验。如果余数为0,则认为数据没有出错;反之,则认为数据出错。 CRC可以提供较高的校验效率和误码检测能力,常被应用于计算机网络、存储器、传感器等领域。它的优点是计算简单、校验速度快,并且能够检测到多种错误,包括单比特差错、双比特差错和突发差错等。 总而言之,CRC循环冗余校验是一种常用的数据校验方,通过除运算来计算校验码,并能够高效地检测数据的错误。它在通信和存储领域发挥着重要作用,确保了数据的完整性和可靠性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值