CRC校验原理及CRC-8简单校验函数设计
CRC为循环冗余校验码,是一种常用的、具有检错、纠错能力的校验码。通常发送方在发送的数据之后,附上其CRC校验码。接收方收到数据后,也做同样的CRC校验,得到本地CRC校验码,并和接收到的CRC校验码比较,如果一致,则认为数据无误,如果不一致,则认为发收过程中出错。
CRC校验码是被计算数据和计算码的二进制模二除法的余数。二进制模二除法和二进制常规算法的区别是,模二除法在除法运算过程中采用模二减法,不产生借位,也就是0-1=1,0-0=0,1-1=0,1-0=1;而00-01=01,10-01=11。也就是按位异或的效果。
CRC-8计算方式
以MLX90614测温芯片的CRC-8校验为例,其计算码多项式为X^8 + X^2 + X^1 + 1
,也就是对应9个二进制位的数据,即100000111,第一位和最后一位都是1,也即0x107。在计算时,对于计算数据要计算到最后一位,因此实际计算时,计算数据要左移8位,再与0x107进行模二除法。
算法方面,要首先对计算数据进行从高位开始的第一个二进制“1”的位置搜索,因为模二除法从这里开始启动。
对于MLX90614读取的温度数据长度为5个字节,左移8位后就是6个字节,可以用64位无符号长整型装载。而9位的计算码可以用16位无符号短整型装载。
在算法计算过程中,要考虑前面一次计算后,余数已小于计算码时,需要给余数从计算数据中进行补位的各种情况,包括余数已为全0而计算还未完成的情况,所以每次补位的长度不一样。这里需要注意,补位是补到余数的后面,而不是和余数直接进行异或,而是补位后的余数再和计算码进行模二减法。
当计算到已无法通过补位继续进行计算的时候,则此余数就是最后的校验码。
CRC-8校验函数
MLX90614读操作的时序如下:
前面5个字节数据的校验码为PEC(第六个字节), 也即设计的校验函数PY_CRC_MLX90614_READ(0xb4, 0x07, 0xd2, 0x3a)计算结果为0x30。注意这里根据MLX90614输入数据特征在函数内部做了处理,输入变量是4个字节,实际对应是5个字节。
校验函数设计如下:
uint8_t PY_CRC_MLX90614_READ(uint8_t daddr, uint8_t Raddr, uint8_t dl, uint8_t dh)
{ //Written by Pegasus Yu 2022/02/22
uint64_t cdata = 0; //Computed total data
uint16_t data_t = 0; //Process data of CRC computing
uint16_t crc_poly = 0x0107; //X^8+X^2+X^1+1 total 9 effective bits. Computed total data shall be compensated 8-bit '0' before CRC computing from 9-1=8.
uint16_t index_t = 47; ///bit shifting index for initial '1' searching
uint16_t index = 47; //bit shifting index for CRC computing
uint8_t rec = 0; //bit number needed to be compensated for next CRC computing
cdata |= (((uint64_t)daddr)<<40); //device write address
cdata |= (((uint64_t)Raddr)<<32); //register access address
cdata |= (((uint64_t)(daddr+1))<<24); //device read address
cdata |= (((uint64_t)dl)<<16); //data LSB
cdata |= (((uint64_t)dh)<<8); //data HSB
//8-bit '0' compensated into cdata so cdata involves 48 bits stored in 64-bit format.
while(index_t>0)
{
if( (cdata>>index_t)&1 )
{
index = index_t;
index_t = 0;
data_t |= (cdata>>(index-8));
{
data_t = data_t ^ crc_poly;
}
while((index!=0x5555)&&(index!=0xaaaa))
{
if ((data_t>>7)&1) rec = 1;
else if ((data_t>>6)&1) rec = 2;
else if ((data_t>>5)&1) rec = 3;
else if ((data_t>>4)&1) rec = 4;
else if ((data_t>>3)&1) rec = 5;
else if ((data_t>>2)&1) rec = 6;
else if ((data_t>>1)&1) rec = 7;
else if ((data_t>>0)&1) rec = 8;
else rec = 9; ///
if((index-8)<rec)
{
data_t = data_t<<(index-8);
data_t |= (uint16_t)((cdata<<(64-(index-8)))>>(64-(index-8)));
index = 0x5555;
}
else
{
for(uint8_t i=1;i<=rec;i++)
{
data_t = (data_t<<1)|((cdata>>(index-8-i))&1) ;
}
if(rec!= 9)
{
data_t = data_t ^ crc_poly;
index -= rec;
}
else
{
data_t = 0;
index_t = index-8-1;
index = 0xaaaa;
}
}
}
if(index==0x5555) break;
}
else
{
index_t--;
if(index_t<8) break;
}
}
return (uint8_t)data_t;
}
CRC-8长数据校验
如上面的算法原理解释,这里的简单校验函数只支持到7个字节的输入数据校验,如果计算数据太长,则需要进行升级设计,设计上可以按照64位进行分段,当高一段64位计算到尽头时,在下一段64位开始对余数进行位补,以使得模二减法能继续进行下去。直到最后一个64位段计算到尽头,则余数是CRC-8的8位校验码。
而当进行的是CRC-16或CRC-32校验码计算时,依然可升级上述算法进行实现,因为无符号64位宽度能够覆盖16位和32位的模二除法(减法)计算要求。这是本算法相对于其它用8位单字节宽度来分段计算数据的算法的特点。
–End–