引言
最近在研究modbus协议,通讯流程基本搞通了,开始上手写程序。crc校验是很重要的部分,也比较简单,虽然文档上有查表法实现的代码,但还是写一个计算的方法测试一下。
循环冗余校验
循环冗余校验(英语:Cyclic redundancy check,通称“CRC”)是一种根据网络数据包或电脑文件等数据产生简短固定位数校验码的一种散列函数,主要用来检测或校验数据传输或者保存后可能出现的错误。生成的数字在传输或者存储之前计算出来并且附加到数据后面,然后接收方进行检验确定数据是否发生变化。
MODBUS生成 CRC 的过程为:
- 将一个 16 位寄存器装入十六进制 FFFF (全 1). 将之称作 CRC 寄存器.
- 将报文的第一个 8 位字节与 16 位 CRC 寄存器的低字节异或,结果置于 CRC 寄存器.
- 将 CRC 寄存器右移 1 位 (向 LSB 方向), MSB 充零. 提取并检测 LSB.
- (如果 LSB 为 0): 重复步骤 3 (另一次移位).
(如果 LSB 为 1): 对 CRC 寄存器异或多项式值 0xA001 (1010 0000 0000 0001). - 重复步骤 3 和 4,直到完成 8 次移位。当做完此操作后,将完成对 8 位字节的完整操作。
- 对报文中的下一个字节重复步骤 2 到 5,继续此操作直至所有报文被处理完毕。
- CRC 寄存器中的最终内容为 CRC 值.
- 当放置 CRC 值于报文时,如下面描述的那样,高低字节必须交换。
按照官方上面的流程生成代码,但是测试结果错误,和官方文档的查表法结果不一样。上网查找别人的代码,发现问题在于第3步,应该是检测的没有移位前的最低位,即移出的那一位,我的程序是检测的移出后CRC寄存器的最低位,所以计算结果错误。
修改后的代码如下:
uint16_t cal_crc16(uint16_t *buffer, uint16_t buffer_length)
{
uint16_t crc = 0xFFFF;
while (buffer_length--) {
crc ^= *buffer++;
for (int i = 0; i < 8; i++) {
if (crc & 0x01)
crc = crc >> 1 ^ 0xA001;
else
crc >>= 1;
}
}
crc = ((crc & 0xff) << 8) + (crc >> 8);
return crc;
}
原来的代码:
uint16_t my_crc16(uint16_t *buffer, uint16_t buffer_length)
{
uint16_t crc = 0xFFFF;
while (buffer_length--) {
crc ^= *buffer++;
for (int i = 0; i < 8; i++) {
crc >>= 1;
if (crc & 0x01)
crc ^= 0xA001;
}
}
crc = ((crc & 0xff) << 8) + (crc >> 8);
return crc;
}
查表法的原理
CRC查表法师将移位异或运算的计算结果做成了一个表,就是将0~256放入一个长度为16位的寄存器中的低8位,高八位填充0,然后将该寄存器与多项式0xA001按照CRC计算过程(3)(4)执行,直到8位全部移出,最后寄存器中的值就是表格数据。高8位、低8位分别列一个表,可以省去交换高低字节
生成表的代码
uint16_t table(uint16_t n)
{
uint16_t crc = n;
for (int i = 0; i < 8; i++) {
if (crc & 0x01)
crc = crc >> 1 ^ 0xA001;
else
crc >>= 1;
}
return crc;
}
以下分别是高低字节单独列表的代码和只列一个表的代码
/* Table of CRC values for high-order byte */
static const uint8_t table_crc_hi[] = {
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1,