详述循环冗余校验CRC(C代码)

本内容包括循环校验码CRC(Cyclic Redundancy Checksum)及其生成原理介绍,CRC-16校验码的使用与其校验码计算方法,汽车CAN的介绍,含有工程实际使用代码。紫色文字是超链接,点击自动跳转至相关博文。持续更新,原创不易!

目录:

一、CRC概念

1、什么是CRC

2、使用方法概述

二、CRC名称的定义

三、常用CRC与CAN的CRC介绍

1、标准CRC多项式

2、汽车CAN的CRC多项式

四、CRC校验算法前置知识

1、异或

2、模2加法

3、模2减法

4、模2除法

五、CRC原理

六、CRC-16校验码

1、校验码的使用

2、计算方法

3、代码示例

1)计算法   2)查表法

4、在线CRC校验与工具软件CRC校验


一、CRC概念

1、什么是CRC

CRC(Cyclic Redundancy Checksum)是一种检错技术,代表循环冗余校验和。

数据通信领域中最常用的一种差错校验码,其特征是信息字段和校验字段的长度可以任意选定。为尽量提高接受方收到数据的正确率,在接收方接收数据之前需要对数据进行差错检测,当且仅当检测的结果为正确时接收方才真正收下数据。检测的方式有多种,常见的有奇偶校验因特网校验循环冗余校验等。

2、使用方法概述

循环冗余校验是一种用于校验通信链路上数字传输准确性的计算方法(通过某种数学运算来建立数据位和校验位的约定关系)。

发送方计算机使用某公式计算出被传送数据所含信息的一个值,并将此值附在被传送数据后,接收方计算机则对同一数据进行相同的计算,应该得到相同的结果。

如果这两个CRC结果不一致,则说明发送中出现了差错,接收方可要求发送方重新发送该数据。

二、CRC名称的定义

这里需要知道几个组成部分或者说计算概念:多项式公式、多项式简记式、数据宽度、初始值、结果异或值、输入值反转、输出值反转、参数模型。

1、多项式公式

对于CRC标准除数,一般使用多项式(或二项式)公式表示,如下图中除数11011(poly值为0x1b)的二项式为G(X) = X^4+X^3+X^1+1,X的指数就代表了该bit位上的数据为1,(最低位为0)。

这里特别注意一下位数问题,除数的位数为二项式最高次幂+1(4+1 = 5),这个很重要。

2、多项式简记式

