简介:CRC循环冗余校验的MATLAB仿真程序面向通信工程、数据存储及嵌入式领域的初学者与科研人员,用于直观理解CRC编解码和差错检测原理。资源为RAR压缩包,共4个.m源文件,大小仅2KB,包括CRC主程序、编码函数、解码函数及校验函数,代码精简,便于逐行研读和修改实验。程序完整覆盖生成多项式定义、预处理移位、模2除法求余、校验码附加、接收端解码及误码检测流程,可在传输中模拟位翻转并验证CRC能否正确发现错误,也适合自行修改生成多项式与数据字段开展对比实验。已有940人学习本资源,适合正在学习数据链路层纠错编码或需要快速搭建CRC仿真实验的读者,通过运行调试可直观掌握校验码生成与验证的核心环节,为进一步设计可靠通信或存储方案打下基础。
1. 为什么数据传着传着就错了:CRC校验的数学背景
1.1 一个能立刻复现的动机:解压文件时的CRC错误
前阵子给机器装显卡驱动,安装包解压到一半,7-Zip直接弹了个“7-Zip CRC error”。我当时的第一个反应是下载包损坏,重新下载一遍就好。但这行提示背后,其实是CRC算法在帮系统拦截一批已经出错的数据。
CRC,全称Cyclic Redundancy Check,循环冗余校验。它做的事很朴素:在数据发送或者文件存储前,按照某条规则算出一个校验码附在后面;接收方拿到数据后,再按同一条规则重新算一遍,看两个校验码能不能对上。如果对不上,说明数据变了。注意,CRC是检错,不是纠错,它只能告诉你“出了问题”,不能告诉你“哪里出了问题”。
我这次在MATLAB里做CRC仿真,不只是为了写一个函数交差,而是想真正把几个问题搞清楚:CRC的数学本质到底是什么?不同生成多项式为什么检错能力不一样?MATLAB仿真程序怎么设计才能比较快地验证随机错误、突发错误下的检测率?这篇文章就围绕这套仿真程序展开,代码都能直接跑,适合在学通信原理、数字信号处理,或者想弄懂校验算法来龙去脉的读者。
1.2 从奇偶校验到模2除法:CRC的核心思想
先看最简单的奇偶校验。一个字节8个bit,数一下里面1的个数,如果是奇数就补一个1,偶数补0,接收方再数一遍。这个方法很便宜,但有个致命弱点:只要同时翻两个bit,比如00变成11,1的个数没变,校验就漏过去了。
CRC的思路比奇偶校验更进一步:它不是对全部bit做个简单统计,而是把整个数据看成一个二进制多项式。比如数据1011,对应多项式是:
$$M(x)=1\cdot x^3 + 0\cdot x^2 + 1\cdot x + 1$$
然后选定一个生成多项式 $G(x)$,CRC的核心就是计算:
$$R(x) = M(x)\cdot x^n \bmod G(x)$$
这里的$n$是生成多项式的次数,也就是CRC校验位的长度。所谓“模2除法”,和普通除法唯一的区别是每一步用异或代替减法,也就是同样位置都是1才消掉,不考虑进位。
举个例子,发送端有一个消息,想算3位CRC,生成多项式取$x^3 + x + 1$,对应bit是1011。消息先往左挪3位,后面补3个0,然后做模2除法,得到的余数就是CRC。发送的时候把余数拼在消息后面,接收端对整串数据再做一遍同样的模2除法,如果余数是0,则认为没有错误。用术语说,这叫“发送多项式能被生成多项式整除”。
这个思想可以继续延伸。CRC为什么能抓住很多奇偶校验抓不住的错误?因为只要错误模式对应的差错多项式不是生成多项式的倍式,就会被检出来。生成多项式选得好,常见错误模式基本都会被抓住。
1.3 生成多项式不是随便选的:参数背后的工程约定
不是随便哪个多项式都能当生成多项式,选错的话,CRC的检错能力会打折扣。工程上有几个常用多项式,参数表我整理在下面。
| CRC类型 | 生成多项式(十六进制) | 初始值 | Refin/Refout | XorOut | 常见场景 |
|---|---|---|---|---|---|
| CRC-8 | 0x07 | 0x00 | false/false | 0x00 | 嵌入式、传感器数据 |
| CRC-16/MODBUS | 0x8005 | 0xFFFF | true/true | 0x0000 | 工业总线、Modbus协议 |
| CRC-32 | 0x04C11DB7 | 0xFFFFFFFF | true/true | 0xFFFFFFFF | ZIP/Gzip、以太网、下载校验 |
这些参数里,我重点说三个:初始值、反射、输出异或。初始值不是总为0,比如CRC-32初始值是全1,是为了防止数据前面连续多个0被漏检。反射是指输入数据的bit顺序要不要倒着读,很多通信协议是LSB先发,所以算的时候要把bit倒过来。输出异或是最后再把结果异或一个常数,CRC-32是异或全1。很多人在两个工具里算同一个数据,结果不一致,绝大多数都是因为这三个参数有一项没对上。
2. MATLAB仿真程序的总体设计与按位实现
2.1 仿真架构:发送端、信道、接收端三段式
我做仿真习惯先画清楚链路,再写代码。CRC仿真不需要搞多复杂,但一定要把三段分清楚:发送端负责把消息加上CRC,构成完整帧;信道负责模拟各种错误,比如随机翻bit或者连续一段被干扰;接收端拿到有错误的帧,重新算CRC,判断是不是全0。三段式结构的好处是,后面想改成不同误码率测试,只动信道部分就行。
MATLAB里仿真程序的核心不是界面,也不是一键运行,而是两个函数:一个负责算CRC,一个负责模拟错误注入。算CRC的函数要写得通用,别把CRC-8写死,因为后面很可能需要换CRC-16、CRC-32来对比。错误注入函数则要支持单比特、双比特、突发错误三种模式,这样才能测试出CRC对不同错误类型的检测能力。
2.2 CRC编码核心函数:crc_bitwise的MATLAB代码
先给出最核心的按位CRC计算函数。这个函数用模2除法的寄存器思路写,逻辑清楚,代码也短。它接收三个参数:消息比特向量msg_bits、生成多项式完整位模式poly_bits、寄存器初始值init_bits。
function crc_out = crc_bitwise(msg_bits, poly_bits, init_bits) % msg_bits : 待计算CRC的信息位,0/1向量 % poly_bits : 生成多项式完整位模式,最高位必须是1 % init_bits : 寄存器初始值,长度等于多项式次数 n = length(poly_bits) - 1; reg = double(init_bits(:)'); msg = double(msg_bits(:)'); for i = 1:length(msg) fb = xor(reg(1), msg(i)); reg = [reg(2:end), 0]; if fb == 1 for j = 1:n reg(j) = xor(reg(j), poly_bits(j + 1)); end end end crc_out = reg; end为什么reg要左移一位,最低位填0,然后根据反馈位异或多项式?这就是LFSR线性反馈移位寄存器的标准做法。消息bit进来后,先和寄存器最高位做异或,得到反馈位fb。如果fb是1,说明当前多项式除法这一步需要减一次生成多项式;如果fb是0,只需要正常移位。整个过程模拟的就是模2除法。
比如要算CRC-8,生成多项式$x^8+x^2+x+1$对应的poly_bits是[1 0 0 0 0 0 1 1 1],因为它的系数从$x^8$到$x^0依次排列。这里最高位必须保留1,但在内部循环里不参与异或,所以poly_bits(j+1)`取的是后面8位,也就是去掉最高项后的低阶系数。
2.3 参数怎么传:initial、poly、reflection的统一处理
实际使用CRC时,不能只写一个固定函数,还要把反射和输出异或也处理掉。我写了一个统一入口函数,接收完整参数,这样仿真不同标准时不用改核心逻辑。
function crc_bits = crc_custom(msg_bits, poly_bits, init_bits, refin, refout, xorout_bits) msg = double(msg_bits(:)'); if refin msg = fliplr(msg); end reg = crc_bitwise(msg, poly_bits, init_bits); if refout reg = fliplr(reg); end crc_bits = xor(reg, xorout_bits); end这里refin为true时把输入bit翻转,refout为true时把输出bit翻转,最后再异或xorout_bits。以标准CRC-32为例,init_bits是32个1,refin和refout都是true,xorout_bits是32个1。注意,初始值全1时,无论是否反射,初始状态都一样,所以常见的CRC-32实现里
本文还有配套的精品资源,点击获取