手撕CRC校验

目录

前言

CRC校验的目的

CRC校验算法前置知识

CRC校验算法的多项式

CRC校验算法原理

第一步:计算初始值

第二步:进行输入反转

第三步:进行模二除法

第四步:进行输出反转

第五步:进行结果异或

包含所有可能的一个样例:

C++实现代码

避坑指南

初始值计算时的坑

输入反转的坑

被除数的坑

输出反转的坑

输出值的坑

结果异或的坑

补充C语言实现代码


前言

CRC校验网上能找到有很多运算过程和代码实现,但基本上运算过程都是不完整的,而且对实现过程的解释通常都局限于个别算法,以及1个字节的数据案例。因此往往看懂了一个算法例子,换一个算法或增加输入的数据后,还是不知道怎么计算。

比如初始值和结果异或值有什么要求?当多项式宽度小于8位时初始值怎么处理,当多项式宽度大于8位时输入数据又要怎么处理?等等。

代码的实现也缺少注释,网上能找到的代码都是循环法和查表法,很奇怪的是针对不同的CRC算法,都需要不同的代码实现,感觉不同的算法之间就像没有统一规律的联系,只知道运算结果正确,但不知道为什么可以这样写。

本文将带你从头剖析CRC的算法,以及其背后的运用方式和数学原理,最终用使所有CRC校验的算法都能复用同一套代码。

CRC校验的目的

CRC校验属于一种数据摘要算法,其基本原理是利用算法对传输的信息进行运算,生成一串固定长度的数字摘要,它并不是一种加密机制,但可以用来判断数据有没有被窜改。

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

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

那么,CRC校验的具体做法就是

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

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

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

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

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

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

二是把原始帧与上面选定的除数进行二进制除法运算,计算出CRC校验值。

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

例如:

使用算法CRC-4/ITU,计算数据0b00011100(0x1C),的CRC校验值。

CRC算法名称:CRC-4/ITU;多项式公式:x4 + x + 1;宽度(width):4;初始值(init)(hex):00;输入反转(refin):true;多项式(poly):0b0011,即0x03;输出反转(refout):true;结果异或值(xorout)(hex):00。

其过程如下,注意输入值有反转(后面会有详解):

因为该算法要进行输出反转,因此得到的校验和(CheckSum)为0b00000010(0x02)。

发送端使用上面计算的校验和,把它附在需要传输的消息数据后面,就是最终要传输的数据0b1110010100000010(0x1C02)。

接收端使用接收到的数据0b1110010100000010(0x1C02),使用同一个算法运算

对第一个字节0x1C运算后得到的结果同上,是0b01000000;

对第二个字节0x02运算,先反转再与上一个字节的运算结果进行异或:

因此被除数就是0,根据crc校验的算法,使用模二除法除以指定除数0b11011,得到是也0,则认为数据是正确的。

CRC校验算法前置知识

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

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

1.0^0 = 0

2.0^1 = 1

3.1^1 = 0

4.1^0 = 1

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

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

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

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

4.a ^ b = b ^ a 交换律

5.a ^ (b ^ c) = (a ^ b) ^c 结合律

模2加法

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

模2减法

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

模2除法

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

CRC校验算法的多项式

以CRC算法名称:CRC-4/ITU为例,其多项式公式:x4 + x + 1;多项式生成的二进制序列位数是最高次幂加一,即5位,即从第0位开始到第4位,,实际上原式为:x4 + 03 + 02 + x + 1(数字都代表是幂次),对应到比特位就是0b10011,完整的生成项是0x13,这个值就是运算时作为被除数的值,是正常算法中使用的值。

最高位的幂次就是宽度width。

但实际代码中处理时并不使用完整的生成项作为除数,而是使用舍去最高位1的多项式poly=0b0011,即0x03。

原因是想通过代码实现crc算法,当被除数(每个字节的数据)最高位为1时才与除数(完整生成项)相除,而此时无论除数还是被除数的最高位都是1,因此可以使被除数左移一位后,直接与生成项忽略掉最高位1后,即多项式poly相除。

这是多项式poly为什么总是少了最高位1的原因。

CRC校验算法原理

crc校验有几个关键参数:

WIDTH:宽度,即CRC比特数。

POLY:生成项的简写,以16进制表示。例如:CRC-32即是0x04C11DB7,忽略了最高位的"1"。完整的生成项是0x104C11DB7。这里要注意,作为运算时除数的应该是完整的生成项,但是因为被除数最高位为1时需要左移1位后再运算,正好可以与多项式poly(舍弃最高位补的1)后相对应,因此代码实现时使用的是忽略最高位“1”的生成项作为除数。

INIT:这是算法开始时寄存器(crc)的初始化预置值,十六进制表示。

