浅谈CRC校验

CRC校验详解

前言:CRC校验是技能大赛比较重要的一种数据校验方式,也是考察的重点。但是很多初次接触的人总是会感觉无从下手。鉴于此,总结一下我对CRC的认识和运用。

  • 什么是CRC校验?
  • CRC的校验原理
  • 模2除法
  • 二进制系数多项式
  • 具体步骤
  • 代码实现

什么是CRC校验?

CRC校验实用程序库在数据存储和数据通讯领域,为了保证数据的正确,就不得不采用检错的手段。在诸多检错手段中,CRC是最著名的一种。CRC的全称是循环冗余校验,其特点是:检错能力强,开销小,易于用编码器及检测电路实现。从其检错能力来看,它所不能发现的错误的几率仅为0.0047%以下。能够对数据进行多项式计算,并将得到的结果附在帧的后面,接收设备也执行类似的算法,以保证数据传输的正确性和完整性。

CRC的校验原理

循环冗余校验同其他差错检测方式一样,通过在要传输的k比特数据D后添加(n-k)比特冗余位(又称帧检验序列, FrameCheckSequenceFCS) F r a m e C h e c k S e q u e n c e , F C S ) F形成n比特的传输帧T,再将其发送出去。
特别的,循环冗余校验提供一个预先设定的 (nk+1) ( n − k + 1 ) 比特整数P,并且要求添加的 (nk) ( n − k ) 比特F满足:
TmodP==0(1) T m o d P == 0 … … ( 1 )
T=2nkD+F(2) T = 2 n − k D + F … … ( 2 )
基于上述要求,实际应用时,发送方和接收方按以下方式通信:

  1. 发送方和接收方在通信前,约定好预设整数P。
  2. 发送方在发送前通过(1)和(2)式确定并填充F,然后发送。
  3. 接收方收到数据,进行 result=TmodP r e s u l t = T m o d P 运算,当且仅当result = 0时接收方认为没有差错。

发送方在发送数据前需要确定填充的 (nk) ( n − k ) 比特F,以下提供了两种等价的方式来确定F。

模二运算

模二运算采用无进位的二进制加法,恰好为异或(XOR)操作。
由于我们最终的目的是(1)式,根据(2)式,我们有
(2nkD+F)/P=2nkD/P+F/P(3) ( 2 n − k D + F ) / P = 2 n − k D / P + F / P … … ( 3 )
现在,我们令
2nkD/P=Q+R/P(4) 2 n − k D / P = Q + R / P … … ( 4 )
于是,我们有
(2nkD+F)/P=Q+R/P+F/P(5) ( 2 n − k D + F ) / P = Q + R / P + F / P … … ( 5 )
由于采用无进位的二进制加法(等价于XOR操作),因此当我们令 F = R 时,即T = 2n-kD + R,有
(2nkD+F)/P=Q+R/P+F/P=Q(6) ( 2 n − k D + F ) / P = Q + R / P + F / P = Q … … ( 6 )
此时便有(1)式成立。
因此利用模二加法我们知,我们需要添加的帧检验序列F为:
F=2nkDmodP(7) F = 2 n − k D m o d P … … ( 7 )

二进制系数多项式

该种方法,我们试图对任意的二进制数都构造与其对应的一个二进制系数多项式,构造如下:
对于任意k位二进制数D =dk-1…d2d1d0,其对应的多项式为
D(X)=diXii[0,k)(8) D ( X ) = ∑ d i ∗ X i , i ∊ [ 0 , k ) … … ( 8 )
例如,D = 110101,则 D(X)=X5+X4+X2+1 D ( X ) = X 5 + X 4 + X 2 + 1 。
运算过程依然是模二的,则此时的CRC过程可描述如下:
XnkD(X)/P(X)=Q(X)+R(X)/P(X)(9) X n − k D ( X ) / P ( X ) = Q ( X ) + R ( X ) / P ( X ) … … ( 9 )
T(X)=XnkD(X)+R(X)(10) T ( X ) = X n − k D ( X ) + R ( X ) … … ( 10 )
即,此时的F(X)满足:
F(X)=XnkD(X)modP(X)(11) F ( X ) = X n − k D ( X ) m o d P ( X ) … … ( 11 )

具体步骤

  1. 选择合适的除数
  2. 看选定除数的二进制位数,然后再要发送的数据帧上面加上这个位数-1位的0,然后用新生成的帧以模2除法的方式除上面的除数,得到的余数就是该帧的CRC校验码。注意,余数的位数一定只比除数位数少一位,也就是CRC校验码位数比除数位数少一位,如果前面位是0也不能省略。
  3. 将计算出来的CRC校验码附加在原数据帧后面,构建成一个新的数据帧进行发送;最后接收端在以模2除法方式除以前面选择的除数,如果没有余数,则说明数据帧在传输的过程中没有出错。

CRC C R C 校 验 码 计 算 示 例 :
现假设选择的CRC生成多项式为G(X) = X4 + X3 + 1,要求出二进制序列10110011的CRC校验码。下面是具体的计算过程:
①将多项式转化为二进制序列,由G(X) = X4 + X3 + 1可知二进制一种有五位,第4位、第三位和第零位分别为1,则序列为11001
②多项式的位数位5,则在数据帧的后面加上5-1位0,数据帧变为101100110000,然后使用模2除法除以除数11001,得到余数。
③将计算出来的CRC校验码添加在原始帧的后面,真正的数据帧为101100110100,再把这个数据帧发送到接收端。
④接收端收到数据帧后,用上面选定的除数,用模2除法除去,验证余数是否为0,如果为0,则说明数据帧没有出错。

代码实现

以下是CRC的核心代码,仅供研究CRC的实现过程。

