CRC原理

news2024/12/25 9:32:36

文章目录

        • 简介
      • CRC思想
        • 错误检测
        • 基本思想
        • 多项式运算
        • 没有进位的二进制计算
      • CRC计算方式
        • 发送方计算
        • 接收方校验
        • 多项式选择
    • CRC实现原理
      • CRC8
        • 整体处理数据
        • 单个处理数据与整体数据处理比较
        • 使用查找表加速计算
      • 扩展到CRC16
        • 整体处理数据
        • 单个处理数据与整体数据处理比较
        • 使用查找表加速计算

参考http://www.sunshine2k.de/articles/coding/crc/understanding_crc.html#ch44

简介

CRC即循环冗余校验码(Cyclic Redundancy Check):
数据通信领域中最常用的一种查错校验码,其特征是信息字段和校验字段的长度可以任意选定。循环冗余检查(CRC)是一种数据传输检错功能,对数据进行多项式计算,并将得到的结果附在帧的后面,接收设备也执行类似的算法,以保证数据传输的正确性和完整性。

CRC思想

错误检测

错误检测的目的是使消息接收方在接收到消息时, 检测该消息是否在传输过程中被损坏。

Message		              :  6 23
Message with checksum     :  6 23 29
Message after transmission:  6 27 29

上述例子中,使用累加的办法计算出8字节校验和:(6 + 23)mod 256,然后将结果附在消息后边进行传输,接收方就可以进行数据校验。

假设如下消息被接收后,使用此种方式并不能检测出来,此时需要一个更好的错误检测方法

Message after transmission:   8 21 29

基本思想

CRC算法基本思想是将消息视作一个大的二进制数据,然后除以一个固定的二进制数,得到的余数即是校验码,接收方可以执行相同的除法,然后将余数与校验和进行比较。
如消息为(6, 23)两个字节,可以视为16进制的0x0617,二进制0000-0110-0001-0111,然后使用一个固定的二进制除数1001,得出余数为0010,即十进制的1559除以9,得到余数2。
在这里插入图片描述

如上例中(6, 23)->(8, 21),使用除法就可以进行校验:
将(8, 21)视为0x0815,即十进制数2069,除以9,得到余数为8,与原来计算的2不一致。
在这里插入图片描述

使用除法在计算过程中,余数会产生很多变化,如果更多字节加入到消息中,计算结果会很快发生根本性的变化,这是加法做不到的地方。

多项式运算

可以将一个二进制数据看成一个多项式,如十进制数23,二进制为10111,可以将其看成这样一个多项式:

1x^4 + 0x^3 + 1x^2 + 1x^1 + 1*x^0

简单地就是:

x^4 + x^2 + x^1 + x^0

使用这种方法,比如我们想计算1101乘以1011,就可以使用多项式进行计算:

(x^3 + x^2 + x0)(x3 + x^1 + x^0) = x^6 + x^5 + x^4 + 3*x^3 + x^2 + x^1 + x^0

我们知道x就是2,所以可以进行进位将3*x3看成x4+x^3,这样公式可以简化为:

x^7 + x^3 + x^2 + x^1 + x^0

这里把基数x抽象出来的重点是,如果我们不知道x的具体值,就不能执行进位,可以将x2和x3看成不一样的类型,每个系数当成不同的类型被分离开,这里存在一个特殊的多项式算术,即所有的多项式系数必须为0或1,可以看作为有限域[0, 1],多项式的系数即为MOD 2;因此之前的计算结果为:

(x^3 + x^2 + x0)(x3 + x^1 + x^0) = x^6 + x^5 + x^4 + 3*x^3 + x^2 + x^1 + x^0
= x^6 + x^5 + x^4 + x^3 + x^2 + x^1 + x^0

这种计算方法也可以看作没有进位的二进制计算

没有进位的二进制计算

在CRC的计算过程中都是以没有进位的二进制进行计算的,例如:
加法:0+0=0 0+1=1 1+0=1 1+1=0
减法:0-0=0 1-0=1 1-1=0 0-1=1
这种计算方式会使一些概念变得没有意义,如1010并不比1001大,因为1001可以通过1010加减同一个数得到:
1001 = 1010 + 0011
1001 = 1010 - 0011

乘法:1101 * 1011 = 1101 + 11010 + 0000 + 1101000 = 1111111(1101根据1011左移然后求和)
除法:较为复杂一点,就是如何界定x要大于y,定义:当x最高位为1的位置大于或等于Y最高位为1的位置,例如:10110111 除以 10110
在这里插入图片描述

如果一个二进制数B可以通过二进制数A进行移位并与0进行XOR,则表明B可以被A整除如:
0000 ^ 0011 = 0011 ^ 0110 = 0101 ^ 1100 = 1001

CRC计算方式

发送方计算

在计算之前最重要的就是先选择一个多项式(poly)的除数,这个多项式可以任意选择,但是有一些多项式经过验证是比其他多项式好的;
多项式的宽度(MSB)在整个计算中是很重要的;如现在我们选择多项式为10011,它的宽度是4,而不是5;
选定多项式后,就可以进行计算,如:
原始消息(m(x)):1101011011
多项式(g(x)):10011
附加多项式的宽度个0后的消息(用于填充校验码):11010110110000
在这里插入图片描述

然后我们将计算结果附加到消息后边
发送的消息为 (t(x)):11010110111110

接收方校验

接收者可以通过两种方式来验证消息是否完整:
分离消息和校验和,计算附加多项式的宽度个0后的消息的校验和与消息中的校验和进行比较
通过接收到的消息和多项式直接计算校验和,验证是否为0
原理:t(x) = m(x) - m(x) mod g(x),得出t(x) mod g(x) = 0
在这里插入图片描述

多项式选择

传输的消息:T(x),多项式:G(x),产生的损坏:E(x)

(T(x) + E(x)) mod G(x) ?= 0

根据之前的说明:

T(x) mod G(x) = 0

所以对于所有情况,需要找出一个G(x),使得E(x) mod G(x) != 0,之前讲过可以将二进制转换成多项式:

  • 单bit错误,即E(x) = xi,最简单的就是使用至少两位的G
    eg:G(x) = xi + xj 即 E(x) / G(x) = xi / (xi + xj) = 1 / (1 + xj-i),很明显会得到一个小数,即不能整除
  • 2个bit错误,即E(x) = xi+xj(x > j) = xj(xj-i + 1) = xj(xk + 1),这个就比较复杂了,可以找出一些例子,比如G(x) = x16 + x12 + x5 + 1 作用于 k <= 32751,可以检测出任意两位的错误
  • 更多位错误:参考:https://users.ece.cmu.edu/~koopman/crc/

汉明距离:二进制A最少变换多少位可以转化位B,变换的位数就是汉明距离,此处可理解位多少位发生错误

CRC实现原理

整体处理数据:将所有要处理的数据看作一个大的二进制数,并填充多项式宽度位的0

单个处理数据:一个字节一个字节进行处理,最终得到与整体数据处理相同的结果;单个数据处理较为特殊,即先填充多项式宽度位0,然后与多项式进行取余。

CRC8

整体处理数据

需处理的数据:0xC2, 多项式0x1D,实际除数为100011101,

计算校验和过程:

11000010 00000000
100011101
---------	
010011001
 100011101
 ---------	
 000101111
  100011101
   100011101
    100011101
    ---------	
    001100101
     100011101
      100011101
      ---------	
      010001001
       100011101
       ---------	
       000001111 =  0x0F