REFIN:待测数据的每个字节是否按位反转,True或False。

REFOUT:在计算后之后,异或输出之前,整个数据是否按位反转,True或False。

XOROUT:计算结果与此参数异或后得到最终的CRC值。

声明一个运算变量crc,当算法的width

第一步:计算初始值

当宽度width不足8的倍数时,初始值需要左移8的倍数减width位。

然后使crc的值等于它和初始值的异或值。因为该算法初始值为0,所以crc异或0后的值无变化;

第二步:进行输入反转

输入值数据的每个字节需按位反转后再进行运算,若算法的width>8,则需要在反转后,通过左移补足至总位数与width相等。本例输入值是0x1C,按位反转后是0x38,width

然后使crc的值等于它和输入值反转后的异或值。

第三步:进行模二除法

用当前crc值作为被除数,多项式的值最高位补1后作为除数进行模二除法(异或运算),其中要注意的是:

1.若crc的最高位是0,除不尽,直接左移1位(这样可以保持数据的位数是8的倍数),使crc的值等于它左移1位后的值;

2.若crc的最高位是1,因为多项式最高位也补了1,因此crc左移1位后,正好可以与多项式poly(舍弃最高位补的1)进行异或。使crc的值等于它和多项式poly异或后的值;

3.因为每个字节的数据是8位,所以不管多项式的width是多少,crc值作为被除数都是只运算8次

4.如果传输的数据不止1个字节,下一个字节按第二步处理后作为输入值,然后使crc的值等于它和输入值的异或值。当前crc值作为被除数继续运算。

第四步:进行输出反转

若crc作为被除数已运算完毕,若有输出反转,使crc的值等于它按位反转后的值

若没有输出反转,则根据多项式的宽度,需要对结果进行右移处理,使有效位数等于width。即使crc的值等于它右移crc变量的类型位数减width位后的值。

第五步:进行结果异或

若存在结果异或,使crc等于它与xorout异或后的值,此时crc就是最终的校验值。

包含所有可能的一个样例:

使用CRC算法名称:CRC-5/USB;多项式公式:x5 + x2 + 1;宽度(width):5;初始值(init):0x1F;输入反转(refin):true;多项式(poly):0x05;输出反转(refout):true;结果异或值(xorout):0x1F。

计算数据0x1C1C的crc校验值。

1.初始值(init):0x1F,即0b00011111,因为宽度width不足8的倍数,初始值需要左移3位,所以crc=0b11111000;

2.第一个数据输入反转:0x1C,即0b00011100,反转后为0b00111000,与原来的crc异或,得到crc=0b11000000;

3.crc=0b11000000作为被除数,完整多项式(0b00100101)作为除数进行异或运算;

4.因为数据包含多个字节,需要对下一个字节按第二步处理后作为输入值,继续运算;

输入值0x1C(0b00111000)按位反转后,与原本的crc值(0b01001000)进行异或运算后,得到新的crc=0b0111000作为被除数,继续运算;

得到的crc=0b01101000;

5.因为所有数据均处理完毕,需要进行输出反转,使crc的值等于它按位反转后的值,即crc=00000110;

6.结果异或,使crc等于它与xorout异或后的值,此时crc就是最终的校验值,crc=00011001;

C++实现代码

C++因为有类模板和函数模板,代码更简洁,实现代码如下:

头文件crc.h:

//crc.h
#pragma once
#ifndef __CRC_H__
#define __CRC_H__

#include<stdio.h>
#include<stdint.h>
#include<stdbool.h>
#include <stdlib.h>
//C++的结构体声明
template <class T>
struct crc_type {
    size_t width;
    size_t poly;
    T init;
    bool refin;
    bool refout;
    T xorout;
};
//C的结构体声明
//typedef struct crc_type {
//    size_t width;
//    size_t poly;
//    size_t init;
//    bool refin;
//    bool refout;
//    size_t xorout;
//}crc_type;

#endif


源文件crc.cpp:

