C语言CRC校验代码(CRC16)(CRC函数、CRC校验函数解析、循环冗余校验、在线CRC校验、CRC在线、CRC网站)

直接上代码

函数

uint16_t Calculate_CRC16(const uint8_t *data, uint16_t length)
{
    uint16_t crc = 0xFFFF;
    for (uint16_t i = 0; i < length; i++)
    {
        crc ^= data[i]; // 直接 XOR 低字节,因为多项式是反转的
        for (uint8_t j = 0; j < 8; j++)
        {
            if (crc & 0x0001)              // 检查最低位
                crc = (crc >> 1) ^ 0xA001; // 右移并应用多项式
            else
                crc >>= 1; // 只右移
        }
    }
    return crc;
}

调用

int VendorB_PowerStart(serial_t *serial, char **errorMsg)
{
    // 后面可能有web设置报警器喇叭静音功能,到时提供一个全局变量和设置接口,本函数就只被算法触发的时候调,本函数会读全局变量

    uint8_t index = 0;
    uint8_t tbuf[128]; // 局部请求缓冲区
    uint8_t rbuf[128]; // Response buffer

    // 构建请求数据包
    tbuf[index++] = ClientInfor.devaddr; // 地址(设备地址)
    tbuf[index++] = 0x10;                // 功能码(对于此声光报警器控制板:把设置的数值写入指定的连续寄存器)
    tbuf[index++] = 0x04;                // 寄存器地址(高位)
    tbuf[index++] = 0x0E;                // 寄存器地址(低位)
    tbuf[index++] = 0x00;                // 寄存器个数(高位)
    tbuf[index++] = 0x05;                // 寄存器个数(低位)
    tbuf[index++] = 0x0A;                // 寄存器字节数(一个寄存器——高位+低位,有四个16进制数,等于两个字节)
    tbuf[index++] = 0x00;                // 修改数据(高位)—— 报警器状态
    tbuf[index++] = 0x03;                // 修改数据(低位)
    tbuf[index++] = 0x00;                // 修改数据(高位)—— 报警器音量
    tbuf[index++] = 0x1E;                // 修改数据(低位)
    tbuf[index++] = 0x01;                // 修改数据(高位)—— 报警器音调
    tbuf[index++] = 0x01;                // 修改数据(低位)
    tbuf[index++] = 0x00;                // 修改数据(高位)—— 报警器播放模式
    tbuf[index++] = 0x01;                // 修改数据(低位)
    tbuf[index++] = 0x01;                // 修改数据(高位)—— 警示灯闪烁模式(调试发现只支持默认模式,即 0x01 0x01)
    tbuf[index++] = 0x01;                // 修改数据(低位)

    // 默认出厂配置
    // 0x040E  00H, 03H 报警器和警示灯都打开
    // 0x040F  00H, 1EH 音量为 30;
    // 0x0410  01H, 01H 第1个文件夹的第1个语音;
    // 0x0411  00H, 01H 单曲循环播放;
    // 0x0412  01H, 01H 警示灯不与报警器同步,模式为爆闪模式;

    // Calculate CRC16,注意CRC16先发低位再发高位
    uint16_t crc = Calculate_CRC16(tbuf, index);
    tbuf[index++] = crc & 0xFF;        // CRC16(低位)
    tbuf[index++] = (crc >> 8) & 0xFF; // CRC16(高位)

    if (MODBUS_SendReceive(serial, tbuf, index, rbuf, sizeof(rbuf), errorMsg) != 0)
    {
        return -1;
    }

    // 这里可以添加对响应数据的验证逻辑
    // 例如验证响应的正确性或解析错误代码等

    return 0;
}

在这里插入图片描述
在这里插入图片描述

在线CRC校验网站

可用在线crc16校验网站来检验:CRC在线计算

01 10 04 0E 00 05 0A 00 03 00 1E 01 01 00 01 01 01

将发送数据(CRC校验码之前的数据)粘贴进去计算,结果与我们代码计算结果一致:

在这里插入图片描述

CRC16校验函数解析

uint16_t Calculate_CRC16(const uint8_t *data, uint16_t length)
{
    uint16_t crc = 0xFFFF;
    for (uint16_t i = 0; i < length; i++)
    {
        crc ^= data[i]; // 直接 XOR 低字节,因为多项式是反转的
        for (uint8_t j = 0; j < 8; j++)
        {
            if (crc & 0x0001)              // 检查最低位
                crc = (crc >> 1) ^ 0xA001; // 右移并应用多项式
            else
                crc >>= 1; // 只右移
        }
    }
    return crc;
}