///C# CRC16 核心代码
public byte[] CRC16_C(byte[] data)
{
    byte maxValue1 = byte.MaxValue;
    byte maxValue2 = byte.MaxValue;
    byte num1 = 1;
    byte num2 = 160;
    foreach (byte num3 in data)
    {
        maxValue1 ^= num3;
        for (int index = 0; index <= 7; ++index)
        {
            byte num4 = maxValue2;
            byte num5 = maxValue1;
            maxValue2 >>= 1;
            maxValue1 >>= 1;
            if (((int)num4 & 1) == 1)
                maxValue1 |= (byte)128;
            if (((int)num5 & 1) == 1)
            {
                maxValue2 ^= num2;
                maxValue1 ^= num1;
            }
        }
    }
    return new byte[2] { maxValue1, maxValue2 };
}

仅靠上述代码是无法将CRC运用到实践中去的。因为实际使用中总会遇见一些特殊的情况,这是有实际情景决定的。我写了一个CRC计算工具,一下仅供学习参考。

/// <summary>
//通过点击事件获取我们需要的CRC16 高低位
/// </summary>
private void Btn_GetCRC_Click(object sender, RoutedEventArgs e)
{
    try
    {
        txt_CRC.Text = "";
        if (txt_OldDate.Text.Length <= 0)
            return;
        foreach (byte num in this.CRC16_C(strToToHexByte(txt_OldDate.Text)))
            txt_CRC.Text += num.ToString("X02");
            '''strToToHexByte是一个字符传转字节的方法,由于步骤繁琐就不献丑了。'''
    }
    catch
    {
        MessageBox.Show("输入十六进制字符格式不对!");
        txt_OldDate.Focus();
    }
}

通过上述代码段基本上可以解决问题了。如有需要,可以点击下载完整的Demo.程序界面原型以及效果(如图)。
这里写图片描述
支持各类数据转换,附带LRC校验。

### 串口通讯协议设计规范与最佳实践 #### 设计规范 串口通信作为一种常见的数据传输方式,在硬件和软件层面都需要遵循一定的设计规范,以确保系统的可靠性和稳定性。 1. **波特率设置** 波特率决定了每秒传输的数据位数。为了保证双方设备之间的同步性,必须严格匹配波特率参数[^2]。通常情况下,常用的波特率为9600、115200等标准化数值。 2. **数据帧结构** 数据帧由起始位、数据位、奇偶校验位(可选)、停止位组成。具体配置如下: - 起始位:固定为低电平,表示一帧数据的开始。 - 数据位:一般为7到8位,取决于实际需求。 - 奇偶校验位:用于检测传输错误,可以选择无校验、奇校验或偶校验。 - 停止位:高电平持续时间,可以是一倍或多倍于比特周期。 3. **超时机制** 在长时间未接收到有效数据时,应引入超时机制来防止死锁现象的发生。这可以通过定时器实现,并结合事件驱动模型提升程序响应效率[^1]。 4. **流控策略** 实现软硬件握手功能非常重要。常用的方法包括XON/XOFF字符控制或者RTS/CTS信号线管理流量方向及速率调整。 #### 最佳实践 除了基本的设计原则之外,还有一些经过验证的最佳实践经验可以帮助优化整个过程: 1. **CRC校验增强可靠性** 循环冗余检验(CRC)算法被广泛应用于保障消息完整性方面。特别是在噪声环境较为恶劣条件下尤为必要[^2]。 2. **采用异步模式减少耦合度** 使用UART这样的通用异步接收发送装置(UART),可以在不依赖外部时钟源的前提下完成高效的信息交换操作[^4]。 3. **模块化编码提高维护便利性** 将复杂的逻辑分解成独立的功能单元分别处理,比如初始化函数单独封装;读写操作各自定义接口形式等等做法均有利于后期升级迭代工作开展顺利进行下去。 4. **充分考虑边界条件测试覆盖范围广** 对极端情况下的行为表现给予特别关注,例如缓冲区溢出保护措施安排合理与否直接影响整体性能指标达成效果如何评估等问题都要仔细考量清楚才行。 ```python import serial from time import sleep def init_serial(port='/dev/ttyUSB0', baudrate=115200, timeout=1): ser = serial.Serial( port=port, baudrate=baudrate, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, bytesize=serial.EIGHTBITS, timeout=timeout ) if not ser.is_open: raise Exception(f"Failed to open {port}") return ser def send_data(ser, data): try: ser.write(data.encode()) sleep(0.1) # Allow some delay for transmission except Exception as e: print(e) def read_response(ser): response = "" while True: byte = ser.read().decode('utf-8') if not byte or byte == '\n': break response += byte return response.strip() ``` 以上代码片段展示了如何利用Python中的`pyserial`库创建一个简单的串口通信应用程序实例。其中包括了端口打开、数据发送以及响应读取等功能部分演示说明[^2]。 #### 标准参考 不同领域可能会有不同的特定要求和技术规格书可供查阅学习借鉴之处很多。例如工业自动化场景下经常涉及到Modbus RTU/TCP协议栈构建任务就需要参照官方文档指南执行相应步骤动作序列组合起来形成最终解决方案成果展示出来供大家交流讨论共同进步成长壮大队伍规模扩大影响力范围延伸出去影响更多人群受益匪浅值得推荐尝试一下看看效果怎样再做进一步改进完善细节方面的不足之处加以弥补修正过来达到预期目标为止结束本次话题分享环节谢谢大家的支持配合共同努力创造美好未来前景无限光明灿烂辉煌明天等着我们一起去开创属于自己的传奇故事篇章吧! ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

洋洋脚踝的金铃响了

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值