//crc.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include"crc.h"
//模板类
crc_type< uint8_t >  crc4_ITU = { 4, 0x03, 0x00, true, true, 0x00 };
crc_type< uint8_t >  crc5_EPC = { 5, 0x09, 0x09, false, false, 0x00 };
crc_type< uint8_t >  crc5_ITU = { 5, 0x15, 0x00, true, true, 0x00 };
crc_type< uint8_t >  crc5_USB = { 5, 0x05, 0x1f, true, true, 0x1f };
crc_type< uint8_t >  crc6_ITU = { 6, 0x03, 0x00, true, true, 0x00 };
crc_type< uint8_t >  crc7_MMC = { 7, 0x09, 0x00, false, false, 0x00 };
crc_type< uint8_t >  crc8 = { 8, 0x07, 0x00, false, false, 0x00 };
crc_type< uint8_t >  crc8_ITU = { 8, 0x07, 0x00, false, false, 0x55 };
crc_type< uint8_t >  crc8_ROHC = { 8, 0x07, 0xff, true, true, 0x00 };
crc_type< uint8_t >  crc8_MAXIM = { 8, 0x31, 0x00, true, true, 0x00 };
crc_type< uint16_t >  crc16_IBM = { 16, 0x8005, 0x0000, true, true, 0x0000 };
crc_type< uint16_t > crc16_MAXIM = { 16, 0x8005, 0x0000, true, true, 0xffff };
crc_type< uint16_t > crc16_USB = { 16, 0x8005, 0xffff, true, true, 0xffff };
crc_type< uint16_t > crc16_MODBUS = { 16, 0x8005, 0xffff, true, true, 0x0000 };
crc_type< uint16_t > crc16_CCITT = { 16, 0x1021, 0x0000, true, true, 0x0000 };
crc_type< uint16_t > crc16_CCITT_FALSE = { 16, 0x1021, 0xffff, false, false, 0x0000 };
crc_type< uint16_t > crc16_X25 = { 16, 0x1021, 0xffff, true, true, 0xffff };
crc_type< uint16_t > crc16_XMODEM = { 16, 0x1021, 0x0000, false, false, 0x0000 };
crc_type< uint16_t > crc16_DNP = { 16, 0x3D65, 0x0000, true, true, 0xffff };
crc_type< uint32_t > crc32 = { 32, 0x04c11db7, 0xffffffff, true, true, 0xffffffff };
crc_type< uint32_t > crc32_MPEG2 = { 32, 0x4c11db7, 0xffffffff, false, false, 0x00000000 };

//自定义参数模型
//crc_type< uint8_t >  custom = { 5, 0x15, 0x25, true, true, 0x10 };
crc_type< uint8_t >  custom = { 6, 0x15, 0x31, true, true, 0x19 };


size_t outOfRange(size_t width)
{
    size_t ret = 0;
    for (int i = 0; i < width; i++)
    {
        size_t oneBit = 0x01 << (width - 1 - i);
        ret += oneBit;
    }
    return ret;
}

template<class T>
T reverseRet(T resByte, size_t bitSize)
{
    T ret = 0;
    for (size_t i = 0; i < bitSize; i++)
    {
        if (resByte & 0x01)//数据或1,得到最小位的值
        {
            T oneBit = 0x01 << (bitSize - 1 - i);//如果是1则通过移位的方式达到单个位的反转
            ret += oneBit;//把所有位反转后的值加和,得到所有位按位反转后的值
        }
        resByte >>= 1;//每取走1位就右移1位,取下一位
    }
    return ret;
}

template<class T>
T funCrc(T crc , size_t bitSize, size_t moveBit, crc_type<T> optionType, unsigned char* arr, size_t len)
{
    while (len--)
    {
        if (optionType.refin)
        {
            crc ^= ((T)reverseRet(*arr++,8))<<(bitSize - 8);
        }
        else
        {
            crc ^= (T)(*arr++) << (bitSize - 8);
        }
        for (int i = 0; i < 8; i++)
        {
            if (crc & moveBit)
            {
                crc = (crc <<= 1) ^ (optionType.poly << (bitSize - optionType.width));
            }
            else
            {
                crc <<= 1;
            }
        }
    }

    if (optionType.refout)
    {
        crc = reverseRet(crc, bitSize);

        if (optionType.xorout)
        {
            crc ^= optionType.xorout;
        }
        return crc;
    }
    else
    {
        if (optionType.xorout)
        {
            crc ^= optionType.xorout;
        }
        return crc >> (bitSize - optionType.width);
    }
}

template <class T>
uint32_t crc_check(crc_type<T> optionType, unsigned char* arr,size_t len)
{
    if (optionType.init > outOfRange(optionType.width))
    {
        printf("初始值超范围\n");
        return -1;
    }
    if (optionType.xorout > outOfRange(optionType.width))
    {
        printf("结果异或值超范围\n");
        return -1;
    }
    size_t bitSize = 8;
    size_t moveBit = 0x80;
    if (optionType.width <= 8)
    {
        bitSize = 8;
        moveBit = 0x80;
    }
    else if (optionType.width > 8 && optionType.width <= 16)
    {
        bitSize = 16;
        moveBit = 0x8000;
    }
    else if (optionType.width > 16)
    {
        bitSize = 32;
        moveBit = 0x80000000;
    }
    else
    {
        printf("多项式宽度超范围\n");
        return -1;
    }
    T crc = optionType.init << (bitSize - optionType.width);
    return funCrc(crc, bitSize, moveBit, optionType, arr, len);
}

