CRC是一种常见的校验方式,校验效果较好,在嵌入式系统中有着很广泛的应用。本文将对CR校验算法的C语言的实现进行详细地讲解。本文不对CRC校验的原理进行表述,只是讲解如何根据CRC校验要求或标准进行C语言代码的实现。本文对CRC类型进行讲解,并且针对不同的类型,都会有代码实现,帮助理解代码。
CRC代码测试工具:CRC代码测试工具:采用CRC Calculator。
CRC类型
简单来讲,CRC分为CRC-4、CRC-5、CRC-6、CRC-7、CRC-8、CRC-16、CRC-32;但是又可以进行更细节的分类,如图1所示,同样是CRC-8却有四种不同的计算方法,如CRC-8、CRC-8/ITU、CRC-8/ROHC、CRC-8/MAXIM。因此在进行CRC检验时,要根据实际情况进行选择,并且在双方进行通讯时,校验方式务必一致。
图1 CRC类型图
CRC参数:
从图1中可以看出,每一种CRC都有多个参数,这个参数用来确定检验算法具体如何实现,如图2所示。
图2 CRC参数图
CRC Name:
CRC算法的名称,也是在CRC校验使用过程中很重要的参数,使用时通讯双方使用同一种检验方式进行检验、对比才有意义
CRC Width:
CRC输出结果的位宽,与CRC名称中的-X相对应。
CRC Poly:
该参数是CRC校验的核心参数,它与CRC校验公式有关。例如:CRC-5/ITC的公式为x5+x4+x2+1,该公式等效为x5×1+x4×1+x3×0+x2×1+x1×0+x0×1,二进制表示其阶数关系:10101(必须去掉最高阶数),二进制的10101等于十六进行的0x15。同理其他检验的Poly也可以以同样的方式进行确定。
CRC Init:
该参数为CRC校验的结果的初始值。
CRC RefIn:
该参数确定输入的数据是否进行字节的反转,如果是true则需要反转,若为false则不需要反转。
CRC RefOut:
该参数确定CRC初步校验结果是否进行字节的反转,如果是true则需要反转,若为false则不需要反转。
CRC XorOut:
该参数确定CRC校验结果输出时的异或值。
CRC检验的过程的理解:
关于CRC校验,我的理解是,首先有一个最基本的CRC的算法,它是CRC的主干,他需要的参数是Width、Poly、Init;还有一些外围的算法,例如输入输出是否反转,输出结果需要异或的值,它的参数包括:RefIn、RefOut、XorOut。
首先以CRC-8为例讲一下最基本的CRC算法实现方式:
CRC算法的参数如图3右侧红框内所示,Poly=0x07;Init=0x00;RefIn=false(不反转);RefOut=false(不反转);XorOut = 0x00,输出异或上0x00,相当于不异或。
图3 CRC-8 参数
CRC校验的步骤(以CRC-8为例):
1、 预置1个8位的寄存器赋值为初始值(根据要0xFF 或 0x00)。
2、 将输入数据与CRC寄存器的高八位进行异或。
3、 判断CRC寄存器的最高位,如果为1,左移以为与CRC Ploy进行异或;如果为0,则只左移一位。
4、 重复步骤3,次数为输入数据的位数。
5、 重复将要进行CRC校验的所有数据进行输入,并完成2、3、4步骤。
代码实现如下:
unsigned char crc8(unsigned char *data, int data_len)
{
unsigned char data_in;
int i;
unsigned char crc = 0x00;//CRC 初始值
unsigned char crc_poly = 0x07;//CRC Ploy值
//依次输入数据
while (data_len--)
{
data_in = *data++;
crc = crc ^ data_in;//CRC的高八位异或输入数据
//判断8次CRC的做高位,并左移
for (i = 0; i < 8; i++)
{
if (crc & 0x80)//最高位为1
{
crc = (crc << 1) ^ crc_poly;
}
else //最高位为0
{
crc = crc << 1;
}
}
}
return crc ^0x00;
}
CRC工具测试结果:
图4 CRC-8计算结果
算法测试代码及测试结果:
#include
#include
#include "crc.h"
int main()
{
unsigned char test_data[4] = { 0x01, 0x02,0x03,0x04};
printf("%x\n", crc8(test_data, sizeof(test_data)));
system("pause");
}
下面对一个比较复杂的CRC算法进行讲解:
CRC-8/ROHC的参数如图右侧所示,它与CRC-8的不同之处在于Init=0xFF;RefIn和RefOut都为true,即输入数据和输出数据都需要进行反转。
图6 CRC-8/ROHC参数
8位无符号数据反转代码如下:
int invert_uint8(unsigned char *dest, unsigned char *src)
{
int i;
unsigned char temp = 0x00;
for (i = 0; i < 8; i++)
{
if (*src & (1 << i))
{
temp |= 1 << (7-i);
}
}
*dest = temp;
}
CRC校验代码如下:
unsigned char crc8_ROHC(unsigned char *data, int data_len)
{
unsigned char data_in;
int i;
unsigned char crc = 0xFF;
unsigned char crc_poly = 0x07;
while (data_len--)
{
data_in = *data++;
invert_uint8(&data_in,&data_in);
crc = crc ^ data_in;
for (i = 0; i < 8; i++)
{
if (crc & 0x80)
{
crc = (crc << 1) ^ crc_poly;
}
else
{
crc = crc << 1;
}
}
}
invert_uint8(&crc,&crc);
return crc ^ 0x00;
}
可以看出CRC-8/ROHC 与 CRC-8算法的不同,主要体现在那三个参数的不同,只需要根据那个参数的异同进行相应的操作即可;同理,如果CRC-8算法有其他参数的差异,只需要在基本算法的基础上,根据差异来进行代码的调整既可以实现算校验。
校验工具计算结果:
图7 CRC-8/ROHC计算结果
CRC算法校验测试代码和测试结果:
#include
#include
#include "crc.h"
int main()
{
unsigned char test_data[4] = { 0x01,0x02,0x03,0x04};
printf("%x\n", crc8_ROHC(test_data, sizeof(test_data)));
system("pause");
}
图8 CRC-8/ROHC算法计算结果
采用同样的方法,关于CRC-8系列的其他的算法也都可以写出相应的代码。
根据同样的原理:我们进行CRC-16系列代码的讲解。
CRC-16系列算法与CRC-8系列算法相比较,最大的区别在于CRC输出结果是16位的,其他部分的差异都可以在算法中根据参数进行调整。
CRC-16/IBM校验参数如下所示:
图9 CRC-16/IBM参数
16位无符号数反转代码:
int invert_uint16(unsigned short *dest, unsigned short *src)
{
int i;
unsigned short temp = 0x0000;
for (i = 0; i < 16; i++)
{
if (*src & (1 << i))
{
temp |= 1 << (15 - i);
}
}
*dest = temp;
}
CRC-16/IBM算法如下:
unsigned short crc16_IBM(unsigned char *data, int data_len)
{
unsigned char data_in;
int i;
unsigned short crc = 0x0000;
unsigned short crc_poly = 0x8005;
while (data_len--)
{
data_in = *data++;
invert_uint8(&data_in, &data_in);
crc = crc ^ (data_in<<8);
for (i = 0; i < 8; i++)
{
if (crc & 0x8000)
{
crc = (crc << 1) ^ crc_poly;
}
else
{
crc = crc << 1;
}
}
}
invert_uint16(&crc, &crc);
return crc ^ 0x0000;
}
CRC校验工具计算结果:
图10 CRC-16/IBM计算结果
CRC算法计算结果:
图11 CRC-16/IBM算法计算结果
按照上述的方法,我们可以根据不同CRC算法的参数要求,来实现言的CRC算法,并且可以定义自己的CRC算法。