误码检测基本方法对比:从奇偶校验到CRC

链路传数据,噪声是躲不掉的。比特翻转、时钟抖动、信号反射,哪个都能让你的数据变质。那我怎么知道收到的数据是对的还是错的?这是通信系统最实在的问题。本章就把四种主流误码检测方法摆在一起,看看它们各自怎么干活的。我入行那会儿,刚从这些基本原理开始折腾起。

误码检测方法对比——知识体系 误码检测基本方法 奇偶校验 单比特检测 奇/偶两种模式 开销最低 校验和(Checksum) 累加求补 IP/TCP/UDP使用 检错率约99.6% 循环冗余校验 R1×16-8最早版本 误警率较高 已演进为CRC CRC (现代标准) 模块化余式计算 突发检测能力强 硬件实现高效 四大指标对比 检测能力: 实现复杂度: 速度/吞吐: 通用性: 奇偶 < 校验和 < 早期循环冗余 < CRC 奇偶最低 → 校验和中 → 早期循环冗余中 → CRC略高 CRC硬件可达10+ Gbps,软件约1-2 Gbps CRC应用最广(存储/网络/射频/工业总线)

2.1 奇偶校验 —— 最简单的方法

奇偶校验是码元级别的基本保护。原理其实很简单:发端数一下数据里1的个数,然后在数据最后额外加一个比特。如果采用偶校验,就保证总共有偶数个1;奇校验就是奇数个1。

收端收到数据后,重新算一遍。一旦发现1的个数奇偶性和预期的对不上,就判定数据出错。

奇偶校验核心特性

  • 检测能力:只能检测奇数个比特的错误。偶数个比特同时翻转,它看不出来。
  • 开销:最低。每8比特数据只加1比特校验位,开销12.5%。
  • 实现复杂度:完全是硬件异或门的事。
  • 应用场景:UART串口通信、SDRAM内存条。

我的经验:有过一个项目,低速串行链路用单奇偶校验,偶尔出现误码居然检测不出来。后来排查发现干扰源每次翻转两个比特。这就是偶校验的死角——偶数个比特错误。说白了,奇偶校验只适合噪声极小的场景。我看过有人在RS-232上用,能对付。

2.2 校验和(Checksum)—— 互联网的老将

校验和比奇偶校验进了一步。它把数据切分成固定长度的字(通常是16位),然后把所有字加起来。加完之后取反,作为校验码携带在报文里。

收端做同样的加法,必须得到结果全1(即-0的补码)。这是IP、TCP、UDP协议使用的标准校验方式。

校验和核心特性

  • 检测能力:绝大多数单比特和多比特错误都能检出,但存在补偿现象——某字多1,另一字少1,总和不变。
  • 开销:对IP头20字节,校验和只占2字节,约10%。
  • 实现复杂度:只需累加器和补码运算。
  • 应用场景:网络协议栈(IP/TCP/UDP)。

我建议:校验和是个软件友好的方案。在嵌入式处理器上,你甚至可以用C语言一行循环搞定。但如果用硬件做四层负载均衡,校验和计算反而成了瓶颈。我记得有一次做100G网卡加速,校验和卸载到硬件做了。

2.3 循环多余校验 —— CRC的前身

“循环多余校验”这个说法来自早期文献,其实它就是CRC的雏形。我查了一些80年代的标准,当时的R1×16-8等协议已经用多项式除法做校验了。

核心思想:把数据视为一个高次多项式的系数。发端用生成多项式做模2除法,余数就是校验码。收端重新做除法,余数为0表示数据无误。

注意

  • 早期循环多余校验使用的生成多项式较短(如8位、12位),误警率相对较高。
  • 不同厂家的多项式实现不统一,存在互操作性问题。
  • 后来的CCITT/CRC标准取代了这些私有方案。

我曾经:接手过一台老设备,它的总线用了一种自定义的多项式做循环多余校验。维护文档早已失传。我花了几天工夫反推多项式和初始值。你以为现代系统都是标准CRC?还是有不少现场总线玩自己的花样。

2.4 CRC —— 现代工业标准

CRC是循环多余校验的正统继承者,标准化程度高得多。不同的CRC多项式被国际标准委员会定义,比如CRC-8、CRC-16、CRC-32、CRC-64。

CRC的计算和循环多余校验一样,使用模2除法做余数计算。但CRC在某些方面做了优化:

  • 生成多项式标准化:不同的CRC变种有特定多项式,比如CRC-32(0x04C11DB7)是IEEE 802.3标准。
  • 初始值/终值异或:有些CRC实现还做了初始值预设和输出异或处理。
  • 查表法加速:软件CRC可以通过一个256字节的预计算表来高速计算。

CRC vs 其他方法 — 关键对比

特性 奇偶校验 校验和 循环多余校验 CRC
检测突发错误 无(只查单比特奇偶性) 较弱(补偿易躲过) 中等(取决于多项式长度) 强(≥32位多项式能查全部单比特及大部分突发)
实现开销 低(1 bit) 中(16 bit累加) 中高(多项式除法) 中(查表或硬件LFSR)
误警率 极高(偶数错误完全漏报) 约2^-16 约2^-8 ~ 2^-12 约2^-32(CRC-32)
标准程度 无全局标准 IETF标准(RFC 1071) 私有/厂商特定 IEEE/ITU/ISO标准
典型用途 UART、内存奇偶 IP/TCP/UDP头 老式现场总线 ETH/SATA/PCIe/USB

我的经验:做存储的时候,SATA链路用CRC-32。有一次遇到控制器间歇性报CRC错,但因为CRC的误警率极低,几乎可以确定就是物理层信号完整性问题。后来在PCB走线加了等长约束,问题消失。CRC不仅能检错,还能反向定位硬件设计缺陷,挺有意思。

2.5 四种方法对比总结

你想想看,从最简单的奇偶校验到成熟的CRC,业界走了几十年:

  • 奇偶校验,说白了就是个计数器。检测能力最弱,但成本最低。
  • 校验和,互联网协议选了它。因为在软件里好实现。
  • 循环多余校验,是个过渡产品。不统一的标准给互操作挖了不少坑。
  • CRC,是当今王道。标准化、强检测、高速实现,缺一不可。

一句话总结:短突发、低错场景用奇偶校验或校验和;长突发、高可靠场景直接上CRC。没有一种方法能检出所有误码,但CRC是当下最接近“足够好”的选择。

CRC计算示例(代码)

拿CRC-8做例子。实际项目中我很少用软件跑CRC,因为效率吃紧。但理解原理还是需要的。

// CRC-8 查表法计算
#include <stdint.h>

uint8_t crc8_table[256];

void crc8_init_table(void) {
    for (uint16_t i = 0; i < 256; i++) {
        uint8_t crc = (uint8_t)i;
        for (uint8_t j = 0; j < 8; j++) {
            if (crc & 0x80)
                crc = (crc << 1) ^ 0x07;  // 多项式 0x07
            else
                crc <<= 1;
        }
        crc8_table[i] = crc;
    }
}

uint8_t crc8_calc(uint8_t *data, uint32_t len) {
    uint8_t crc = 0x00;
    for (uint32_t i = 0; i < len; i++) {
        crc = crc8_table[crc ^ data[i]];
    }
    return crc;
}

我建议:如果你在动手写CRC代码,注意三点:多项式初始值、输出异或(final XOR)、字节顺序(LSB/MSB first)。很多兼容性问题就出在这几项参数上。网上有CRC参数计算器,算之前定好参数。