int main()
{
    unsigned char arr[] = { 0x1C,0x1C };
    //unsigned char arr[] = { 0x1C };
    //unsigned char arr[] = { 0x1C,0x02 };

    printf("crc4_ITU=%02X\n", crc_check(crc4_ITU, arr, sizeof(arr) / sizeof(char)));
    printf("crc5_EPC=%02X\n",crc_check(crc5_EPC, arr, sizeof(arr) / sizeof(char)));
    printf("crc5_ITU=%02X\n",crc_check(crc5_ITU, arr, sizeof(arr) / sizeof(char)));
    printf("crc5_USB=%02X\n",crc_check(crc5_USB, arr, sizeof(arr) / sizeof(char)));
    printf("crc6_ITU=%02X\n", crc_check(crc6_ITU, arr, sizeof(arr) / sizeof(char)));
    printf("crc7_MMC=%02X\n", crc_check(crc7_MMC, arr, sizeof(arr) / sizeof(char)));
    printf("crc8=%02X\n", crc_check(crc8, arr, sizeof(arr) / sizeof(char)));
    printf("crc8_ITU=%02X\n", crc_check(crc8_ITU, arr, sizeof(arr) / sizeof(char)));
    printf("crc8_ROHC=%02X\n", crc_check(crc8_ROHC, arr, sizeof(arr) / sizeof(char)));
    printf("crc8_MAXIM=%02X\n", crc_check(crc8_MAXIM, arr, sizeof(arr) / sizeof(char)));
    printf("crc16_IBM=%04X\n", crc_check(crc16_IBM, arr, sizeof(arr) / sizeof(char)));
    printf("crc16_MAXIM=%04X\n", crc_check(crc16_MAXIM, arr, sizeof(arr) / sizeof(char)));
    printf("crc16_USB=%04X\n", crc_check(crc16_USB, arr, sizeof(arr) / sizeof(char)));
    printf("crc16_MODBUS=%04X\n", crc_check(crc16_MODBUS, arr, sizeof(arr) / sizeof(char)));
    printf("crc16_CCITT=%04X\n", crc_check(crc16_CCITT, arr, sizeof(arr) / sizeof(char)));
    printf("crc16_CCITT_FALSE=%04X\n", crc_check(crc16_CCITT_FALSE, arr, sizeof(arr) / sizeof(char)));
    printf("crc16_X25=%04X\n", crc_check(crc16_X25, arr, sizeof(arr) / sizeof(char)));
    printf("crc16_XMODEM=%04X\n", crc_check(crc16_XMODEM, arr, sizeof(arr) / sizeof(char)));
    printf("crc16_DNP=%04X\n", crc_check(crc16_DNP, arr, sizeof(arr) / sizeof(char)));
    printf("crc32=%08X\n", crc_check(crc32, arr, sizeof(arr) / sizeof(char)));
    printf("crc32_MPEG2=%08X\n", crc_check(crc32_MPEG2, arr, sizeof(arr) / sizeof(char)));
    printf("custom=%02X\n", crc_check(custom, arr, sizeof(arr) / sizeof(char)));

    return 0;
}

避坑指南

CRC校验的基本思想,就是使用待测数据的每个字节作为被除数,多项式作为除数进行模二除法(异或)后,得到一个与多项式宽度“一致”的CRC校验值。

在整个运算过程中,有几个关键因素,包括:多项式公式,宽度(width),初始值(init),输入反转(refin),多项式(poly),输出反转(refout),结果异或值(xorout)

参与运算的除数和被除数的bitSize一定是8的整数倍。

初始值、结果异或值、CRC校验和都不能超过宽度所能代表的位的最大值。

初始值计算时的坑

1.初始值的位数应该与宽度width一致,不足在低位补零,因此初始值不能超过宽度所能代表的位的最大值。比如CRC-5/USB多项式是x5 + x2 + 1,宽度(width)是5,最高位数的bit4(从bit0开始数),所能代表的最大值是0b11111,即0x1F,因此init参数的值只能取[0x00,0x1F];

2.初始值位数不足8的整数倍,通过左移数据使其位数达到8的整数倍。因为数据是按字节传输的,每个字节都是8位,所以初始值得转换成8位的倍数才能参与下一步运算。

比如CRC-5/USB多项式是x5 + x2 + 1,宽度(width)是5,只有5位。若初始值是0x1F,即0b11111,计算时需要左移8-5=3位,即变成0b11111000。

输入反转的坑

1.数据是按字节反转的,每个字节(只有8位)都需要按位反转;

2.因为数据肯定是按字节来的,固定是8位,但它作为被除数参与运算时,可能因为除数的多项式不同,导致1个字节的有效数据在bitSize中的低位,因此需要固定左移bitSize-8位,把有效数据移到最高的8个位。

当多项式宽度小于等于8位时,数据的位数就用8,需要左移的位数是8-8=0,仅需要在计算初始值时,把初始值的位数调整至8位即可;

比如:CRC-5/USB多项式是x5 + x2 + 1,宽度(width)是5,因为参与运算的被除数的bitSize是8,有效数据已经占据了所有位,因此需要左移的位数是8-8=0位;

当多项式宽度大于8位时,需要调整数据,使有效数据左移到bitSize的最高位。

比如CRC-16/CCITT-FALSE的多项式是x16 + x12 + x5 + 1,宽度(width)是16,多项式作为除数,它的位数大于8。因为参与运算的被除数的bitSize一定是8的整数倍,此时被除数的bitSize为16,但有效的1个字节的数据都在低位,所以要对数据进行左移处理,使数据移动到高位,左移的位数是16-8=8位,这样数据从0b00000000+1个字节数据,变为0b1个字节数据+00000000。

被除数的坑

实际算法中除数是多项式最高位补1的完整生成项,但代码中除数是多项式,原因前面讲多项式时已经说过了。

那么只需要注意一点,代码中被除数和除数的bitsize要一致,被除数的bitSize经过前面的一系列操作是已经确定的,是8位的整数倍。

如果多项式作为除数,它的位数不是8位的整数倍,则需要左移bitSize-width位补足,是多项式中的有效数据占据bitSize的高位。

输出反转的坑

输出值不能超过宽度所能代表的位的最大值。

因为不论使用哪个多项式,得到的余数都是8的整数倍,因此输出反转时直接按位反转即可,不需要进行任何移位操作,同时反转后的值因为移位运算时补的0都反转到高位,有效数值都反转到低位,反转后的运算结果一定在宽度所能代表的位的最大值的范围内,所以最后不需要再右移处理结果。

比如上例CRC-5/USB多项式是x5 + x2 + 1,对数据0x1C1C进行crc校验,最后除完得到的余数是crc=0b01100000。

因为输出值不能超过宽度所能代表的位的最大值,本例宽度width=5,所能代表的最大值是0b11111,即0x1F,因此输出结果只能取[0x00,0x1F]。

此时crc=0b01100000,即0x60是越界的(0x60>0x1F)。

但对输出值crc=0b01100000进行按位反转后,使crc=0b00000110,即crc=0x06,因为0x06∈[0x00,0x1F],此时输出值不再越界,就可以直接参与后续的运算。

输出值的坑

输出值不能超过宽度所能代表的位的最大值,如果算法不涉及输出反转,则最终得到的值需要右移bitSize减宽度(width)位。

比如:使用CRC算法名称:CRC-5/EPC;多项式公式:x5 + x3 + 1;宽度(width):5;初始值(init):0x09;输入反转(refin):false;多项式(poly):0x09;输出反转(refout):false;结果异或值(xorout):0x00。

计算数据0x1C的crc校验值。

因为输出值不能超过宽度所能代表的位的最大值,本例宽度width=5,所能代表的最大值是0b11111,即0x1F,因此输出结果只能取[0x00,0x1F]。

最终得到的值如果右移bitSize减宽度(width)位,可以保证输出结果在[0x00,0x1F]。

因此进行模二除法后得到的值为0b11111000,即0xF8是越界的,不能直接作为crc校验的结果,需要右移bitSize减宽度(width)位,即8-5=3位,因此最终crc=0b00011111(0x1F)。

结果异或的坑

仅需注意一点,异或的值不能超过宽度所能代表的位的最大值。

接上例,本例宽度width=5,所能代表的最大值是0b11111,即0x1F,因此结果异或的值只能取[0x00,0x1F]。

补充C语言实现代码

因为C语言不支持模板,所以调用的函数中因为参数模型的多项式字节数不同,导致返回值的数据类型不同,所以对最终结果按位反转需要写三个函数,分别按照8、16、32位的crc数据类型进行反转。

头文件crc.h:

//crc.h
#pragma once
#ifndef __CRC_H__
#define __CRC_H__

#include<stdio.h>
#include<stdint.h>
#include<stdbool.h>
#include <stdlib.h>
//C++的结构体声明
//template <class T>
//struct crc_type {
//    size_t width;
//    size_t poly;
//    T init;
//    bool refin;
//    bool refout;
//    T xorout;
//};
//C的结构体声明
typedef struct crc_type {
    size_t width;
    size_t poly;
    size_t init;
    bool refin;
    bool refout;
    size_t xorout;
}crc_type;

#endif

源文件crc.c

//crc.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"crc.h"
//标准参数模型
crc_type crc4_ITU = { 4, 0x03, 0x00, true, true, 0x00 };
crc_type crc5_EPC = { 5, 0x09, 0x09, false, false, 0x00 };
crc_type crc5_ITU = { 5, 0x15, 0x00, true, true, 0x00 };
crc_type crc5_USB = { 5, 0x05, 0x1f, true, true, 0x1f };
crc_type crc6_ITU = { 6, 0x03, 0x00, true, true, 0x00 };
crc_type crc7_MMC = { 7, 0x09, 0x00, false, false, 0x00 };
crc_type crc8 = { 8, 0x07, 0x00, false, false, 0x00 };
crc_type crc8_ITU = { 8, 0x07, 0x00, false, false, 0x55 };
crc_type crc8_ROHC = { 8, 0x07, 0xff, true, true, 0x00 };
crc_type crc8_MAXIM = { 8, 0x31, 0x00, true, true, 0x00 };
crc_type crc16_IBM = { 16, 0x8005, 0x0000, true, true, 0x0000 };
crc_type crc16_MAXIM = { 16, 0x8005, 0x0000, true, true, 0xffff };
crc_type crc16_USB = { 16, 0x8005, 0xffff, true, true, 0xffff };
crc_type crc16_MODBUS = { 16, 0x8005, 0xffff, true, true, 0x0000 };
crc_type crc16_CCITT = { 16, 0x1021, 0x0000, true, true, 0x0000 };
crc_type crc16_CCITT_FALSE = { 16, 0x1021, 0xffff, false, false, 0x0000 };
crc_type crc16_X25 = { 16, 0x1021, 0xffff, true, true, 0xffff };
crc_type crc16_XMODEM = { 16, 0x1021, 0x0000, false, false, 0x0000 };
crc_type crc16_DNP = { 16, 0x3D65, 0x0000, true, true, 0xffff };
crc_type crc32 = { 32, 0x04c11db7, 0xffffffff, true, true, 0xffffffff };
crc_type crc32_MPEG2 = { 32, 0x4c11db7, 0xffffffff, false, false, 0x00000000 };

//自定义参数模型
crc_type custom = { 5, 0x15, 0x25, true, true, 0x10 };

size_t outOfRange(size_t width)
{
    size_t ret = 0;
    for (int i = 0; i < width; i++)
    {
        size_t oneBit = 0x01 << (width - 1 - i);
        ret += oneBit;
    }
    return ret;
}

//结果按位反转:
//因为参数模型的多项式字节数不同,导致返回值的数据类型不同,
//所以对最终结果按位反转需要写三个函数,分别按照8、16、32位进行反转
//8位数据反转
uint8_t reverseRet8(uint8_t resByte, size_t bitSize)
{
    uint8_t ret = 0;
    for (size_t i = 0; i < bitSize; i++)
    {
        if (resByte & 0x01)//数据或1,得到最小位的值
        {
            uint8_t oneBit = 0x01 << (bitSize - 1 - i);//如果是1则通过移位的方式达到单个位的反转
            ret += oneBit;//把所有位反转后的值加和,得到所有位按位反转后的值
        }
        resByte >>= 1;//每取走1位就右移1位,取下一位
    }
    return ret;
}
//16位数据反转
uint16_t reverseRet16(uint16_t resByte, size_t bitSize)
{

    uint16_t ret = 0;
    for (size_t i = 0; i < bitSize; i++)
    {
        if (resByte & 0x01)
        {
            uint16_t oneBit = 0x01 << (bitSize - 1 - i);
            ret += oneBit;
        }
        resByte >>= 1;
    }
    return ret;
}
//32位数据反转
uint32_t reverseRet32(uint32_t resByte, size_t bitSize)
{
    uint32_t ret = 0;
    for (size_t i = 0; i < bitSize; i++)
    {
        if (resByte & 0x01)
        {
            uint32_t oneBit = 0x01 << (bitSize -1 - i);
            ret += oneBit;
        }
        resByte >>= 1;
    }
    return ret;
}
//数据每个字节按字节反转,因为每个字节位数固定是8,所以只有一种数据类型,只需要一个反转函数即可
unsigned char reverseInput(unsigned char resByte)
{
    uint8_t ret = 0;
    for (int i = 0; i < 8; i++)
    {
        if (resByte & 0x01)
        {
            uint8_t oneBit = 0x01 << (7 - i);
            ret += oneBit;
        }
        resByte >>= 1;
    }
    return ret;
}

