ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

CRC校验算法详解:从原理到C语言/Python实战实现

CRC校验算法详解:从原理到C语言/Python实战实现 1. 项目概述从“校验和”到“循环冗余校验”在嵌入式开发、通信协议、文件校验乃至日常的数据传输中我们经常听到“校验”这个词。最简单的校验是“校验和”就是把所有数据字节加起来取个低8位或16位。但这种方式太容易被“蒙混过关”了比如你把一个字节从0x01改成0xFF再把另一个字节从0xFF改成0x01总和没变校验和也就发现不了错误。这对于要求可靠性的场景是致命的。于是更强大的循环冗余校验登场了也就是我们常说的CRC。我第一次接触CRC是在调试一个串口通信模块时设备总是间歇性收错数据用简单的累加和校验根本抓不到问题。后来协议改成CRC16问题立刻现形从此我对这个看似复杂的算法充满了敬意。CRC的本质是一种基于二进制多项式除法的校验算法它不仅能检测随机错误还能检测突发错误连续多位出错其检错能力远非简单求和可比。你可能听说过CRC8、CRC16、CRC32甚至CRC24它们名字里的数字代表最终生成的校验码长度单位是比特。CRC8生成一个字节的校验码常用于单字节命令校验CRC16生成两个字节在Modbus、XMODEM等经典协议中广泛应用CRC32生成四个字节因其极高的检错率被用于ZIP、RAR压缩包、以太网帧校验等领域CRC24则在一些特定通信标准如蓝牙基带、FlexRay中见到。这篇文章我将带你彻底搞懂CRC的原理抛开那些让人望而生畏的数学公式用图解和类比的方式让你直观理解。更重要的是我会用最贴近实战的C语言和Python代码手把手带你实现这几种常见的CRC算法。无论你是正在学习C语言的学生还是需要为产品添加可靠校验的嵌入式工程师或是用Python处理数据的开发者这篇文章都能给你提供可直接“抄作业”的解决方案。2. CRC校验的核心原理图解很多人一看到CRC的数学描述就头疼什么“生成多项式”、“模2除法”、“初始值”、“结果异或值”一堆术语砸过来直接劝退。别急我们换个方式理解。你可以把CRC计算想象成一个非常特殊的“流水线除法器”。2.1 核心比喻一个只会做减法的除法器我们平常做的除法比如13除以4商3余1。这个除法器会“比较”大小。但CRC的除法是“模2除法”它更简单它不比较大小它只做一件事对齐和异或。数据我们待校验的一串二进制数据比如11010011101100。除数一个预先选定的二进制数称为生成多项式。例如CRC-16-CCITT对应的多项式是0x1021写成二进制是1 0000 0010 0001最高位的1通常省略所以是17位但实际用16位表示。计算过程在数据的末尾补上若干个0补0的数量等于CRC校验码的位数CRC16就补16个0。这相当于先把数据“扩大”到包含校验位的位置。用这个补了0的“大数”除以我们的“生成多项式”。这个除法非常特殊每一步只看当前被除数最高位是不是1。如果是1就把生成多项式和它对齐然后进行异或操作相同为0不同为1。这步就相当于“减法”但因为没有借位所以就是异或。如果是0就整体右移一位。一直进行到最后剩下的“余数”就是CRC校验码。我画个简单的示意图以4位CRC为例 假设数据是1101生成多项式是10114位CRC实际有效位是3位最高位1省略写作011但计算时用1011。数据补01101000011010000开始“除法”1011 (生成多项式) ---- 1011)11010000 1011 (因为最高位是1对齐异或) ---- 0110000 1011 (最高位是0右移一位后新的最高位是1对齐异或) ---- 0011000 1011 (最高位是0右移... 过程略) ---- 余数 0011最终余数0011就是CRC校验码。关键理解这个“余数”的特性在于如果你把原始数据和这个余数拼接起来即1101001111010011再去除以同一个生成多项式那么新的余数一定是0。接收方就是利用这个特性来校验数据是否出错的。2.2 关键概念解析理解了基本流程我们再看看那些让人困惑的参数生成多项式这是CRC算法的“灵魂”决定了它的检错能力。不同标准对应不同的多项式。例如CRC-8: 常用0x07(x⁸ x² x 1)CRC-16-CCITT:0x1021(x¹⁶ x¹² x⁵ 1)CRC-16-MODBUS:0x8005(x¹⁶ x¹⁵ x² 1)CRC-32:0x04C11DB7(x³² x²⁶ ... 1)初始值在计算开始前CRC寄存器的初始值。有的标准是0有的是0xFFFF。设为全1有时能避免数据开头有0时的一些问题。输入/输出反转这是为了兼容不同硬件实现。有的协议规定数据字节要位反转LSB first后再送入计算计算结果也要反转后再输出。比如Modbus CRC16就是输入输出都反转。结果异或值计算完CRC后再与一个固定值异或。很多标准是0x0000但CRC-32/MPEG-2就是0xFFFFFFFF。这些参数组合起来就形成了一个具体的CRC算法标准。网上很多“在线CRC计算器”出错往往就是因为参数没设对。2.3 查表法效率飞跃的关键按照上面的“除法器”流程每个比特都要处理一次效率太低。于是有了查表法。它的思想是一个字节的数据8位经过CRC计算后会产生一个确定的结果。我们可以预先计算出所有256种可能一个字节有0-255共256个值对应的CRC结果做成一张表。这样计算长数据的CRC时我们不再逐比特处理而是逐字节处理取一个数据字节。将这个字节与当前CRC寄存器的高8位或低8位取决于实现进行某种组合通常是异或。用组合后的值作为索引去查表得到一个中间值。将这个中间值与CRC寄存器的剩余部分进行运算更新CRC寄存器。重复1-4直到所有数据处理完。查表法将计算量从 O(n*bits) 降低到 O(n)对于单片机等资源受限的嵌入式设备这是必用的优化手段。后文的C语言实现将重点展示查表法。3. 核心细节解析与参数选择在动手写代码之前我们必须把算法参数彻底搞清楚。参数选错计算结果就和标准对不上通信双方就会“鸡同鸭讲”。3.1 不同CRC标准的参数对照表下表整理了最常见的几种CRC算法的参数这是实现和调试的基石CRC标准多项式十六进制初始值输入反转输出反转结果异或值常见应用场景CRC-80x070x00NoNo0x001-Wire总线一些简单传感器CRC-8/ITU0x070x00YesYes0x55CRC-16/CCITT0x10210xFFFFNoNo0x0000X.25, XMODEM, Bluetooth HCICRC-16/CCITT-FALSE0x10210xFFFFYesYes0x0000很多库的默认CRC16CRC-16/MODBUS0x80050xFFFFYesYes0x0000Modbus RTU协议CRC-16/XMODEM0x10210x0000NoNo0x0000XMODEM协议CRC-320x04C11DB70xFFFFFFFFYesYes0xFFFFFFFFZIP, RAR, PNG, 以太网帧CRC-32/MPEG-20x04C11DB70xFFFFFFFFNoNo0x00000000SATA, MPEG-2 TSCRC-240x864CFB0xB704CENoNo0x000000FlexRay, 蓝牙基带实操心得在对接设备或解析文件时第一件事就是确认对方使用的CRC标准是表中的哪一种。最稳妥的方法是找官方协议文档。如果找不到可以尝试用已知的正确数据校验码通过工具反推参数。网上有一些“CRC逆向工具”就是干这个的。3.2 输入输出反转详解这是最容易出错的地方。所谓“反转”是指一个字节内的比特顺序。正常顺序最高位在前。例如字节0x01的二进制是0000 0001。反转后最低位在前。0x01反转后还是0000 0001因为只有最低位是1。但0x801000 0000反转后就变成了0000 0001。在代码中如何实现反转一个高效的方法是使用查找表但也可以位操作。一个经典的8位反转函数如下uint8_t reverse8(uint8_t x) { x ((x 0xF0) 4) | ((x 0x0F) 4); x ((x 0xCC) 2) | ((x 0x33) 2); x ((x 0xAA) 1) | ((x 0x55) 1); return x; }对于16位或32位反转思路类似只是操作的位数更多。在计算流程中输入反转意味着在数据字节参与CRC计算之前先调用reverse8()函数对它进行处理。输出反转意味着整个数据计算完成后对最终得到的CRC值16位或32位进行整体的位反转操作然后再进行“结果异或”。3.3 初始值与结果异或的意义初始值通常设置为非零值如0xFFFF是为了避免一个特殊情况——如果数据开头是一连串的0CRC寄存器初始为0会导致前几个字节的计算像没发生一样降低了检错能力。全1的初始值相当于在数据前虚拟了一个“ preamble ”。结果异或值这是一个后处理步骤。有些协议为了使得正常的CRC结果不会是全0全0在传输中可能容易被误判或者为了其他编码目的会要求最后异或一个固定值。0xFFFFFFFF在CRC32中很常见它实际上是对结果进行了按位取反。4. C语言实现查表法与位操作法C语言是嵌入式系统和性能敏感场景的首选。我们将实现查表法高效和位操作法直观并以CRC16-MODBUS和CRC32为例。4.1 CRC16-MODBUS 实现查表法Modbus协议在工业领域应用极广其CRC16要求输入输出反转。#include stdint.h #include stddef.h // CRC16-MODBUS 查表法 // 多项式0x8005 (x^16 x^15 x^2 1) // 初始值0xFFFF // 输入反转Yes // 输出反转Yes // 结果异或0x0000 // 预先生成的CRC16表256个条目 static const uint16_t crc16_table[256] { 0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241, 0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440, // ... 此处省略中间240个值实际代码需补全 0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40, 0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841 }; // 计算一段数据的CRC16-MODBUS值 uint16_t crc16_modbus(const uint8_t *data, size_t length) { uint16_t crc 0xFFFF; // 初始值 for (size_t i 0; i length; i) { // 1. 输入反转将数据字节与CRC低8位异或结果作为查表索引 uint8_t index (crc ^ data[i]) 0xFF; // 2. CRC右移8位然后与查表得到的值异或 crc (crc 8) ^ crc16_table[index]; } // 输出反转这里注意Modbus协议传输时是低字节在前 // 我们的crc变量是主机字节序需要先反转字节内比特然后以低字节序传输 // 以下操作等效于输出反转并转换为小端字节序 uint16_t result crc; result ((result 0xFF00) 8) | ((result 0x00FF) 8); // 字节交换 // 更精确的输出反转应逐位反转但Modbus通常直接交换字节即可因为其“输出反转”“小端传输”的效果就是字节交换。 // 严谨的实现应先位反转再交换字节这里为清晰起见采用常见写法 // result (reverse8(result 8) | (reverse8(result 0xFF) 8)); return result; } // 一个简单的8位反转函数用于生成表格或严谨的输出反转 uint8_t reverse8(uint8_t x) { x ((x 0xF0) 4) | ((x 0x0F) 4); x ((x 0xCC) 2) | ((x 0x33) 2); x ((x 0xAA) 1) | ((x 0x55) 1); return x; } // 生成CRC16表的函数仅需运行一次将结果固化到代码中 void generate_crc16_table() { uint16_t polynomial 0x8005; for (int i 0; i 256; i) { uint16_t crc i; for (int j 0; j 8; j) { if (crc 0x0001) { crc (crc 1) ^ polynomial; } else { crc 1; } } crc16_table[i] crc; } }如何使用uint8_t modbus_frame[] {0x01, 0x03, 0x00, 0x00, 0x00, 0x02}; size_t frame_len 6; uint16_t crc crc16_modbus(modbus_frame, frame_len); // crc 的低字节和高字节需要附加到帧的末尾且低字节在前 // 即frame[frame_len] crc 0xFF; // frame[frame_len 1] (crc 8) 0xFF;4.2 CRC32 实现查表法CRC32广泛应用于文件校验如ZIP。它的参数是多项式0x04C11DB7初始值0xFFFFFFFF输入输出反转结果异或0xFFFFFFFF。#include stdint.h #include stddef.h // CRC32 查表法 static uint32_t crc32_table[256]; // 初始化CRC32表只需调用一次 void init_crc32_table() { uint32_t polynomial 0x04C11DB7; for (uint32_t i 0; i 256; i) { uint32_t crc i 24; // 因为输入反转我们处理的是反转后的位所以左移 for (int j 0; j 8; j) { if (crc 0x80000000) { crc (crc 1) ^ polynomial; } else { crc 1; } } crc32_table[i] crc; } } // 计算CRC32值 uint32_t crc32_calculate(const uint8_t *data, size_t length) { uint32_t crc 0xFFFFFFFF; // 初始值 for (size_t i 0; i length; i) { // 输入反转通过查表法的索引计算隐含了反转操作 // 常见的实现是crc (crc 8) ^ crc32_table[(crc ^ data[i]) 0xFF]; // 但这是针对LSB-first的。对于MSB-first且输入反转需要调整。 // 以下是标准CRC32的经典查表法实现 uint8_t index (crc ^ data[i]) 0xFF; crc (crc 8) ^ crc32_table[index]; } // 输出反转并异或 crc ^ 0xFFFFFFFF; return crc; } // 另一种更清晰的、显式处理反转的实现方式推荐 uint32_t crc32_standard(const uint8_t *data, size_t length) { uint32_t crc 0xFFFFFFFF; for (size_t i 0; i length; i) { // 1. 输入反转反转数据字节 uint8_t byte reverse8(data[i]); // 2. 与CRC高8位异或作为索引 uint8_t index ((crc 24) ^ byte) 0xFF; // 3. CRC左移8位然后与查表值异或 crc (crc 8) ^ crc32_table[index]; } // 4. 输出反转对整个32位CRC进行位反转 crc reverse32(crc); // 5. 结果异或 crc ^ 0xFFFFFFFF; return crc; } // 32位反转函数 uint32_t reverse32(uint32_t x) { x ((x 0xFFFF0000) 16) | ((x 0x0000FFFF) 16); x ((x 0xFF00FF00) 8) | ((x 0x00FF00FF) 8); x ((x 0xF0F0F0F0) 4) | ((x 0x0F0F0F0F) 4); x ((x 0xCCCCCCCC) 2) | ((x 0x33333333) 2); x ((x 0xAAAAAAAA) 1) | ((x 0x55555555) 1); return x; }注意事项网上很多CRC32的代码看起来不一样主要是因为位序Bit Order的处理。有的代码为效率优化使用了(crc 8) ^ table[(crc ^ data) 0xFF]这种形式这其实是针对反射算法的它等价于输入输出反转。我们的crc32_standard函数是直观版本crc32_calculate是优化后的反射版本两者结果相同。使用反射算法时生成多项式也需要使用其反射值0xEDB88320而不是0x04C11DB7。这是CRC实现中最混乱的一点务必确认你参考的代码和表格是匹配的。4.3 CRC8 实现位操作法对于资源极其紧张或CRC8这种简单校验有时直接用位操作更节省ROM空间。#include stdint.h // CRC-8 (多项式 0x07) - 位操作法 uint8_t crc8_bitwise(const uint8_t *data, size_t length) { uint8_t crc 0x00; // 初始值 uint8_t polynomial 0x07; for (size_t i 0; i length; i) { crc ^ data[i]; // 数据与CRC异或 for (uint8_t bit 0; bit 8; bit) { if (crc 0x80) { // 判断最高位是否为1 crc (crc 1) ^ polynomial; } else { crc 1; } } } return crc; } // CRC-8/ITU (输入输出反转) - 位操作法 uint8_t crc8_itu_bitwise(const uint8_t *data, size_t length) { uint8_t crc 0x00; uint8_t polynomial 0x07; // 多项式不变但计算顺序反了 for (size_t i 0; i length; i) { crc ^ data[i]; for (uint8_t bit 0; bit 8; bit) { if (crc 0x01) { // 判断最低位是否为1 (反转) crc (crc 1) ^ polynomial; } else { crc 1; } } } return crc; }位操作法 vs 查表法位操作法代码简单占用ROM小但计算慢每个字节需要循环8次内层循环。适合数据量小、对速度不敏感或ROM极其稀缺的场景。查表法需要256字节的查找表CRC8是256字节CRC16是512字节CRC32是1024字节占用ROM多但计算极快每个字节只需几次操作。这是空间换时间的经典案例在绝大多数场景下都是首选。5. Python实现利用标准库与手动实现Python在数据处理、测试脚本和上位机开发中非常方便。实现CRC同样有两条路使用强大的标准库或者自己手动实现以加深理解。5.1 使用binascii和zlib标准库Python内置库提供了CRC32的直接计算。import binascii import zlib # 使用 binascii.crc32 (参数与CRC32/ISO 3309一致) data bHello, World! crc_value binascii.crc32(data) print(fbinascii.crc32: {crc_value:#010x}) # 输出 0x1c291ca3 # 注意binascii.crc32 初始值为0结果异或0xFFFFFFFF输入输出反转。 # 使用 zlib.crc32 (与 binascii.crc32 结果相同) crc_value_zlib zlib.crc32(data) print(fzlib.crc32: {crc_value_zlib:#010x}) # 增量计算 crc_initial 0 crc_initial binascii.crc32(bHello, , crc_initial) crc_final binascii.crc32(bWorld!, crc_initial) print(fIncremental CRC32: {crc_final:#010x})对于CRC16Python标准库没有直接提供但我们可以用crcmod这个强大的第三方库。pip install crcmodimport crcmod # 定义CRC16-MODBUS函数 crc16_modbus_func crcmod.mkCrcFun(poly0x18005, initCrc0xFFFF, revTrue, xorOut0x0000) # 注意crcmod的多项式表示法通常省略最高位的1所以0x8005写成0x18005。 # revTrue 表示输入输出反转。 data b\x01\x03\x00\x00\x00\x02 crc crc16_modbus_func(data) print(fCRC16-MODBUS: {crc:04x}) # 输出应为 c40b # 定义CRC16-CCITT (XMODEM) 函数 crc16_xmodem_func crcmod.mkCrcFun(poly0x11021, initCrc0x0000, revFalse, xorOut0x0000)5.2 手动实现查表法以CRC16-MODBUS为例理解原理后用Python实现查表法非常直观。def generate_crc16_table(poly0x8005): 生成CRC16查表法的表多项式为0x8005针对LSB-first反射算法 table [] for i in range(256): crc i for _ in range(8): if crc 1: crc (crc 1) ^ poly else: crc 1 table.append(crc 0xFFFF) return table # MODBUS CRC16表 (反射算法多项式0x8005) CRC16_MODBUS_TABLE generate_crc16_table(0x8005) def crc16_modbus_python(data: bytes) - int: 计算CRC16-MODBUS校验值 crc 0xFFFF for byte in data: # 反射算法索引 (crc ^ byte) 0xFF index (crc ^ byte) 0xFF # 查表更新crc (crc 8) ^ table[index] crc (crc 8) ^ CRC16_MODBUS_TABLE[index] crc 0xFFFF # 确保是16位 # MODBUS要求输出为小端字节序且已隐含了反射反转 return crc # 测试 test_data b\x01\x03\x00\x00\x00\x02 result crc16_modbus_python(test_data) print(fManual CRC16-MODBUS: {result:04x}) # 输出 c40b # 验证将CRC附加到数据后再计算一次CRC结果应为0 full_frame test_data result.to_bytes(2, little) verify_result crc16_modbus_python(full_frame) print(fVerification CRC (should be 0): {verify_result:04x}) # 输出 00005.3 手动实现位操作法以CRC8为例def crc8_bitwise(data: bytes, poly0x07, init0x00) - int: CRC8位操作法实现 crc init for byte in data: crc ^ byte for _ in range(8): if crc 0x80: # 判断最高位 crc ((crc 1) 0xFF) ^ poly else: crc (crc 1) 0xFF return crc def crc8_itu_bitwise(data: bytes, poly0x07, init0x00) - int: CRC8/ITU (输入输出反转) 位操作法实现 crc init # 计算时使用反射多项式对于poly0x07其反射值也是0x07 (因为对称) for byte in data: crc ^ byte for _ in range(8): if crc 0x01: # 判断最低位 crc (crc 1) ^ poly else: crc 1 return crc # 测试 data b\x01\x02\x03 print(fCRC8: {crc8_bitwise(data):02x}) print(fCRC8/ITU: {crc8_itu_bitwise(data):02x})6. 常见问题与调试技巧实录在实际项目中CRC校验出问题是家常便饭。下面是我踩过坑后总结的排查清单。6.1 问题排查速查表现象可能原因排查步骤计算出的CRC与标准工具/设备不一致1.参数错误多项式、初始值、反转、异或值2.字节序问题大端/小端3.数据范围错误是否包含CRC本身1. 使用已知正确的短数据如0x01, 0x02, 0x03测试。2. 找一个权威的在线CRC计算器如crccalc.com选择对应标准对比结果。3. 检查代码中的多项式、初始值等参数是否与标准严格一致。4. 确认数据输入是否正确特别是字符串和字节的转换。增量计算与整体计算结果不同1. 增量计算时初始值传递错误。2. 数据分片边界处理有误。1. 确保每次增量计算后将返回值作为下一次计算的初始值。2. 用整体计算的结果验证增量计算的结果。通信对方校验失败但我方计算“正确”1.传输字节序对方是先传CRC高字节还是低字节2.数据包含范围对方计算CRC时是否包含了地址、长度等字段3.算法标准不符你以为的MODBUS可能是XMODEM。1. 抓取通信数据包用逻辑分析仪、串口助手等。2. 将抓到的完整帧不含CRC部分用自己的算法计算比较结果。3. 尝试交换CRC结果的高低字节后再比较。查表法结果与位操作法结果不同1. 查表生成算法与计算算法不匹配反射 vs 非反射。2. 表格数据错误或未初始化。1. 用同一个简单的数据如单个字节0x00或0xFF分别测试两种方法。2. 检查生成表格的函数是否正确特别是多項式的位序。CRC校验偶尔通过但数据明显错误CRC的检错能力并非100%。对于特定模式的错误任何CRC都可能漏检。1. 这是正常现象。CRC是校验不是加密。2. 如果对可靠性要求极高考虑使用更强的校验算法如SHA系列哈希或结合重传机制。6.2 调试技巧与心得从零开始验证不要一上来就用复杂的长数据测试。先用单个字节如0x00测试你的CRC函数记录结果。然后找一两个在线的、口碑好的CRC计算器用同样的标准和数据验证。这是建立信心的第一步。善用Python作为“计算尺”在嵌入式开发中我经常先用Python的crcmod库快速计算出标准结果作为C语言代码的黄金参考。在单片机调试时可以把数据通过串口打印出来在PC上用Python脚本计算对比效率极高。关注字节序这是通信协议中最常见的坑。Modbus协议规定CRC低字节在前。这意味着如果你计算出的CRC16值是0xC40B在数据帧中排列顺序应该是0x0B, 0xC4。很多新手在这里栽跟头计算对了但发送顺序错了。理解“反射”这是CRC实现里最绕的概念。简单记住如果算法要求“输入反转”那么通常采用“反射算法”来实现会更高效。反射算法的特点是计算时从最低位开始处理右移。使用的生成多项式是原多项式的反射值例如0x04C11DB7的反射是0xEDB88320。查表法的更新公式通常是crc (crc 8) ^ table[(crc ^ data) 0xFF]。大多数常见的CRC标准CRC16-MODBUS, CRC32都使用反射算法。当你看到这种形式的代码基本可以确定它是反射实现。表格的存储在嵌入式C项目中CRC查表表通常以const数组的形式存储在Flash中而不是RAM中以节省宝贵的内存。确保你的表被正确声明为static const。在线计算器的陷阱不同在线计算器对同一标准的命名可能不同参数也可能有默认差异。务必仔细核对计算器提供的参数选项Poly, Init, RefIn, RefOut, XorOut并和你代码中的参数一一对应。7. 进阶话题从CRC到更强大的校验CRC虽然强大但并非万能。在一些对数据完整性要求极高的场景如金融交易、固件升级CRC可能不够用。CRC的局限CRC是检错码不是纠错码。它只能告诉你数据错了但不能修复错误。对于故意篡改CRC也无法提供安全性保证。更强大的选择哈希函数如MD5、SHA-1、SHA-256等。它们产生一个固定长度的“指纹”任何微小的数据变动都会导致指纹剧变。常用于文件完整性验证、数字签名。但计算量比CRC大得多。纠错码如海明码、RS码。不仅能发现错误还能在一定范围内纠正错误。常用于存储介质如CD、DVD、RAID和深空通信。实战建议单片机通信CRC16如MODBUS或CRC32足矣兼顾可靠性和计算开销。文件传输对于大文件可在分片中使用CRC32校验每个包整体文件再用SHA-256校验一次。安全敏感数据必须使用加密哈希函数如SHA-256并结合数字签名技术。最后分享一个我调试CAN总线通信时的真实案例。设备间歇性丢帧用CRC15CAN标准用的校验发现不了问题。后来发现是硬件电磁干扰导致帧间隔出错CRC校验的是帧内数据对帧间错误无能为力。最终在CRC校验之上增加了序列号和应答重传机制才彻底解决。所以校验是重要的一环但一个健壮的通信系统需要多层防御。理解CRC用好CRC是构建可靠数字系统的基本功。希望这篇长文能帮你把这件工具打磨得更加顺手。
返回列表