这段C语言代码是实现了CRC-16(循环冗余校验)的一个常见版本。CRC-16是一种检测数据错误的方法,广泛用于通信和存储系统中。让我们逐步解释这段代码的关键部分:

初始化CRC值

uint16_t crc = 0xFFFF;

这行代码初始化了CRC的值为0xFFFF。这是开始计算前的标准初始值,用于确保CRC的高位在开始计算前就已经设置好了。

主循环

for (uint16_t i = 0; i < length; i++)

这个循环负责遍历输入的数据数组data,其中length是数组中数据的个数。每次循环处理一个字节。

XOR操作(异或操作)

crc ^= data[i];

在这一行中,当前的CRC值与数据的当前字节进行异或运算(XOR)。异或运算是一种基础的位操作,用于CRC计算中以整合当前字节的数据。

内部循环:位处理

for (uint8_t j = 0; j < 8; j++)

此循环对CRC值的每一位进行处理,总共循环8次,因为每个字节有8位。

条件判断与多项式运算

if (crc & 0x0001)
    crc = (crc >> 1) ^ 0xA001;
else
    crc >>= 1;

这里是CRC计算的核心部分。首先检查CRC的最低位(通过与0x0001进行AND运算)。如果最低位是1(表示当前的CRC是奇数),则CRC值先向右移一位,然后与多项式0xA001进行异或运算。如果最低位是0,则CRC值只向右移一位。

疑问
为什么是crc & 0x0001而不是crc & 0x1、crc & 0x01或者crc & 0x00000001?

在C语言中,表达式 crc & 0x0001crc & 0x1crc & 0x01、和 crc & 0x00000001 都是用来检查变量 crc 的最低位是否为 1。这三个表达式的功能是完全相同的,因为它们都会与 crc 的最低位进行位与(AND)操作。差别只在于数值表示的方式,但这对操作结果没有影响。

具体来说:

  • crc & 0x0001:这里 0x0001 是一个16位的常量,表示的是最低位为1,其余位为0。使用这种格式有助于清晰地显示我们关注的是16位整数的最低位。
  • crc & 0x01:与上面的表达式功能相同,只是以更简洁的形式表示。
  • crc & 0x00000001:这是一个32位的常量,但因为 crcuint16_t(即16位无符号整数),所以额外的高位零没有任何作用。

通常,在编写代码时选择哪种形式取决于编程风格和可读性的考虑。例如,在处理16位数据时(我们函数中定义的crc变量是uint16_t类型的,16位),使用 0x0001 可以明确地表示这是一个16位操作,这对于代码的可读性和维护性是有益的。

多项式0xA001

多项式0xA001是反转多项式,它来自标准的CRC-16多项式0x8005。在这种情况下,因为CRC的计算从低位开始,所以使用了反转的多项式。

总结

通过这种方式,CRC算法可以检测数据中的小错误,如单个位的翻转,或者两个位的错误。每处理完一个数据字节后,CRC值会更新,最终的crc值就是数据的CRC-16校验码。这个校验码可以用来检查数据在传输或存储过程中是否被更改过。

实际计算示例

在这里插入图片描述

让我们通过一个简单的示例来详细展示 Calculate_CRC16 函数的工作流程。假设我们有一个简单的输入数据,该数据只包含两个字节:0x030x04。我们将一步步计算这个数据的 CRC-16。

初始化变量

  • crc = 0xFFFF:初始化CRC值为全1,即16位都是1。
  • data = [0x03, 0x04]:输入数据。
  • length = 2:数据长度为2个字节。

