一、背景
CRC计算中常用空间换时间的方案即查表法,从前面的文章关于CRC的介绍中不难知道,crc的表其实就是对于整个字节256个数据的不同结果记录的集合。
1. 本文主要针对CRC16查表法表的计算,因为crc16有多种协议,因此代码做了针对不同协议的合集,先列出各个协议之间的区别
CRC算法名称 | 多项式 | 宽度 | 初始值 | 结果异或 | 输入反转 | 输出反转 |
---|---|---|---|---|---|---|
CRC16_CCITT | 1021 | 16 | 0000 | 0000 | true | true |
CRC16_X25 | 1021 | 16 | ffff | ffff | true | true |
CRC16_MAXIM | 8005 | 16 | 0000 | ffff | true | true |
CRC16_USB | 8005 | 16 | ffff | ffff | true | true |
CRC16_MODBUS | 8005 | 16 | ffff | 0000 | true | true |
CRC16_IBM | 8005 | 16 | 0000 | 0000 | true | true |
CRC16_DNP | 3d65 | 16 | 0000 | ffff | true | true |
2.模二除法的原理
crc校验码生成的原理是数据对多项式进行模二除法求余,因此查表法表格的数据就是对一个字节的所有数据进行模二除法后的生成的校验码。其基本原理如下:
data :0x01
poly :0x1021
seed :0x0
1000 0000 | 0000 0000 0000 0000 //(swap(0x01) << 16u)
1000 1000 0001 0000 1 //0x11021
------------------
1000 0001 0000 1000 0
1000 1000 0001 0000 1
-----------------------
1001 0001 1000 1000 //(0x9188) swap后得 0x1189
(swap) 0001 0001 1000 1001 //(0x1189)
swap的操作是针对这个协议CCITT要对输入的数据进行反转,具体反转的操作就是最高位和最低位的数据互换,次高位和次低位数据进行互换,具体操作可参考我的文章:(96条消息) C语言数据高低位转换(8-16-32位)_溪山行旅1024的博客-CSDN博客
使用代码的效果如下:检测高位遇到1相除并更新除数,遇到0则进行移位操作
for(i=0;i<256;i++){
for(int j=0;j<8;j++){
if(p&0x8000){
p=p<<1;
p = (p^poly);
}else{
p=p<<1;
}
}
}
当然此段代码只是数据已经进行过seed赋初值,输入反转之后的模二除法操作,具体的操作还需要加上对多项式poly和初始值seed的配置及最后对结果进行异或操作
将CRC16的协议类型作为参数分别获得seed和poly:
static u32 poly_seed_config(u8 poly_type)
{
u32 poly_cfg = 0;
switch(poly_type){
case CRC16_CCITT:
poly_cfg = 0x10210000;
break;
case CRC16_X25 :
poly_cfg = 0x1021ffff;
break;
case CRC16_MAXIM:
poly_cfg = 0x80050000;
break;
case CRC16_USB :
poly_cfg = 0x8005ffff;
break;
case CRC16_MODBUS:
poly_cfg = 0x8005ffff;
break;
case CRC16_IBM :
poly_cfg = 0x80050000;
break;
case CRC16_DNP :
poly_cfg = 0x3d650000;
break;
default:
break;
}
return poly_cfg ;
}
void main()
{
u16 poly = poly_seed_config(CRC16_IBM) >> 16u;
u16 seed = (u16)poly_seed_config(CRC16_IBM) & 0xffff;
}
此外,将CRC16的协议类型作为参数获得异或操作数:
static u16 result_config(u8 poly_type)
{
u16 result = 0;
switch(poly_type){
case CRC16_CCITT:
result = 0x0000;
break;
case CRC16_X25 :
result = 0xffff;
break;
case CRC16_MAXIM:
result = 0xffff;
break;
case CRC16_USB:
result = 0xffff;
break;
case CRC16_MODBUS:
result = 0x0000;
break;
case CRC16_IBM :
result = 0x0000;
break;
case CRC16_DNP :
result = 0xffff;
break;
default:
break;
}
return result ;
}
void main(){
u16 poly = poly_seed_config(CRC16_IBM) >> 16u;
u16 seed = (u16)poly_seed_config(CRC16_IBM) & 0xffff;
u16 xor_result = result_config(CRC16_IBM);
}
综上分析,我们只需要知道协议的类型就可以计算出0~ff数据的crc值,将他们放进一个char类型的表中,即为我们所需的CRC表 ,完整实现代码(以CRC16_IBM为例)如下:
void main()
{
u16 poly = poly_seed_config(CRC16_IBM) >> 16u;
u16 seed = (u16)poly_seed_config(CRC16_IBM) & 0xffff;
u16 xor_result = result_config(CRC16_IBM);
u16 table[256];
u16 i=0;
u16 p=0;
for(i=0;i<256;i++)
{
p=i^seed;
p=(u16)(crc_swap_u32((u32)p)>>16);
for(int j=0;j<8;j++)
{
if(p&0x8000)
{
p=p<<1;
p = (p^poly);
}else
{
p=p<<1;
}
}
table[i]=crc_swap_u32((u32)p)>>16 ^ xor_result;
printf("0x%xu, ", table[i]);
if((i+1)%8 == 0)
printf("\n");
}
}
最终计算的结果如下: