ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

MATLAB CRC校验仿真:CRC16参数配置与查表法实现

MATLAB CRC校验仿真:CRC16参数配置与查表法实现 简介面向通信与数据存储场景的CRC校验Matlab仿真资源包适合正在学习差错控制编码、或需要在课程设计中实现CRC-16校验的读者也可作为通信系统可靠性验证的参考模板。资源共4个M文件包含CRC编码主程序、循环冗余解码及校验函数压缩包整体仅2KB代码紧凑、依赖少下载后即可在Matlab中直接运行便于逐行阅读与二次修改适合入门者快速复现实验。已有763人学习下载。示例基于CRC-16生成多项式展开完整覆盖初始值设定、寄存器移位、模2除法、校验码附加及差错判别流程并通过comm.CRCGenerator系统对象与step函数实现数据校验。代码中详细注释了关键步骤配合实际运行输出可帮助初学者完成从理论到仿真的过渡。读者不仅能理解CRC计算的每个环节还能获得可复用的M函数模板适用于课堂实验、毕业设计和工程预研快速迁移到自定义通信协议或文件完整性验证场景中。1. 在 MATLAB 中把 CRC 校验仿真做扎实先从一份乱码帧讲起通信仿真里最常见的“假成功”就是 checksum 对上了、数据却错了反过来 CRC 报错时又拿不准是多项式配错还是字节序反转。标题里的crc16校验看起来只是调一个函数真正落地时涉及生成多项式、初值、输入反转、输出异或、字节序这一串参数任何一个不匹配仿真结果到了 Verilog 或单片机里就是废的。这篇文章按我平时做通信链路仿真和文件校验的路径把 MATLAB 中 CRC 的仿真、参数设计、错误注入和逆向验证讲透。适合正在做链路层协议仿真、需要把 CRC 算法迁到硬件或需要给 CSV 文件做逐行校验的人读完能用 MATLAB 跑出和硬件一致的结果也能反过来用已知数据帧验证你的仿真模型对不对。2. 用 MATLAB 通信工具箱做 crc 仿真对象式接口和参数语义2.1 为什么不用手写多项式除法先看 comm.CRCGeneratorMATLAB 的 Communications Toolbox 提供了comm.CRCGenerator和comm.CRCDetector这一对对象直接对应发送端追加 CRC 校验码、接收端校验并输出错误标志的完整链路。很多初学者一上来就搜“crc校验 matlab 源码”其实工具箱里的对象已经覆盖了绝大多数标准 CRC包括 CRC-16/CCITT、CRC-32 等而且参数可以直接以属性名配置代码可读性比手写移位寄存器强得多。一个最简的生成端代码如下。% 构建 CRC 生成器CRC-16/CCITT多项式 x^16 x^12 x^5 1 gen comm.CRCGenerator(... Polynomial, [1 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 1], ... InitialConditions, 0, ... DirectMethod, true); % 输入原始数据列向量每个元素是一个 bit0 或 1 data randi([0 1], 64, 1); codeword gen(data); % 输出的前 64 个是原数据后 16 个是 CRC 校验位 disp(size(codeword));Polynomial是从最高位到最低位的系数向量必须包含 x^0 的系数 1长度等于 CRC 位数加一。InitialConditions决定移位寄存器初值CRC-16/CCITT 标准里一般是 0xFFFF也就是全 1但仿真时很多同学直接填 0结果和标准计算结果对不上。DirectMethod选择直接法还是非直接法直接法不需要在数据末尾补零计算逻辑更贴近硬件实现仿真时优先选 true。配套检测端代码同样简洁。dec comm.CRCDetector(gen); % 直接传入生成器对象 [detected, err] dec(codeword); % err 0 表示校验通过detected 是去掉了 CRC 的原始数据注意comm.CRCDetector可以复用comm.CRCGenerator的配置这一点在实际工程里很省事但要求两端参数完全一致尤其是InitialConditions和FinalXOR。2.2 参数不匹配时的 5 个典型失败症状如果检测端报错但发送端确信自己算对了先别怀疑随机信道噪声按下面的清单排查这几乎是 MATLAB 仿真里最常见的问题。InitialConditions不同是最大的坑。发送端用 0xFFFF接收端用 0结果所有帧全错。如果通信双方都按同一个标准文档实现但文档里写的是“preset to 0xFFFF”就要在 MATLAB 里写成十进制 65535注意不是 65536。FinalXOR被忽略同样隐蔽。CRC-32 标准要求输出异或 0xFFFFFFFF很多人在 MATLAB 里只配了多项式忘了配FinalXOR导致仿真自测时生成端和检测端都错得一致因为检测端也继承了这个错误的输出链路内部居然能自洽一旦和真实抓包数据对比就露馅。InputOrder和OutputOrder涉及字节序。MATLAB 里默认输入是按列向量逐 bit 进入移位寄存器如果你的数据来源是 uint8 数组要先用de2bi转成比特列向量并且确认最低有效位在前还是最高有效位在前这个顺序和 CRC 算法里的refin输入反转是等价关系。ChecksumsPerFrame按帧生成 CRC 时要确保 MATLAB 的帧长和通信协议里定义的帧长一致。比如一条 5G NR 的传输块分成了几个码块每个码块独立校验还是整块统一校验直接决定这个参数的值。DirectMethod混合使用会导致多项式除法中间结果差一步。同一套多项式直接法算出来的 CRC 和非直接法看起来不一样但实际上都满足校验方程。问题只出现在一端直接法、另一端非直接法的混搭情况下。所以仿真链路里两端的DirectMethod必须一致。2.3 用仿真波形确认 cms 校验的时序关系comm.CRCGenerator输出的是串行比特流数据和校验位是拼接关系这对理解硬件实现很有帮助。数据长度是 KCRC 长度是 M输出长度就是 KM。做通信链路仿真时通常的做法是先把编码后的码块传给 CRC 生成器再加扰、调制、过信道接收端先解调、解扰再送入comm.CRCDetector检测。一个稍有工程感的例子是模拟一个误码点。% 在 codeword 第 30 个 bit 上翻转模拟信道误码 codeword_err codeword; codeword_err(30) ~codeword_err(30); % 检测错误 [~, err] dec(codeword_err); fprintf(误码后 CRC 检测结果: %d\n, err); % 输出 1 表示检测到错误这里可以看到CRC 并不纠正错误它只能告诉你这一帧有没有问题纠错要靠 FEC 或者重传机制。很多刚接触 crc 仿真的人会期望 CRC 能定位到具体哪个比特错了这是把 CRC 和海明校验搞混了。海明校验编码解码实验里我们会关心错误位置但 CRC 的目标是尽可能高的漏检率性能不是定位。3. 手工实现 m 代码版 CRC16 校验查表法和位运算是可直接迁移的基础3.1 从多项式除法到位运算这里把公式说清楚工具箱对象虽然好用但有两个场景必须自己写一是没有通信工具箱许可的 MATLAB 环境二是需要把 CRC Verilog 生成器或 C 代码参考模型时需要一份纯算法逻辑去对照。手工实现 CRC16 校验的核心思想是模 2 多项式除法即二进制加法不进位异或就是加减法。一个基于位运算的完整函数如下。function crc crc16_bitwise(data, poly, init, refin, refout, xorout) % data: uint8 数组 % poly: CRC 多项式的十六进制表示比如 0x8005 % init: 初值寄存器 crc init; for i 1:length(data) byte data(i); if refin byte flip_bits(byte, 8); % 输入反转 end crc bitxor(crc, bitshift(byte, 8)); for j 1:8 if bitand(crc, 0x8000) crc bitxor(bitshift(crc, 1), poly); else crc bitshift(crc, 1); end crc bitand(crc, 0xFFFF); end end if refout crc flip_bits(crc, 16); end crc bitxor(crc, xorout); end function out flip_bits(in, width) out 0; for k 1:width out bitor(out, bitshift(bitand(in, 1), width - k)); in bitshift(in, -1); end end这段代码的逻辑是每输入一个字节先按refin决定是否翻转位序然后异或进 CRC 寄存器的高 8 位再逐 bit 做多项式长除。最高位是 1 就左移后异或多项式最高位是 0 就单纯左移。最后根据refout决定是否对 16 位结果整体反转再和xorout异或。bitand(crc, 0xFFFF)保证寄存器始终是 16 位避免 MATLAB 默认的整数类型膨胀。对照真实标准CRC-16/MODBUS 的配置是poly0x8005, init0xFFFF, refintrue, refouttrue, xorout0x0000而 CRC-16/CCITT-FALSE 的配置是poly0x1021, init0xFFFF, refinfalse, refoutfalse, xorout0x0000。这两个标准不要混尤其是 MODBUS 的输入输出反转很多人写成refinfalse导致整条链路算出来的值都不对。3.2 查表法加速仿真里跑大文件才不卡位运算法每输入一个 bit 都要做一次条件判断和移位在 MATLAB 这种解释型环境里仿真一个大文件比如几十 MB 的固件效率很低。工程上更常见的是查表法提前计算 256 个表项每个字节输入时只需要查表加三次异或。function crc crc16_table(data, poly, init, refin, refout, xorout) tbl zeros(1, 256); for i 0:255 crc i; for j 1:8 if bitand(crc, 1) crc bitxor(bitshift(crc, -1), poly); else crc bitshift(crc, -1); end end tbl(i 1) bitand(crc, 0xFFFF); end crc init; for i 1:length(data) if refin idx bitxor(bitand(crc, 0xFF), data(i)) 1; crc bitxor(bitshift(crc, -8), tbl(idx)); else idx bitxor(bitshift(crc, -8), data(i)) 1; crc bitxor(bitshift(bitand(crc, 0xFF), 8), tbl(idx)); end crc bitand(crc, 0xFFFF); end if refout crc flip_bits(crc, 16); end crc bitxor(crc, xorout); end这里表的生成逻辑和位运算法内层循环一一对应但主循环里每字节只查一次表循环次数从 8 倍字节数降为字节数本身。查表法在refintrue和refinfalse两种模式下的查表索引计算方式不同前者直接用 CRC 低字节异或输入后者用 CRC 高字节异或输入很多人抄代码时漏掉了这个细节。3.3 用已知向量验证你的 crc 算法没写错验证 CRC 实现最直接的方法是拿标准测试向量跑一遍。CRC-16/CCITT-FALSE 标准里输入123456789这 9 个 ASCII 字符输出必须是0x29B1。CRC-16/MODBUS 同样输入输出是0x4B37。data uint8(123456789); crc crc16_bitwise(data, 0x8005, 0xFFFF, true, true, 0x0000); fprintf(0x%04X\n, crc); crc crc16_bitwise(data, 0x1021, 0xFFFF, false, false, 0x0000); fprintf(0x%04X\n, crc);如果输出和标准不一致优先检查refin和refout的逻辑。refintrue时输入字节要先按位倒序refouttrue时输出要倒序这两个操作在位运算法里是两段独立的代码很容易只写一个。4. 按场景定 CRC 参数从 Modbus 到 CSV 文件校验的仿真实践4.1 常见标准 CRC 的完整参数表仿真和硬件对照用CRC 算法本身并不规定用哪个多项式不同行业、不同协议各自选型MATLAB 仿真时按协议文档填入参数即可。下表按我平时用的频率整理了一份可直接对照的参数集。算法名称多项式初值输入反转输出反转输出异或常见场景CRC-16/MODBUS0x80050xFFFFtruetrue0x0000Modbus 串口总线工控设备CRC-16/CCITT-FALSE0x10210xFFFFfalsefalse0x0000X.25、蓝牙、PPPCRC-16/XMODEM0x10210x0000falsefalse0x0000XMODEM 文件传输协议CRC-320x04C11DB70xFFFFFFFFtruetrue0xFFFFFFFFZIP、GZIP、PNG、以太网帧CRC-8/MAXIM0x310x00truetrue0x00单总线传感器DS18B20 温度传感器注意 CRC-32 的多项式在 MATLAB 里写的时候要按最高位到最低位的系数向量表示即[1 0 0 0 0 0 1 0 0 1 1 0 0 0 0 0 0 1 0 0 0 1 1 1 0 1 1 0 1 1 0 1]它是 33 位不要和 16 位 CRC 混淆。crc32 校验在 MATLAB 里也可以直接用comm.CRCGenerator(Polynomial, z^32 z^26 z^23 z^22 z^16 z^12 z^11 z^10 z^8 z^7 z^5 z^4 z^2 z 1)这种字符串形式工具箱接受多项式字符串。4.2 仿真一个 Modbus 帧的 crc 校验码计算过程Modbus RTU 是工业现场最常见的协议之一它的 CRC 计算是 16 位多项式 0x8005输入输出都反转初值 0xFFFF计算结果先传低字节再传高字节。在 MATLAB 里仿真一个完整请求帧可以这么写。% Modbus RTU 请求帧从站地址 0x01功能码 0x03起始地址 0x0000寄存器数量 0x0002 pdu uint8([0x01 0x03 0x00 0x00 0x00 0x02]); crc crc16_table(pdu, 0x8005, 0xFFFF, true, true, 0x0000); % 低字节在前高字节在后拼成完整帧 low_byte bitand(crc, 0xFF); high_byte bitshift(crc, -8); frame [pdu low_byte high_byte]; fprintf(带 CRC 的完整 Modbus 帧: ); fprintf(%02X , frame); fprintf(\n);这个例子可以直接对照 Modbus 协议规范里的示例手算能验证也可以用第三方 CRC 计算工具验证。多提一点Modbus 的 CRC 字节序是低字节先发和 XMODEM 的高字节先发不同做串口抓包对比时经常看到有人把这两个顺序搞反。4.3 给 CSV 文件做逐行 md5 校验和 crc 校验的对比实现标题里的“校验”如果落到文件场景很多人的第一反应是 md5。md5 是哈希函数和 CRC 有本质区别md5 的雪崩效应使得任意一位变化输出结果面目全非而 CRC 的线性性质会导致某些特定错误模式漏检。文件校验场景里CSV 文件逐行做 CRC 校验的好处是速度快能定位到是哪一行坏了缺点是抗恶意篡改能力弱。下面是 MATLAB 里逐行计算 CRC16 的完整代码。% 读入 CSV 文件为文本行数组 fid fopen(data.csv, r); lines {}; while ~feof(fid) lines{end1} fgetl(fid); %#okSAGROW end fclose(fid); % 逐行计算 CRC16结果写入新列 crcs zeros(length(lines), 1); for i 1:length(lines) line_bytes uint8(lines{i}); crcs(i) crc16_table(line_bytes, 0x8005, 0xFFFF, true, true, 0x0000); end % 把结果拼回去方便人为检查 out_lines cell(length(lines), 1); for i 1:length(lines) out_lines{i} sprintf(%s,0x%04X, lines{i}, crcs(i)); end这里fgetl读出来的字符串是不含换行符的这在实际工程里是一个细节。如果你要校验的 CSV 文件是由 Windows 生成的行尾是\r\n用fgetl会把\r也去掉但不同系统上fgetl处理方式略有差异跨平台仿真时要注意这个差异是否影响了 CRC 结果。4.4 错误模型仿真内向测试漏检率而不是只测一次对不对很多人在 crc 仿真里只测试一个误码比特看一下 CRC 能不能检测出来这对教学可以但对工程不够。通信工程师更关心的是漏检率即在大量随机错误模式下CRC 没能察觉的比例。MATLAB 里可以快速做蒙特卡洛仿真。rng(42); total_frames 10000; detected_errors 0; hidden_errors 0; for i 1:total_frames data randi([0 1], 64, 1); codeword gen(data); % 随机翻转 1 到 8 个比特 num_flips randi(8); pos randperm(length(codeword), num_flips); corrupted codeword; corrupted(pos) 1 - corrupted(pos); [~, err] dec(corrupted); if err detected_errors detected_errors 1; else hidden_errors hidden_errors 1; end end fprintf(漏检帧数: %d / %d\n, hidden_errors, total_frames);注意这里对我们前面给的comm.CRCDetector来说误码后的数据随机翻转检测不到的情况理论上在 2^-16 量级也就是大约万分之一点五。但实际仿真中不同的多项式、初值、错误位置分布会导致漏检率波动这个统计数字能帮你判断所选 CRC 配置是否匹配链路的误码率要求。5. 用 CRC 逆向思路验证仿真已知帧反推多项式与参数5.1 逆向实验的价值CRC 仿真做得差不多时会碰到一个尴尬场景手头有一段抓到的真实通信帧数据部分已知、校验码部分已知但不知道协议标准里用的是哪个多项式MATLAB 仿真不知道该填什么参数。这时不需要猜可以直接用 MATLAB 做 CRC 逆向从已知帧反推出多项式和参数配置。更常见的场景是给 verilog 生成器做验证先用 MATLAB 参考模型算出一个 CRC再用生成的 Verilog 代码在仿真器里跑同一个数据对比结果。如果不一样需要快速判断是参数配置问题还是 Verilog 代码问题。5.2 从已知数据帧反推 CRC 多项式参数反推的原理基于 CRC 计算对输入数据 XOR 的线性性质。给定一组数据帧和对应的 CRC 校验值可以构造线性方程组求解多项式的待定系数。这里给一个适用于教学验证的简化版本假设 CRC-16多项式位数固定init0xFFFFrefin/refoutfalse此时每个数据帧都能建立一个关于多项式系数的异或方程。function recovered_poly crc_reverse_polynomial(frames, crcs) % frames: cell array of uint8 vectors % crcs: uint16 array of corresponding CRC values num_eqs length(frames); % 线性方程组矩阵16 个未知数对应 CRC 多项式中间项 A zeros(num_eqs, 16); b zeros(num_eqs, 1); for i 1:num_eqs % 以 init0xFFFF 计算无多项式时的中间值 % 对任意多项式 P满足 linearity: crc base_crc xor (frame * poly) base crc_partial(frames{i}, 0x0000, 0xFFFF); A(i, :) bitget(bitxor(base, crcs(i)), 16:-1:1); b(i) 1; end % 解 GF(2) 线性方程组使用高斯消元 sol gf2_rref([A b]); recovered_poly 0; for k 1:16 if sol(k, end) 1 recovered_poly bitor(recovered_poly, bitshift(1, k - 1)); end end % 加上固定首位和末位 recovered_poly bitor(recovered_poly, 0x8000); end function crc crc_partial(data, poly, init) crc init; for i 1:length(data) crc bitxor(crc, bitshift(uint16(data(i)), 8)); for j 1:8 if bitand(crc, 0x8000) crc bitxor(bitshift(crc, 1), poly); else crc bitshift(crc, 1); end end end end这里省略了伽罗华域高斯消元的完整实现gf2_rref可以用 MATLAB 的gf对象做矩阵运算也可以手写异或消元。这种反推方法的前提是已知 init 和其他参数实际工程里通常一次先固定 init 和反转位反推多项式再反向验证逐项放开。常见的 Crc 校验码计算错误都是多项式反推时把 0x8005 和 0xA001 搞混这两个多项式互为反转关系0xA001 就是把 0x8005 按位倒过来对应 refintrue 和 refinfalse 之间的等价变换。5.3 反推参数后的交叉验证步骤保证仿真与硬件一致逆向得到多项式后用一组全新的数据帧去验证不能用参与反推的那组数据否则只能证明方程组被正确求解了。标准步骤是先准备 10 组抓包帧用其中 5 组反推剩下 5 组做正向预测对比。正向预测的 MATLAB 代码就是前面写的crc16_bitwise把反推出来的多项式填进去即可。如果反推出来的多项式和已知标准都不匹配检查init是否为 0x0000 而不是 0xFFFF。某些协议文档把“初值全零”和“初值全一”写得含糊导致反推方程解出的多项式是对的但代入init0xFFFF后正向计算失败。此时把 init 改成 0x0000 重新反推一遍多数情况就能对上。还有一种特殊情况需要考虑帧里包含的 CRC 部分可能不是按顺序追加的而是放在了帧头或者分成了两个字节插入到不同位置。逆向之前先确认帧结构把 CRC 字节从数据字节中完整剥离否则反推方程组会被污染。抓包工具导出的 hex 字符串通常是最容易直接处理的格式MATLAB 里用sscanf或hex2dec逐字节解析即可。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进