uint8_t funCrc8(uint8_t crc8 , size_t bitSize, size_t moveBit, crc_type optionType, unsigned char* arr, size_t len)
{
    while (len--)
    {
        if (optionType.refin)
        {
            //数据按字节反转,反转后与输入值(初始值)异或
            crc8 ^= reverseInput(*arr++);
        }
        else
        {
            crc8 ^= (*arr++);
        }
        for (int i = 0; i < 8; i++)
        {
            if (crc8 & moveBit)
            {
                crc8 = (crc8 <<= 1) ^ (optionType.poly << (bitSize - optionType.width));
            }
            else
            {
                crc8 <<= 1;
            }
        }
    }

    if (optionType.refout)
    {
        crc8 = reverseRet8(crc8, bitSize);

        if (optionType.xorout)
        {
            crc8 ^= optionType.xorout;
        }
        return crc8;
    }
    else
    {
        if (optionType.xorout)
        {
            crc8 ^= optionType.xorout;
        }
        return crc8 >> (bitSize - optionType.width);
    }
}

uint16_t funCrc16(uint16_t crc16, size_t bitSize, size_t moveBit, crc_type optionType, unsigned char* arr, size_t len)
{
    while (len--)
    {
        if (optionType.refin)
        {
            //数据按字节反转,反转后与输入值(初始值)异或
            //因为数据肯定是按字节来的,固定是8位,但输入值(初始值)可能比8位小或者比8位大
            //初始值比8位小在前面已经通过左移位处理了,这里要对初始值比8位大的情况进行处理
            //当初始值比8位大时,需要调整数据值,使数据左移,位数达到8的倍数,与初始值位数一致
            crc16 ^= ((uint16_t) reverseInput(*arr++))<<(bitSize-8);//如果需要用C++模板的话这里得用bitSize-8而不是optionType.width-8,因为宽度可能是小于8的,而小于8时按8计算
        }
        else
        {   
            //即使数据不用按字节反转,也要处理成位数与初始值一致
            crc16 ^= (uint16_t)(*arr++)<<(bitSize-8);
        }
        //按字节处理数据,因为每字节数据只有8位,所以只需要按位异或8次
        for (int i = 0; i < 8; i++)
        {
            //判断最高位是否为1
            //因为真正的除数是在多项式poly的基础上,在左边增加1位,而且值固定是1/
            //因此经过处理的数据作为被除数除以除数时,若被除数最高位不是1肯定是除不了的,直接左移1位后接着判断最高位是否为1
            if (crc16 & moveBit)
            {
                //若经过处理的数据最高位是1,则因为被除数最高位也是1,那么数据左移1位后与多项式poly(是8的倍数)异或
                //刚好异或的位数就是8的倍数(实际上异或前数据已经左移了一位)
                crc16 = (crc16 <<= 1) ^ (optionType.poly << (bitSize - optionType.width));
            }
            else
            {
                crc16 <<= 1;
            }
        }
    }
    //判断整个数据结果是否需要按位反转
    if (optionType.refout)
    {
        crc16 = reverseRet16(crc16, bitSize);
        //判断计算结果是否需要与XOROUT参数异或
        //注意异或值不能超过多项式poly的位数范围,比如多项式是x5 + x2 + 1,poly转换成位表示是00101
        //因此最高位数的bit4,所能代表的最大值是0b11111,即0x1F,因此XOROUT参数的值只能取[0x00,0x1F]
        if (optionType.xorout)
        {
            crc16 ^= optionType.xorout;
        }
        return crc16;
    }
    else
    {
        if (optionType.xorout)
        {
            crc16 ^= optionType.xorout;
        }
        return crc16 >> (bitSize - optionType.width);
    }
}

uint32_t funCrc32(uint32_t crc32, size_t bitSize, size_t moveBit, crc_type optionType, unsigned char* arr, size_t len)
{
    while (len--)
    {
        if (optionType.refin)
        {
            crc32 ^= ((uint32_t)reverseInput(*arr++))<<(bitSize-8);
        }
        else
        {
            crc32 ^= ((uint32_t)(*arr++))<<(bitSize-8);
        }
        for (int i = 0; i < 8; i++)
        {
            if (crc32 & moveBit)
            {
                crc32 = (crc32 <<= 1) ^ (optionType.poly << (bitSize - optionType.width));
            }
            else
            {
                crc32 <<= 1;
            }
        }
    }

    if (optionType.refout)
    {
        crc32 = reverseRet32(crc32, bitSize);

        if (optionType.xorout)
        {
            crc32 ^= optionType.xorout;
        }
        return crc32;
    }
    else
    {
        if (optionType.xorout)
        {
            crc32 ^= optionType.xorout;
        }
        return crc32 >> (bitSize - optionType.width);
    }
}