通过对CRC的基本了解我,多项式的首尾必定为1,而这个1的位置在下一步计算一定为0,所以就把前面这个1给省略掉了,出现了一个叫简记式的东西,如上例中除数11011的简记式为1011,很多看过CRC高级语言源码的人会知道,对于CRC_16标准下G(X) = X^16+X^15+X^2+1(16#18005)的poly值实际上是8005,这里使用的就是简记式。后面“三、常用的CRC与CAN的CRC介绍”会对这个用法做一个说明。

3、数据宽度

数据宽度指的就是CRC校验码的长度(二进制位数),知道了CRC的运算概念和多项式,就可以理解这个概念了,CRC长度始终要比除数位数少1,与简记式长度一致。

以上三个数据就是我们经常能够用到的基本数据。

4、初始值与结果异或值

在一些标准中,规定了初始值,则数据在进行上述二项式运算之前,需要先将要计算的数据与初始值的最低字节进行异或,然后再与多项式进行计算。

而在结果异或值不为零的情况下,则需要将计算得到的CRC结果值再与结果异或值进行一次异或计算,得到的最终值才是我们需要的CRC校验码。

这里可以看出,初始值与结果值的位数要求与数据宽度一致。

5、输入值反转与输出值反转

输入值反转的意思是在计算之前先将二项式反转,然后再用得到的新值和数据进行计算。如对于G(X) = X^16+X^15+X^2+1(16#18005),其正向值为1 1000 0000 0000 0101,反转值则为1010 0000 0000 0001 1即16#A001输出值反转则是将最终得到的CRC结果反转。

通常,输入值反转后的结果值也会反转,所以这两个选项一般同向,我们只有在在线CRC计算器中会看到自由选择正反转的情况存在。

三、常用CRC与CAN的CRC介绍

任意一个由二进制位串组成的代码都可以和一个系数仅为‘0’和‘1’取值的多项式一一对应。例如:代码#100000111B(0X107)对应的多项式为X^8+X^2+X^1+1,而多项式X^16+X^15+X^2+1对应的代码#11000000000000101B(0X18005)。

1、标准CRC多项式

ModBus CRC16的模型为:X^16+X^15+X^2+1,如下图所示,由于16进制数只能0~15位,所以舍去X16即可得简记式0X8005

通过把0x8005H的“高位”与“低位”进行互换,#1010 0000 0000 0001B(A001H)自右向左变换为#1000 0000 0000 0101B(8005H),得到最终的多项式:0xA001H(1010 0000 0000 0001 B)。

2、汽车CAN的CRC多项式

图3.2.1 多项式表格

有关CAN的相关内容移步:CAN总线,关于CAN FD的相关内容移步:CAN FD与CAN协议区别

CRC校验的计算采用的是模2(mod2)运算法,不进位亦不借位,实际上是被除的二进制多项式与生成多项式两个操作数的逻辑异或运算。

将待处理报文组成的被除多项式A(x)左移n位,即乘以x^n,n为生成多项式g(x)的最高幂值,空出的n位用来存放CRC计算的余数p(x),因此CAN总线控制器实际计算出的带有CRC校验码的报文序列为A(x)·x^n+p(x)。

生成多项式g(x)的首位和末位的系数必须为1。

图3.2.2 流程图

在数据收发的过程中,对由帧起始、仲裁场、控制场、数据场(远程帧无数据场)组成的位流进行CRC循环冗余校验。

CAN协议规定,当CAN总线上连续出现5个以上相同极性的数据时,在第5位数据发送后,CAN总线控制器自动填充一位相反极性的数据发送出去。该位即为填充位,用以避免CAN总线上长时间保持同一数据极性,确保区分错误帧和过载帧。在CAN总线控制器的检错逻辑设计中,为进行CRC计算,被除的多项式(A(x)、B(x))系数由帧起始、仲裁场、控制场、数据场(远程帧无数据场)部分的无填充位流给定。

CAN总线协议中规定,CAN总线控制器检错设计所采用的CRC算法生成多项式为g(x) = X^15+X^14+X^10+X^8+X^7+X^4+X^3+1(如图3.2.1)。CAN总线控制器中采用的CRC校验多项式能够校验七级,它的检错能力很强,误判率极低,成为提高CAN总线设备间数据传输正确性的有效保证手段。被除的多项式[A(x)、B(x)]被g(x)多项式除(其系数以2为模),得到此多项式除法的余数即为发送到CAN总线上的CRC序列。

汽车CAN的CRC由CAN控制器自动完成,这里仅仅做个了解即可。

四、CRC校验算法前置知识

在学习CRC校验算法之前,先复习一下CRC会涉及的主要几个主要的算法。

1、异或

异或,就是不同为1,相同为0,运算符号是^。

0^0 = 0,0^1 = 1,1^1 = 0,1^0 = 1异或运算存在如下几个规律,需要了解。

0^x = x 即0 异或任何数等于任何数;

1^x = ~x 即1异或任何数等于任何数取反;

x^x = 0 即任何数与自己异或结果为0。

a ^ b = b ^ a 交换律,a ^ (b ^ c) = (a ^ b) ^c 结合律。

2、模2加法

模2加法相对于普通的算术加法,主要的区别在模2加法,不做进位处理。具体结果如下。0+0 = 0,0+1 = 1,1+1 = 0,1+0 = 1我们发现模2加法的计算结果,同异或运算结果一模一样。进一步推演,我们会发现,异或运算的5个规律,同样适合于模2加法。

3、模2减法

模2减法相对于普通的算术减法,主要的区别在模2减法,不做借位处理。具体结果如下。0-0 = 00-1 = 11-1 = 01-0 = 1我们发现模2减法的计算结果,同模2加法,以及异或的运算结果一模一样。进一步推演,我们会发现,异或运算的5个规律,同样适合于模2减法。这里,就不在一一列举了。

4、模2除法

模2除法相对于普通的算术除法,主要的区别在模2除法,它既不向上位借位,也不比较除数和被除数的相同位数值的大小,只要以相同位数进行相除即可。

五、CRC原理

在K位信息码(目标发送数据)后再拼接R位校验码,使整个编码长度为N位,因此这种编码也叫(N,K)码。

通俗的说,就是在需要发送的信息后面附加1个数(即校验码),生成1个新的发送数据发送给接收端。这个数据要求能够使生成的新数据被1个特定的数整除。此处整除需要引入模2除法的概念。

CRC校验的具体做法:

①选定一个标准除数(P位二进制数据串)

②在要发送的数据(M位)后面加上P-1位0,然后将这个新数(M+P-1位)以模2除法的方式除以上面这个标准除数,所得到的余数也就是该数据的CRC校验码(注:余数必须比除数少且只少一位,不够就补0)

③将这个校验码附在原M位数据后面,构成新的M+P-1位数据,发送给接收端。

④接收端将接收到的数据除以标准除数,如果余数为0则认为数据正确。

注意:CRC校验中有两个关键点:

①要预先确定一个发送端和接收端都用来作为除数的二进制比特串(或多项式);

②把原始帧与上面选定的除数进行二进制除法运算,计算出FCS。

前者可以随机选择,也可按国际上通行的标准选择,但最高位和最低位必须均为“1”。

计算示例:

设需要发送的信息为M = 1010001101,产生多项式对应的代码为P = 110101,R = 5。在M后加5个0,然后对P做模2除法运算,得余数r(x)对应的代码:01110。故实际需要发送的数据是101000110101110。

完整的校验示例如下:

现假设选择的CRC生成多项式为 P(X) = X^4 + X^3 + 1,要求出二进制序列10110011的CRC校验码。下面是具体的计算过程 :

①将多项式转化为二进制序列,由 P(X) = X^4 + X^3 + 1可知二进制一共有5位,第4位、第3位和第0位分别为1,则序列为11001

多项式的位数5,则在数据帧的后面加上5-1位0,数据帧变为10110011 0000,然后使用模2除法除以除数P(X) = 11001,得到余数;

③将计算出来的CRC校验码添加在原始帧的后面,真正的数据帧为10110011 0100,再把这个数据帧发送到接收端;

④接收端收到数据帧后,用上面选定的除数P(X),用模2除法除去,验证余数是否为0,若为0则说明数据帧未出错。

六、CRC-16校验码

1、校验码的使用

现选择最常用的CRC-16校验,说明它的使用方法。根据Modbus协议,常规485通讯的信息发送形式如下:

   地址  功能码   数据信息  校验码

   1byte   1byte     nbyte      2byte

CRC校验是前面几段数据内容的校验值,为一个16位数据,发送时,低8位在前,高8为最后。

例如:信息字段代码为: 1011001,校验字段为:1010。

发送方:发出的传输字段为:  1 0 1 1 0 0 1     1 0 10

                                                  信息字段       校验字段

接收方:使用相同的计算方法计算出信息字段的校验码,对比接收到的实际校验码,如果相等及信息正确,不相等则信息错误;或者将接受到的所有信息除多项式,如果能够除尽,则信息正确。

2、计算方法

①预置 16 位寄存器为十六进制 0xFFFF(即全为 1) ,称此寄存器为 CRC 寄存器;

②把第一个 8 位数据与 16 位 CRC 寄存器的低位相异或,把结果放于 CRC 寄存器;

③检测相异或后的CRC寄存器的最低位,若最低位为1,CRC寄存器先右移1位,再与多项式A001H进行异或;若为0,则CRC寄存器右移1位,无需与多项式进行异或。

④重复步骤 3 ,直到右移 8 次,这样整个 8 位数据全部进行了处理;

⑤重复步骤 2 到步骤 4,进行下一个 8 位数据的处理;

⑥最后得到的 CRC 寄存器即为 CRC 校验码。

附参考:
数据(16进制):01 03 61 00 00 02的CRC校验码:F7 D

查表法是将移位异或的计算结果做成了一个表,就是将0~255放入一个长度为16位的寄存器中的低8位,高8位填充0,然后将该寄存器与多项式0XA001按照上述3、4步骤,直到8位全部移出,最后寄存器中的值就是表格中的数据,高8位、低8位分别单独一个表。

3、代码示例

1)计算法

/*
* 函数名 :CRC16
* 描述 : 计算CRC16
* 输入 : puchMsg---数据地址,usDataLen---数据长度
* 输出 : 校验值
*/
uint16_t CRC16_MudBus(uint8_t *puchMsg, uint8_t usDataLen){
	
	uint16_t uCRC = 0xffff;//CRC寄存器
	
	for(uint8_t num=0;num<usDataLen;num++){
		uCRC = (*puchMsg++)^uCRC;//把数据与16位的CRC寄存器的低8位相异或,结果存放于CRC寄存器。
		for(uint8_t x=0;x<8;x++){	//循环8次
			if(uCRC&0x0001){	//判断最低位为:“1”
				uCRC = uCRC>>1;	//先右移
				uCRC = uCRC^0xA001;	//再与0xA001异或
			}else{	//判断最低位为:“0”
				uCRC = uCRC>>1;	//右移
			}
		}
	}
	return uCRC;//返回CRC校验值

}

int main(void) {
	uint8_t x[]={0x01 ,0x03 ,0x61 ,0x00 ,0x00 ,0x02};
	while(1){
		uint16_t wCRC_16 = CRC16_MudBus(x,sizeof(x)); //获取CRC16校验值
		//输出校验值wCRC_16 即可(注意:CRC16-->低位在前,高位在后)
	}
}

2)查表法