// single byte
public static byte Compute_CRC8_Simple_OneByte_ShiftReg(byte byteVal)
{
    const byte generator = 0x1D;
    byte crc = 0; /* init crc register with 0 */
    /* append 8 zero bits to the input byte */
    byte[] inputstream = new byte[] { byteVal, 0x00 };
    /* handle each bit of input stream by iterating over each bit of each input byte */
    foreach (byte b in inputstream)
    {
        for (int i = 7; i >= 0; i--)
        {
            /* check if MSB is set */
            if ((crc & 0x80) != 0)
            {   /* MSB set, shift it out of the register */
                crc = (byte)(crc << 1);
                /* shift in next bit of input stream:
                 * If it's 1, set LSB of crc to 1.
                 * If it's 0, set LSB of crc to 0. */
                crc = ((byte)(b & (1 << i)) != 0) ? (byte)(crc | 0x01) : (byte)(crc & 0xFE);
                /* Perform the 'division' by XORing the crc register with the generator polynomial */
                crc = (byte)(crc ^ generator);
            }
            else
            {   /* MSB not set, shift it out and shift in next bit of input stream. Same as above, just no division */
                crc = (byte)(crc << 1);
                crc = ((byte)(b & (1 << i)) != 0) ? (byte)(crc | 0x01) : (byte)(crc & 0xFE);
            }
        }
    }
    return crc;
}

优化处理:

对于除数说明:100011101,即可理解为,只有当被除数的第9位为1时才认为被除数比除数大,即需要运算,所以可以先判断最高位是否为1(x & 0x80),如果是则直接左移,并与00011101(多项式)进行异或运算

1100001000000000
100011101
---------	
 10011001
 100011101
 ---------	
  0010111100
    100011101
    ---------	
    0011001010
      100011101
      ---------	
      010001001
       100011101
       ---------	
        00001111 =  0x0F
// single byte
public static byte Compute_CRC8_Simple_OneByte(byte byteVal)
{
    const byte generator = 0x1D;
    byte crc = byteVal; /* init crc directly with input byte instead of 0, avoid useless 8 bitshifts until input byte is in crc register */

    for (int i = 0; i < 8; i++)
    {
        if ((crc & 0x80) != 0)
        { /* most significant bit set, shift crc register and perform XOR operation, taking not-saved 9th set bit into account */
            crc = (byte)((crc << 1) ^ generator);
        }
        else
        { /* most significant bit not set, go to next bit */
            crc <<= 1;
        }
    }

    return crc;
}

单个处理数据与整体数据处理比较

单个个字节处理

00000001 00000000
              1 00011101
               -------------
              0 00011101  = Remainder

比较:

对比:
单字节处理完第一个字节结果(A):00011101
第二个字节数据(B) :00000010
整体数据处理完第一个字节结果©:00011111

结论:C = A XOR B

得出一个个字节计算出校验和的方式:

// byte array
public static byte Compute_CRC8_Simple(byte[] bytes)
{
    const byte generator = 0x1D;
    byte crc = 0; /* start with 0 so first byte can be 'xored' in */

    foreach (byte currByte in bytes)
    {
        crc ^= currByte; /* XOR-in the next input byte */

        for (int i = 0; i < 8; i++)
        {
            if ((crc & 0x80) != 0)
            {
                crc = (byte)((crc << 1) ^ generator);
            }
            else
            {
                crc <<= 1;
            }
        }
    }

    return crc;
}

使用查找表加速计算

回顾一下之前字节数组处理流程(data{0x01,0x02}, poly: 0x1D):

  1. 初始化crc为0x00
  2. crc XOR 输入字节0x01:0x00 ^ 0x01 = 0x01
  3. crc mod poly:0x01 mod ox1D = 0x1D
  4. 重复2、3步,知道计算完所有字节

这其中可以发现,使用crc对poly取余这一步可以优化,即可以先记录一个查找表,记录所有[0x00, 0xFF]对poly取余的结果,省略这一步的计算

// generate crc_8 lookup table
public static void CalulateTable_CRC8()
{
    const byte generator = 0x1D;
    crctable = new byte[256];
    /* iterate over all byte values 0 - 255 */
    for (int divident = 0; divident < 256; divident++)
    {
        byte currByte = (byte)divident;
        /* calculate the CRC-8 value for current byte */
        for (byte bit = 0; bit < 8; bit++)
        {
            if ((currByte & 0x80) != 0)
            {
                currByte <<= 1;
                currByte ^= generator;
            }
            else
            {
                currByte <<= 1;
            }
        }
        /* store CRC value in lookup table */
        crctable[divident] = currByte;
    }
}

// calc crc_8
public static byte Compute_CRC8(byte[] bytes)
{
    byte crc = 0;
    foreach (byte b in bytes)
    {
        /* XOR-in next input byte */
        byte data = (byte)(b ^ crc);
        /* get current CRC value = remainder */
        crc = (byte)(crctable[data]);
    }

    return crc;
}

扩展到CRC16

整体处理数据

输入数据{0x01, 0x02},多项式:0x1021

整体数据处理(判断最高位0x80 -> 0x8000):

00000001 00000010 00000000 00000000
              1 00010000 00100001
              -------------------
              0 00010010 00100001 = 0x1221 处理完第一个字节结果
                       10001 00000010 0001
              -------------------
                       00011 00100011 0001
                             10 00100000 0100001
                              -------------------
                              01 00000011 0101001
                                1 00010000 00100001
                                 -----------------------
                                0 000100110 1110011 = 0x1373

单个处理数据与整体数据处理比较

单个个字节处理

单字节字节处理:
00000001 00000000 00000000 00000000
              1 00010000 00100001
              -----------------------------
              0 00010000 00100001

比较:

对比:
单字节处理完第一个字节结果(A) :00010000 00100001
第二个字节数据(B) :00000000 00000010
整体数据处理完第一个字节结果© :00010010 00100001

结论:C = A XOR (B << 8)

得出一个个字节计算出校验和的方式:

// calc crc_16
public static ushort Compute_CRC16_Simple(byte[] bytes)
{
    const ushort generator = 0x1021; /* divisor is 16bit */
    ushort crc = 0; /* CRC value is 16bit */

    foreach (byte b in bytes)
    {
        crc ^= (ushort(b << 8); /* move byte into MSB of 16bit CRC */

        for (int i = 0; i < 8; i++)
        {
            if ((crc & 0x8000) != 0) /* test for MSB = bit 15 */
            {
                crc = (ushort((crc << 1) ^ generator);
            }
            else
            {
                crc <<= 1;
            }
        }
    }

    return crc;
}

使用查找表加速计算

回顾一下之前字节数组处理流程(data{0x01,0x02}, poly: 0x1021):

  1. 初始化crc为0x0000
  2. crc XOR 输入字节0x01左移8位:0x0000 ^ (0x01 << 8) = 0x0100
  3. crc mod poly:0x0100 mod 0x1021 = 0x1221
  4. 重复2、3步,知道计算完所有字节

与crc8一样,可以记录一个查找表,记录所有[0x00, 0xFF]对poly取余的结果;唯一的区别是,取余时使用的是crc的高8位数据,即crc右移8位数据进行查表。
以及crc迭代结果时是crc >> 8 ^ table[pos];右移8是因为我们是处理的8个字节,而高8位数据以及通过查表的方式对多项式取余了,所以只剩下后边的数据需要进行处理。

// generate crc_16 lookup table
public static void CalculateTable_CRC16()
{
    const ushort generator = 0x1021;
    crctable16 = new ushort[256];

    for (int divident = 0; divident < 256; divident++) /* iterate over all possible input byte values 0 - 255 */
    {
        ushort curByte = (ushort(divident << 8); /* move divident byte into MSB of 16Bit CRC */

        for (byte bit = 0; bit < 8; bit++)
        {
            if ((curByte & 0x8000) != 0)
            {
                curByte <<= 1;
                curByte ^= generator;
            }
            else
            {
                curByte <<= 1;
            }
        }

        crctable16[divident] = curByte;
    }
}

// calc crc_16
public static ushort Compute_CRC16(byte[] bytes)
{
    ushort crc = 0;
    foreach (byte b in bytes)
    {
        /* XOR-in next input byte into MSB of crc, that's our new intermediate divident */
        byte pos = (byte)( (crc >> 8) ^ b); /* equal: ((crc ^ (b << 8)) >> 8) */
        /* Shift out the MSB used for division per lookuptable and XOR with the remainder */
        crc = (ushort)((crc << 8) ^ (ushort)(crctable16[pos]));
    }

    return crc;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/654041.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

项目管理中,如何实现有效的项目预算管理?

在《PMBOK指南》第七版中&#xff0c;变化较大的一点是从以成果为导向演变为以价值为导向&#xff0c;其十二项指导原则之一——“聚焦于价值”也阐述了价值是项目的最终成功指标和驱动因素。在这一指导原则下&#xff0c;项目经理就不能只关注在范围、进度、成本三重要素约束下…

UmiJs - 拆包优化

UmiJs - 拆包优化 前言一. 如何拆包&#xff0c;怎么拆1.1 分析自己项目的编译产物结构1.2 开始拆包 二. 有哪些注意点2.1 样式丢失2.2 存在需单独打包的页面 前言 我们在写前端代码的时候&#xff0c;难以避免的是&#xff0c;我们可能引入的依赖越来越多。那么随之而来的&am…

Redis入门 - Lua脚本

原文首更地址&#xff0c;阅读效果更佳&#xff01; Redis入门 - Lua脚本 | CoderMast编程桅杆https://www.codermast.com/database/redis/redis-scription.html Redis 脚本使用 Lua 解释器来执行脚本。 Redis 2.6 版本通过内嵌支持 Lua 环境。执行脚本的常用命令为 EVAL。 …

【Golang系列】Golang环境配置和第一个Go程序

⭐️前面的话⭐️ 本篇文章将介绍Golang语言的环境配置&#xff0c;以及如何在VS code中运行第一个golang程序。 &#x1f4d2;博客主页&#xff1a;未见花闻的博客主页 &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f4cc;本文由未…

Pytest教程__定制allure报告(12)

定制报告需要先导入allure模块&#xff0c;再使用以下装饰器方法&#xff1a; feature: 标注主要功能模块。story: 标注feature功能模块下的分支功能。description&#xff1a;在报告中显示用例描述。step: 标注测试用例步骤。issue && testcase&#xff1a;标注用例关…

单链表刷题(1-3)

目录 反转链表 移除元素 合并有序链表 反转链表 力扣 我们用取头节点依次进行头插的方式解决这道题。需要注意的是头插前要保存下一个节点。 struct ListNode* reverseList(struct ListNode* head){typedef struct ListNode SL;SL* cur head;SL* rhead NULL;//初始指向空…

TienChin 代码格式化-项目结构大改造

代码格式化 博主下载项目之后发现&#xff0c;整体的代码格式化风格&#xff0c;与 C 那种语言很相似&#xff0c;说明这个作者之前就是从事这块的导致风格有点类似&#xff0c;我们来格式化一下&#xff0c;当然这不是必要的&#xff0c;我是没习惯这种写法所以这里我写一下我…

2023年测试岗,你真的懂测试吗?凭什么他能月薪25k+

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 测试人员应该居安…

Redis入门 - 事务

原文首更地址&#xff0c;阅读效果更佳&#xff01; Redis入门 - 事务 | CoderMast编程桅杆https://www.codermast.com/database/redis/redis-transaction.html Redis 事务可以一次执行多个命令&#xff0c; 并且带有以下三个重要的保证&#xff1a; 批量操作在发送 EXEC 命…

STM32串口通信详解(嵌入式学习)

STM32串口通信 1.通信基础知识1.1 时钟信号区分同步通信异步通信波特率总线协议(电气协议) 1.2 通信方式划分串行通信并行通信 1.3 通信方向划分单工通信半双工通信全双工通信常见通信总结 2. USARTUSART 介绍 3. 串口通信协议4. 相关寄存器串口控制寄存器波特率寄存器中断和状…

segment anything环境配置与使用测试

硬件&#xff1a;RTX3070 i9-11900H 内存16G 目录 一、环境配置 二、使用测试--predictor_example.ipynb 1.jupyter notebook准备操作 2.Object masks from prompts with SAM与Environment Set-up 3.Set-up 4.Example image 5.Selecting objects with SAM 6.Specifyin…

GeoServer安装部署

GeoServer是一款开源的GIS服务器,用于管理、共享和编辑空间数据。 它的主要功能包括: 管理空间数据&#xff1a;GeoServer可以连接各种空间数据源,包括文件(SHP、CSV等)、数据库(PostGIS,Oracle,SQL Server等)和云存储(S3,Swift,Azure等)。并提供数据的浏览、上传、下载和删除…

webgpu之旅04

继续继续 319854902 319854902 319854902 319854902 webgpu交Q流群首先准备好绘制到屏幕所需的这个descriptor if rendertarget this._textures.initRenderTarget( renderTarget ); 来看一下这个函数里面会做什么 renderTargetProperties是这个target的properties 创建一个co…

历时一个月,腾讯认证python全套项目实战笔记,终于整理出来了

前言 之前拿到一份关于腾讯认证的python的全套项目实战脑图&#xff0c;于是历时花费一个月&#xff0c;终于是熬夜加点的给肝出来了&#xff0c;先用typora全部写出来&#xff0c;然后再导出成PDF文件&#xff0c;目前已经完全搞定。 总共划分内容为&#xff08;七大模块&am…

Telnet协议详解

Telnet协议是一种远程登录协议&#xff0c;它允许用户通过网络连接到远程主机并在远程主机上执行命令。本文将对Telnet协议进行详细介绍&#xff0c;包括其基本概念、连接方式、C/S模式以及工作原理。 一、Telnet协议的基本概念 1. NVT&#xff08;Network Virtual Terminal&a…

通付盾荣获第六届(2023)数字金融创新大赛“创新先锋榜”!

今日&#xff0c;第六届&#xff08;2023&#xff09;数字金融创新大赛“创新先锋榜”揭晓&#xff0c;大赛由中国电子银行网、数字金融联合宣传年主办&#xff0c;自4月6日开启以来&#xff0c;得到数字金融行业各方的积极响应与支持。经过专家评分、路演评审等环节&#xff0…

Android中Activity、View和Window关系详解

Android系统启动篇 1&#xff0c;《android系统启动流程简介》 2&#xff0c;《android init进程启动流程》 3&#xff0c;《android zygote进程启动流程》 4&#xff0c;《Android SystemServer进程启动流程》 5&#xff0c;《android launcher启动流程》 6&#xff0c;…

8年测试总结,App测试要点常见bug分类,从功能到性能测试...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 而针对手机应用软…

数字IC设计怎么入门?(附学习全流程)

看到很多小伙伴都不了解数字IC设计该怎么学&#xff0c;下面就来给大家来具体讲讲。 其实对于初级数字 IC 设计工程师而言&#xff0c;不仅仅需要较好的 Verilog 语法功底&#xff0c;还要熟悉企业的 Linux 环境以及 EDA 工具&#xff0c;此时你就需要掌握 Shell&#xff0c;V…

Django 权限管理和guardian插件

内置权限管理 Django内置的权限管理, 是一种表权限, 就是可分别配置某管理员用户对某个表的全部数据有没有增删改查4种权限. 图形界面配置权限 之前提到&#xff0c;使用命令行创建超管用户&#xff1a; python manage.py createsuperuser这其实是在最普通的用户的基础上将…