uint32_t crc_check(crc_type optionType, unsigned char* arr,size_t len)
{
    if (optionType.init > outOfRange(optionType.width))
    {
        printf("初始值超范围\n");
        return -1;
    }
    if (optionType.xorout > outOfRange(optionType.width))
    {
        printf("结果异或值超范围\n");
        return -1;
    }
    //与多项式宽度有关,宽度小于8的倍数,向上取到8的倍数
    size_t bitSize = 8;
    //与多项式宽度有关,宽度小于8的倍数,向上取到8的倍数
    //作用是后面计算时用来“与”数据,判断数据最高位是否为1
    size_t moveBit = 0x80;
    if (optionType.width <= 8)
    {
        bitSize = 8;
        moveBit = 0x80;
        //初始值位数不足8的倍数,通过左移数据使其位数达到8的倍数
        //注意初始值不能超过多项式poly的位数范围,比如多项式是x5 + x2 + 1,poly转换成位表示是00101
        //因此最高位数的bit4,所能代表的最大值是0b11111,即0x1F,因此init参数的值只能取[0x00,0x1F]
        uint8_t crc8 = optionType.init << (bitSize - optionType.width);
        return funCrc8(crc8, bitSize, moveBit, optionType, arr, len);
    }
    else if (optionType.width > 8 && optionType.width <= 16)
    {
        bitSize = 16;
        moveBit = 0x8000;

        uint16_t crc16 = optionType.init << (bitSize - optionType.width);
        return funCrc16(crc16, bitSize, moveBit, optionType, arr, len);
    }
    else if (optionType.width > 16)
    {
        bitSize = 32;
        moveBit = 0x80000000;
        uint32_t crc32 = optionType.init << (bitSize - optionType.width);
        return funCrc32(crc32, bitSize, moveBit, optionType, arr, len);
    }
    else
    {
        return -1;
    }
}

int main()
{
    unsigned char arr[] = { 0x1C,0x1C };
    //unsigned char arr[] = { 0x1C };
    
    //printf("%02X\n", reverse(0x1c,8));
    printf("crc4_ITU=%02X\n", crc_check(crc4_ITU, arr, sizeof(arr) / sizeof(char)));
    printf("crc5_EPC=%02X\n",crc_check(crc5_EPC, arr, sizeof(arr) / sizeof(char)));
    printf("crc5_ITU=%02X\n",crc_check(crc5_ITU, arr, sizeof(arr) / sizeof(char)));
    printf("crc5_USB=%02X\n",crc_check(crc5_USB, arr, sizeof(arr) / sizeof(char)));
    printf("crc6_ITU=%02X\n", crc_check(crc6_ITU, arr, sizeof(arr) / sizeof(char)));
    printf("crc7_MMC=%02X\n", crc_check(crc7_MMC, arr, sizeof(arr) / sizeof(char)));
    printf("crc8=%02X\n", crc_check(crc8, arr, sizeof(arr) / sizeof(char)));
    printf("crc8_ITU=%02X\n", crc_check(crc8_ITU, arr, sizeof(arr) / sizeof(char)));
    printf("crc8_ROHC=%02X\n", crc_check(crc8_ROHC, arr, sizeof(arr) / sizeof(char)));
    printf("crc8_MAXIM=%02X\n", crc_check(crc8_MAXIM, arr, sizeof(arr) / sizeof(char)));
    printf("crc16_IBM=%04X\n", crc_check(crc16_IBM, arr, sizeof(arr) / sizeof(char)));
    printf("crc16_MAXIM=%04X\n", crc_check(crc16_MAXIM, arr, sizeof(arr) / sizeof(char)));
    printf("crc16_USB=%04X\n", crc_check(crc16_USB, arr, sizeof(arr) / sizeof(char)));
    printf("crc16_MODBUS=%04X\n", crc_check(crc16_MODBUS, arr, sizeof(arr) / sizeof(char)));
    printf("crc16_CCITT=%04X\n", crc_check(crc16_CCITT, arr, sizeof(arr) / sizeof(char)));
    printf("crc16_CCITT_FALSE=%04X\n", crc_check(crc16_CCITT_FALSE, arr, sizeof(arr) / sizeof(char)));
    printf("crc16_X25=%04X\n", crc_check(crc16_X25, arr, sizeof(arr) / sizeof(char)));
    printf("crc16_XMODEM=%04X\n", crc_check(crc16_XMODEM, arr, sizeof(arr) / sizeof(char)));
    printf("crc16_DNP=%04X\n", crc_check(crc16_DNP, arr, sizeof(arr) / sizeof(char)));
    printf("crc32=%08X\n", crc_check(crc32, arr, sizeof(arr) / sizeof(char)));
    printf("crc32_MPEG2=%08X\n", crc_check(crc32_MPEG2, arr, sizeof(arr) / sizeof(char)));
    printf("custom=%04X\n", crc_check(custom, arr, sizeof(arr) / sizeof(char)));


    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值