特点:速度快,语句少,但表格占用一定的程序空间。

/* CRC 高位字节值表 */ 
const  uint8_t auchCRCHi[] = 
{ 
	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低位字节值表*/ 
const  uint8_t auchCRCLo[] = 
{ 
	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 
};

/*
* 函数名 :CRC16
* 描述 : 计算CRC16
* 输入 : puchMsg---数据地址,usDataLen---数据长度
* 输出 : 校验值
*/
uint16_t CRC16(uint8_t *puchMsg, uint8_t usDataLen) 
{ 
	uint8_t uchCRCHi = 0xFF ;              // 高CRC字节初始化  
	uint8_t uchCRCLo = 0xFF ;              // 低CRC 字节初始化 

	uint8_t uIndex ;                      // CRC循环中的索引   
	
	while (usDataLen--)                  // 传输消息缓冲区  
	{ 
		uIndex = uchCRCHi ^ *puchMsg++ ; // 计算CRC         
		uchCRCHi = uchCRCLo ^ auchCRCHi[uIndex]; 
		uchCRCLo = auchCRCLo[uIndex]; 
	} 

	return (uchCRCHi << 8 | uchCRCLo);	// MODBUS 规定高位在前
}

int main(void)
{
	uint8_t x[]={0x01 ,0x03 ,0x61 ,0x00 ,0x00 ,0x02};
	while(1){
		uint16_t wCRC_16 = CRC16(x,sizeof(x)); //获取CRC16校验值
		//输出校验值wCRC_16 即可
	}
}

此法已经使用在实际的项目中,较详细的说明移步STM32单片机-输入捕获、FFT测频

再介绍一个不错的CRC校验的网站Easics CRC Tool,现在估计所有的工程应用均来自该网站生成的代码。使用方便。该网站的代码不易于CRC的学习和研究,但保证是对的。 

4、在线CRC校验与工具软件CRC校验

1)在线CRC校验

网址: CRC(循环冗余校验)在线计算

附参考:

数据(16进制):01 03 61 00 00 02 CRC校验:F7 DB

网址:On-line CRC calculation and free library

附参考:

数据(16进制):01 03 61 00 00 02 CRC校验:F7 DB

2)工具软件CRC校验

新势力CRC16计算器与CRC16校验码计算器v1.2


首先要有一个具体目标,那些所有你认为必须前置的知识,都可以在完成目标的过程中学到。觉得不错,动动发财的小手点个赞哦!

  • 20
    点赞
  • 147
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱上电路设计

你的鼓励是我创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值