CRC校验原理及其C语言实现详解

目录
  • 前言
  • CRC算法简介
  • CRC参数模型
  • CRC计算
  • CRC校验
  • CRC计算的C语言实现
  • CRC计算工具
  • 总结

前言

最近的工作中,要实现对通信数据的CRC计算,所以花了两天的时间好好研究了一下,周末有时间整理了一下笔记。

一个完整的数据帧通常由以下部分构成:

校验位是为了保证数据在传输过程中的完整性,采用一种指定的算法对原始数据进行计算,得出的一个校验值。接收方接收到数据时,采用同样的校验算法对原始数据进行计算,如果计算结果和接收到的校验值一致,说明数据校验正确,这一帧数据可以使用,如果不一致,说明传输过程中出现了差错,这一帧数据丢弃,请求重发。

常用的校验算法有奇偶校验、校验和、CRC,还有LRC、BCC等不常用的校验算法。

以串口通讯中的奇校验为例,如果数据中1的个数为奇数,则奇校验位0,否则为1。

例如原始数据为:0001 0011,数据中1的个数(或各位相加)为3,所以奇校验位为0。这种校验方法很简单,但这种校验方法有很大的误码率。假设由于传输过程中的干扰,接收端接收到的数据是0010 0011,通过奇校验运算,得到奇校验位的值为0,虽然校验通过,但是数据已经发生了错误。

校验和同理也会有类似的错误:

一个好的校验校验方法,配合数字信号编码方式,如(差分)曼彻斯特编码,(不)归零码等对数据进行编码,可大大提高通信的健壮性和稳定性。例如以太网中使用的是CRC-32校验,曼彻斯特编码方式。本篇文章介绍CRC校验的原理和实现方法。

CRC算法简介

循环冗余校验(Cyclic Redundancy Check, CRC)是一种根据网络数据包或计算机文件等数据产生简短固定位数校验码的一种信道编码技术,主要用来检测或校验数据传输或者保存后可能出现的错误。它是利用除法及余数的原理来作错误侦测的。

CRC校验计算速度快,检错能力强,易于用编码器等硬件电路实现。从检错的正确率与速度、成本等方面,都比奇偶校验等校验方式具有优势。因而,CRC 成为计算机信息通信领域最为普遍的校验方式。常见应用有以太网/USB通信,压缩解压,视频编码,图像存储,磁盘读写等。

CRC参数模型

不知道你是否遇到过这种情况,同样的CRC多项式,调用不同的CRC计算函数,得到的结果却不一样,而且和手算的结果也不一样,这就涉及到CRC的参数模型了。计算一个正确的CRC值,需要知道CRC的参数模型。

一个完整的CRC参数模型应该包含以下信息:WIDTH,POLY,INIT,REFIN,REFOUT,XOROUT。

NAME:参数模型名称。

WIDTH:宽度,即生成的CRC数据位宽,如CRC-8,生成的CRC为8位

POLY:十六进制多项式,省略最高位1,如 x8 + x2 + x + 1,二进制为1 0000 0111,省略最高位1,转换为十六进制为0x07。

INIT:CRC初始值,和WIDTH位宽一致。

REFIN:true或false,在进行计算之前,原始数据是否翻转,如原始数据:0x34 = 0011 0100,如果REFIN为true,进行翻转之后为0010 1100 = 0x2c

REFOUT:true或false,运算完成之后,得到的CRC值是否进行翻转,如计算得到的CRC值:0x97 = 1001 0111,如果REFOUT为true,进行翻转之后为11101001 = 0xE9。

XOROUT:计算结果与此参数进行异或运算后得到最终的CRC值,和WIDTH位宽一致。

通常如果只给了一个多项式,其他的没有说明则:INIT=0x00,REFIN=false,REFOUT=false,XOROUT=0x00。

常用的21个标准CRC参数模型:

CRC校验在电子通信领域非常常用,可以说有通信存在的地方,就有CRC校验:

  • 美信(MAXIM)的芯片DS2401/DS18B20,都是使用的CRC-8/MAXIM模型
  • SD卡或MMC使用的是CRC-7/MMC模型
  • Modbus通信使用的是CRC-16/MODBUS参数模型
  • USB协议中使用的CRC-5/USB和CRC-16/USB模型
  • STM32自带的硬件CRC计算模块使用的是CRC-32模型

至于多项式的选择,初始值和异或值的选择,输入输出是否翻转,这就涉及到一定的编码和数学知识了。感兴趣的朋友,可以了解一下每个CRC模型各个参数的来源。至于每种参数模型的检错能力、重复率,需要专业的数学计算了,不在本文讨论的范畴内。

CRC计算

好了,了解了CRC参数模型知识,下面手算一个CRC值,来了解CRC计算的原理。

问:原始数据:0x34,使用CRC-8/MAXIN参数模型,求CRC值?

答:根据CRC参数模型表,得到CRC-8/MAXIN的参数如下:

POLY = 0x31 = 0011 0001(最高位1已经省略)
INIT = 0x00
XOROUT = 0x00
REFIN = TRUE
REFOUT = TRUE

有了上面的参数,这样计算条件才算完整,下面来实际计算:

0.原始数据 = 0x34 = 0011 0100,多项式 = 0x31 = 1 0011 0001
1.INIT = 00,原始数据高8位和初始值进行异或运算保持不变。
2.REFIN为TRUE,需要先对原始数据进行翻转:0011 0100 > 0010 1100
3.原始数据左移8位,即后面补8个0:0010 1100 0000 0000
4.把处理之后的数据和多项式进行模2除法,求得余数:
原始数据:0010 1100 0000 0000 = 10 1100 0000 0000
多项式:1 0011 0001
模2除法取余数低8位:1111 1011
5.与XOROUT进行异或,1111 1011 xor 0000 0000 = 1111 1011 
6.因为REFOUT为TRUE,对结果进行翻转得到最终的CRC-8值:1101 1111 = 0xDF
7.数据+CRC:0011 0100 1101 1111 = 34DF,相当于原始数据左移8位+余数。

模2除法求余数:

验证手算结果:

可以看出是一致的,当你手算的结果和工具计算结果不一致时,可以看看INIT,XOROUT,REFINT,REFOUT这些参数是否一致,有1个参数不对,计算出的CRC结果都不一样。

CRC校验

上面通过笔算的方式,讲解了CRC计算的原理,下面来介绍一下如何进行校验。

按照上面CRC计算的结果,最终的数据帧:0011 0100 1101 1111 = 34DF,前8位0011 0100是原始数据,后8位1101 1111 是 CRC结果。

接收端的校验有两种方式,一种是和CRC计算一样,在本地把接收到的数据和CRC分离,然后在本地对数据进行CRC运算,得到的CRC值和接收到的CRC进行比较,如果一致,说明数据接收正确,如果不一致,说明数据有错误。

另一种方法是把整个数据帧进行CRC运算,因为是数据帧相当于把原始数据左移8位,然后加上余数,如果直接对整个数据帧进行CRC运算(除以多项式),那么余数应该为0,如果不为0说明数据出错。

而且,不同位出错,余数也不同,可以证明,余数与出错位数的对应关系只与CRC参数模型有关,而与原始数据无关。

CRC计算的C语言实现

无论是用C还是其他语言,实现方法网上很多,这里我找了一个基于C语言的CRC计算库,里面包含了常用的21个CRC参数模型计算函数,可以直接使用,只有crcLib.ccrcLib.h两个文件。

GitHub地址:https://github.com/whik/crc-lib-c

Gitee地址:https://gitee.com/whik/crc-lib-c

使用方法非常简单:

#include <stdio.h>
#include <stdlib.h>
#include "crcLib.h"

int main()
{
    uint8_t LENGTH = 10;
    uint8_t data[LENGTH];
    uint8_t crc;

    for(int i = 0; i < LENGTH; i++)
    {
        data[i] = i*5;
        printf("%02x ", data[i]);
    }
    printf("\n");

    crc = crc8_maxim(data, LENGTH);

    printf("CRC-8/MAXIM:%02x\n", crc);
    return 0;
}

计算结果:

CRC计算工具

下面这几款工具都可以自定义CRC算法模型,而且都有标准CRC模型可供选择。如果自己用C语言或者Verilog实现校验算法时,非常适合作为标准答案进行验证。

在线计算:www.ip33.com/crc.html

离线计算工具:CRC_Calc v0.1.exe或者GCRC.exe

格西CRC计算器:

总结

CRC校验并不能100%的检查出数据的错误,非常低的概率会出现CRC校验正确但数据中有错误位的情况。这和CRC的位数,多项式的选择等等有很大的关系,所以在实际使用中尽量选择标准CRC参数模型,这些多项式参数都是经过理论计算得出的,可以提高CRC的检错能力。CRC校验可以检错,也可以纠正单一比特的错误,你知道纠错的原理吗?

以上就是CRC校验原理及其C语言实现详解的详细内容,更多关于C语言 CRC校验的资料请关注我们其它相关文章!

(0)

相关推荐

  • 使用C语言实现CRC校验的方法

    CRC(Cyclic Redundancy Check)校验应用较为广泛,以前为了处理简单,在程序中大多数采用LRC(Longitudinal Redundancy Check)校验,LRC校验很好理解,编程实现简单.用了一天时间研究了CRC的C语言实现,理解和掌握了基本原理和C语言编程.结合自己的理解简单写下来. 1.CRC简介 CRC检验的基本思想是利用线性编码理论,在发送端根据要传送的k位二进制码序列,以一定的规则产生一个检验码r位(就是CRC码),附在信息后面,构成一个新的二进制码序列数

  • C++开发之CRC校验实例详解

    CRC:(循环冗余校验) 循环冗余校验是数据通信领域中最常用的一种差错校验码,主要用来检测或校验数据传输或者保存后可能出现的错误.其特征是信息字段和校验字段的长度可以任意选定. 工作原理: CRC检错方法的工作原理可以从发送端与接收端两个方面进行描述. 1)发送端将发送数据比特序列当作一个多项式f(x),用双方预先约定的生成多项式G(x)去除,求得一个余数多项式R(x).将余数多项式加到数据多项式之后,一起发送到接收端. 2)接收端用同样的生成多项式G(x)去除接收到的数据多项式f'(x),得到

  • C#实现的4种常用数据校验方法小结(CRC校验,LRC校验,BCC校验,累加和校验)

    CRC即循环冗余校验码(Cyclic Redundancy Check):是数据通信领域中最常用的一种查错校验码,其特征是信息字段和校验字段的长度可以任意选定.循环冗余检查(CRC)是一种数据传输检错功能,对数据进行多项式计算,并将得到的结果附在帧的后面,接收设备也执行类似的算法,以保证数据传输的正确性和完整性. CRC算法参数模型解释: NAME:参数模型名称. WIDTH:宽度,即CRC比特数. POLY:生成项的简写,以16进制表示.例如:CRC-32即是0x04C11DB7,忽略了最高位

  • C/C++实现crc码计算和校验

    目录 算法介绍 参数模型 代码计算 算法介绍 循环冗余校验(Cyclic Redundancy Check, CRC)是一种根据网络数据包或计算机文件等数据产生简短固定位数校验码的一种信道编码技术,主要用来检测或校验数据传输或者保存后可能出现的错误.它是利用除法及余数的原理来作错误侦测的. CRC校验计算速度快,检错能力强,易于用编码器等硬件电路实现.从检错的正确率与速度.成本等方面,都比奇偶校验等校验方式具有优势.因而,CRC 成为计算机信息通信领域最为普遍的校验方式.常见应用有以太网/USB

  • C++ 使用CRC32检测内存映像完整性的实现步骤

    仅对.text代码段进行校验: 通常程序中至少包括了代码段,数据段,而数据段中所存储的数据是经常会发生变动的,例如我们的全局变量,静态变量等都会默认存储在数据段,而代码段则不会发生变化,我们在检验时只需要注重.text内存段中的数据完整性即可,针对内存的校验同样可以抵御调试器的CC断点,该断点原理就是在下端处写入int3指令,同样可以检测得到. 校验思路如下 1.首先从内存得到PE的代码节的RVA和节大小 2.根据得到的RVA和节大小计算出crc32或是RC4值 3.读取自身保存的原始CRC32

  • CRC校验原理及其C语言实现详解

    目录 前言 CRC算法简介 CRC参数模型 CRC计算 CRC校验 CRC计算的C语言实现 CRC计算工具 总结 前言 最近的工作中,要实现对通信数据的CRC计算,所以花了两天的时间好好研究了一下,周末有时间整理了一下笔记. 一个完整的数据帧通常由以下部分构成: 校验位是为了保证数据在传输过程中的完整性,采用一种指定的算法对原始数据进行计算,得出的一个校验值.接收方接收到数据时,采用同样的校验算法对原始数据进行计算,如果计算结果和接收到的校验值一致,说明数据校验正确,这一帧数据可以使用,如果不一

  • Vue.js原理分析之nextTick实现详解

    前言 tips:第一次发技术文章,篇幅比较简短,主要采取文字和关键代码表现的形式,希望帮助到大家.(若有不正确还请多多指正) nextTick作用和用法 用法:nextTick接收一个回调函数作为参数,它的作用是将回调延迟到下一次DOM更新之后执行,如果没有提供回调函数参数且在支持Promise的环境中,nextTick将返回一个Promise. 适用场景:开发过程中,开发者需要在更新完数据之后,需要对新DOM做一些操作,其实我们当时无法对新DOM进行操作,因为这时候还没有重新渲染,这时候nex

  • C语言深入详解四大内存函数的使用

    目录 1.memcpy 2.memmove 3.memcmp 4.memset 1.memcpy 与字符串函数 strcpy 类似,也是进行拷贝.但是需要注意的是,strcpy 是针对字符串进行拷贝,而 memcpy 是针对内存进行拷贝. 如何理解呢?strcpy 进行拷贝的时候,只能一个字节一个字节的拷贝,但要实现 整型.浮点型等数据类型拷贝的时候,就不得不用到 memcpy 了. 我们观察 strcpy 的函数声明: char * strcpy ( char * destination, c

  • C语言指针详解及用法示例

    新手在C语言的学习过程中遇到的最头疼的知识点应该就是指针了,指针在C语言中有非常大的用处.下面我就带着问题来写下我对于指针的一些理解. 指针是什么? 指针本身是一个变量,它存储的是数据在内存中的地址而不是数据本身的值.它的定义如下: int a=10,*p; p=&a int a=10; int *p=&a; 首先我们可以理解 int* 这个是要定义一个指针p,然后因为这个指针存储的是地址所以要对a取地址(&)将值赋给指针p,也就是说这个指针p指向a. 很多新手都会对这两种定义方法

  • Python的语言类型(详解)

    Python 是强类型的动态脚本语言 . 强类型:不允许不同类型相加 动态:不使用显示数据类型声明,且确定一个变量的类型是在第一次给它赋值的时候 脚本语言:一般也是解释型语言,运行代码只需要一个解释器,不需要编译 强类型语言和弱类型语言 1.强类型语言:使之强制数据类型定义的语言.没有强制类型转化前,不允许两种不同类型的变量相互操作.强类型定义语言是类型安全的语言,如Java.C# 和 python,比如Java中"int i = 0.0;"是无法通过编译的: 2.弱类型语言:数据类型

  • jQuery原理系列-常用Dom操作详解

    1. 事件绑定$(el).bind ie使用attachEvent,其它浏览器使用addEventListener,不同的是ie多了个on前缀,this绑定在window上,需要用call和apply修正this 的指向. if (element.addEventListener) { element.addEventListener(type, handler, useCapture); } else { if (element.attachEvent) { element.attachEve

  • C 语言基础----详解C中的运算符

    C语言中又有哪些运算符呢? 如下所示: ※ 算术运算符 ※ 赋值运算符 ※ 关系运算符 ※ 逻辑运算符 ※ 三目运算符 C语言基本算术运算符如下表: 除法运算中注意: 如果相除的两个数都是整数的话,则结果也为整数,小数部分省略,如果两数中有一个为小数,结果则为小数. 取余运算中注意: 该运算只适合用两个整数进行取余运算 运算后的符号取决于被模数的符号,如(-10)%3 = -1;而10%(-3) = 1. 注:C语言中没有乘方这个运算符,也不能用×,÷等算术符号. 赋值运算符 下表列出了 C 语

  • vue中的v-model原理,与组件自定义v-model详解

    VUE中的v-model可以实现双向绑定,但是原理是什么呢?往下看看吧 根据官方文档的解释,v-model其实是一个语法糖,它会自动的在元素或者组件上面解析为 :value="" 和 @input="", 就像下面这样 // 标准写法 <input v-model="name"> // 等价于 <input :value="name" @input="name = $event.target.val

  • R语言函数详解及实例用法

    函数是一组组合在一起以执行特定任务的语句. R 语言具有大量内置函数,用户可以创建自己的函数. 在R语言中,函数是一个对象,因此R语言解释器能够将控制传递给函数,以及函数完成动作所需的参数. 该函数依次执行其任务并将控制返回到解释器以及可以存储在其他对象中的任何结果. 函数定义 使用关键字函数创建 R 语言的函数. R 语言的函数定义的基本语法如下 function_name <- function(arg_1, arg_2, ...) { Function body } 函数组件 函数的不同部

  • C语言指针详解

    前言:复杂类型说明     要了解指针,多多少少会出现一些比较复杂的类型,所以我先介绍一下如何完全理解一个复杂类型,要理解复杂类型其实很简单,一个类型里会出现很多运算符,他们也像普通的表达式一样,有优先级,其优先级和运算优先级一样,所以我总结了一下其原则:从变量名处起,根据运算符优先级结合,一步一步分析.下面让我们先从简单的类型开始慢慢分析吧: int p; //这是一个普通的整型变量   int *p; //首先从P 处开始,先与*结合,所以说明P 是一个指针,然后再与int 结合,说明指针所

随机推荐