第一轮计算(对数据 0x03

1. XOR操作

crc 初始值为 0xFFFF 和数据 0x03 进行 XOR 操作。我们首先将这些数转换为二进制形式:

  • crc 初始值: 0xFFFF1111 1111 1111 1111
  • 数据: 0x030000 0000 0000 0011

进行 XOR 操作,即每一位相同则结果为 0,不同则结果为 1:

  1. 对比最右边的两位:11 XOR 11 = 00
  2. 其余位因为 0x03 的高位都是0,所以 1 XOR 0 保持不变,即为 1
结果
  • 1111 1111 1111 1111
  • XOR 0000 0000 0000 0011
  • ----------------------
  • 1111 1111 1111 1100 → 十六进制为 0xFFFC
2. 进行8次循环,每次检查 crc 的最低位,并根据该位是0还是1进行操作
  • 第1次循环
    • 初始 crc = 0xFFFC(二进制:1111 1111 1111 1100
    • crc & 0x0001 => 结果为 0(最低位为0)
    • 右移 crc => crc = 0x7FFE(二进制:0111 1111 1111 1110
  • 第2次循环
    • crc = 0x7FFE
    • crc & 0x0001 => 结果为 0
    • 右移 crc => crc = 0x3FFF(二进制:0011 1111 1111 1111
  • 第3次循环
    • crc = 0x3FFF
    • crc & 0x0001 => 结果为 1
    • 右移并异或多项式 crc = (crc >> 1) ^ 0xA001 => crc = 0xD000(二进制:1101 0000 0000 0000
  • 继续上述循环至第8次,根据是否与1相与来决定是否应用多项式。
  1. 处理结束后的 crc(假设完成上述所有循环后的值)。

第二轮计算(对数据 0x04

  • 进行类似的处理,从XOR开始,直至八次循环。

这个过程会根据数据和每次循环的情况而改变。每一步都检查最低位,并根据这个最低位决定是否应用多项式 0xA001,或者只是简单地右移。

示例演示

接下来,我将编写一个Python程序来模拟这个计算过程,并显示每步的详细结果。这样你就可以清晰地看到每一步的变化。

def calculate_crc16_demo(data):
    crc = 0xFFFF
    steps = []
    
    for byte in data:
        crc ^= byte
        steps.append(f"After XOR with {byte:02X}: {crc:04X}")
        
        for _ in range(8):
            if crc & 0x0001:
                crc = (crc >> 1) ^ 0xA001
            else:
                crc >>= 1
            steps.append(f"Step after shift/XOR: {crc:04X}")
    
    return crc, steps

# Example data: [0x03, 0x04]
data_example = [0x03, 0x04]
final_crc, calculation_steps = calculate_crc16_demo(data_example)
final_crc, calculation_steps

运行结果:

结果
(33536,
 ['After XOR with 03: FFFC',
  'Step after shift/XOR: 7FFE',
  'Step after shift/XOR: 3FFF',
  'Step after shift/XOR: BFFE',
  'Step after shift/XOR: 5FFF',
  'Step after shift/XOR: 8FFE',
  'Step after shift/XOR: 47FF',
  'Step after shift/XOR: 83FE',
  'Step after shift/XOR: 41FF',
  'After XOR with 04: 41FB',
  'Step after shift/XOR: 80FC',
  'Step after shift/XOR: 407E',
  'Step after shift/XOR: 203F',
  'Step after shift/XOR: B01E',
  'Step after shift/XOR: 580F',
  'Step after shift/XOR: 8C06',
  'Step after shift/XOR: 4603',
  'Step after shift/XOR: 8300'])

我们使用了两个字节的数据(0x030x04)进行CRC-16计算,最终的CRC结果是 0x8300(十六进制)。以下是详细的计算步骤:

  1. 初始CRC: 0xFFFF
处理第一个字节(0x03)
  1. XOR操作后0xFFFC
  2. 循环操作
    • 0x7FFE(右移)
    • 0x3FFF(右移)
    • 0xBFFE(右移后应用多项式 0xA001
    • 0x5FFF(右移后应用多项式 0xA001
    • 0x8FFE(右移后应用多项式 0xA001
    • 0x47FF(右移后应用多项式 0xA001
    • 0x83FE(右移后应用多项式 0xA001
    • 0x41FF(右移后应用多项式 0xA001
处理第二个字节(0x04)
  1. XOR操作后0x41FB
  2. 循环操作
    • 0x80FC(右移后应用多项式 0xA001
    • 0x407E(右移)
    • 0x203F(右移)
    • 0xB01E(右移后应用多项式 0xA001
    • 0x580F(右移后应用多项式 0xA001
    • 0x8C06(右移后应用多项式 0xA001
    • 0x4603(右移后应用多项式 0xA001
    • 0x8300(右移后应用多项式 0xA001

每一步我们都进行了检查 crc 的最低位,然后决定是否右移或右移后与多项式 0xA001 进行异或操作。这些步骤一起构成了CRC计算的完整过程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Dontla

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

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

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

打赏作者

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

抵扣说明:

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

余额充值