文章目录
直接上代码函数调用在线CRC校验网站 CRC16校验函数解析初始化CRC值主循环XOR操作(异或操作)内部循环:位处理条件判断与多项式运算疑问为什么是crc & 0x0001而不是crc & 0x1、crc & 0x01或者crc & 0x00000001? 多项式`0xA001`总结 实际计算示例初始化变量第一轮计算(对数据 `0x03`)1. XOR操作结果 2. 进行8次循环,每次检查 `crc` 的最低位,并根据该位是0还是1进行操作 第二轮计算(对数据 `0x04`)示例演示处理第一个字节(0x03)处理第二个字节(0x04)
直接上代码
函数
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 & 0x0001
、crc & 0x1
、crc & 0x01
、和 crc & 0x00000001
都是用来检查变量 crc
的最低位是否为 1。这三个表达式的功能是完全相同的,因为它们都会与 crc
的最低位进行位与(AND)操作。差别只在于数值表示的方式,但这对操作结果没有影响。
具体来说:
crc & 0x0001
:这里 0x0001
是一个16位的常量,表示的是最低位为1,其余位为0。使用这种格式有助于清晰地显示我们关注的是16位整数的最低位。crc & 0x01
:与上面的表达式功能相同,只是以更简洁的形式表示。crc & 0x00000001
:这是一个32位的常量,但因为 crc
是 uint16_t
(即16位无符号整数),所以额外的高位零没有任何作用。 通常,在编写代码时选择哪种形式取决于编程风格和可读性的考虑。例如,在处理16位数据时(我们函数中定义的crc
变量是uint16_t
类型的,16位),使用 0x0001
可以明确地表示这是一个16位操作,这对于代码的可读性和维护性是有益的。
多项式0xA001
多项式0xA001
是反转多项式,它来自标准的CRC-16多项式0x8005
。在这种情况下,因为CRC的计算从低位开始,所以使用了反转的多项式。
总结
通过这种方式,CRC算法可以检测数据中的小错误,如单个位的翻转,或者两个位的错误。每处理完一个数据字节后,CRC值会更新,最终的crc
值就是数据的CRC-16校验码。这个校验码可以用来检查数据在传输或存储过程中是否被更改过。
实际计算示例
让我们通过一个简单的示例来详细展示 Calculate_CRC16
函数的工作流程。假设我们有一个简单的输入数据,该数据只包含两个字节:0x03
和 0x04
。我们将一步步计算这个数据的 CRC-16。
初始化变量
crc = 0xFFFF
:初始化CRC值为全1,即16位都是1。data = [0x03, 0x04]
:输入数据。length = 2
:数据长度为2个字节。 第一轮计算(对数据 0x03
)
1. XOR操作
crc
初始值为 0xFFFF
和数据 0x03
进行 XOR 操作。我们首先将这些数转换为二进制形式:
crc
初始值: 0xFFFF
→ 1111 1111 1111 1111
数据: 0x03
→ 0000 0000 0000 0011
进行 XOR 操作,即每一位相同则结果为 0,不同则结果为 1:
对比最右边的两位:11 XOR 11 = 00
其余位因为 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相与来决定是否应用多项式。 处理结束后的 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'])
我们使用了两个字节的数据(0x03
和 0x04
)进行CRC-16计算,最终的CRC结果是 0x8300
(十六进制)。以下是详细的计算步骤:
0xFFFF
处理第一个字节(0x03)
XOR操作后:0xFFFC
循环操作: 0x7FFE
(右移)0x3FFF
(右移)0xBFFE
(右移后应用多项式 0xA001
)0x5FFF
(右移后应用多项式 0xA001
)0x8FFE
(右移后应用多项式 0xA001
)0x47FF
(右移后应用多项式 0xA001
)0x83FE
(右移后应用多项式 0xA001
)0x41FF
(右移后应用多项式 0xA001
) 处理第二个字节(0x04)
XOR操作后:0x41FB
循环操作: 0x80FC
(右移后应用多项式 0xA001
)0x407E
(右移)0x203F
(右移)0xB01E
(右移后应用多项式 0xA001
)0x580F
(右移后应用多项式 0xA001
)0x8C06
(右移后应用多项式 0xA001
)0x4603
(右移后应用多项式 0xA001
)0x8300
(右移后应用多项式 0xA001
) 每一步我们都进行了检查 crc
的最低位,然后决定是否右移或右移后与多项式 0xA001
进行异或操作。这些步骤一起构成了CRC计算的完整过程。