项目中DSP 28335需要和上位机西门子PLC通过485串口进行SCI通讯,采用Modbus协议(PLC可直接调用相应模块,很方便),
信息帧需要CRC16-Modbus进行校验。因为之前项目多是自己定的通信协议,采用奇偶校验,或者不校验,借着编写DSP通讯程
序的机会学习一下CRC16-Modbus校验。
根据Modbus协议,常规485通讯的信息发送形式如下:
地址 功能码 数据信息 校验码
1byte 1byte nbyte 2byte
CRC校验是前面几段数据内容的校验值,为一个16位数据,发送时,低8位在前,高8为最后。
例如:信息字段代码为: 01 10 12 34 56 78(十六进制),校验字段为:01 10 12 34 56 78 。
发送方:发出的传输字段为: 01 10 12 34 56 78 BB 3D , 其中 BB 3D 就是CRC16校验代码(计算原理后面详述)。
接收方:使用相同的计算方法计算出信息字段的校验码,对比接收到的实际校验码,如果相等及信息正确,不相等则信息错误。
计算原理:
1. 预置 1 个 16 位的寄存器为十六进制FFFF(即全为 1) , 称此寄存器为 CRC寄存器。
2. 把第一个 8 位二进制数据 (通信信息帧的第一个字节) 与 16 位的 CRC寄存器的低 8 位相异或, 把结果放于 CRC寄存器。
3. 把 CRC 寄存器的内容右移一位( 朝低位)用 0 填补最高位, 并检查右移后的移出位。
4. 如果移出位为 0, 重复第 3 步 ( 再次右移一位); 如果移出位为 1, CRC 寄存器与多项式A001 ( 1010 0000 0000 0001) 进行异或。
5. 重复步骤 3 和步骤 4, 直到右移 8 次,这样整个8位数据全部进行了处理。
6. 重复步骤 2 到步骤 5, 进行通信信息帧下一个字节的处理。
7. 将该通信信息帧所有字节按上述步骤计算完成后,得到的16位CRC寄存器的高、低字节进行交换。
8. 最后得到的 CRC寄存器内容即为 CRC码。
注:以上计算步骤中的多项式A001是8005按位颠倒后的结果。
按照这个思路,不难用C语言实现算法:
1 int CRC16Modbus(void) 2 { 3 unsigned short tmp = 0xffff; 4 unsigned short ret1 = 0; 5 unsigned char buff[6] = {0}; 6 buff[0] = 0x01; 7 buff[1] = 0x10; 8 buff[2] = 0x12; 9 buff[3] = 0x34; 10 buff[4] = 0x56; 11 buff[5] = 0x78; 12 13 for(int n = 0; n < 6; n++) //此处的6 -- 要校验的位数为6个 14 { 15 tmp = buff[n] ^ tmp; 16 for(int i = 0;i < 8;i++) //此处的8 -- 指每一个char类型又8bit,每bit都要处理 17 { 18 if(tmp & 0x01) 19 { 20 tmp = tmp >> 1; 21 tmp = tmp ^ 0xA001; 22 } 23 else 24 { 25 tmp = tmp >> 1; 26 } 27 } 28 } 29 30 ret1 = tmp >> 8; //将CRC校验的高低位对换位置 31 ret1 = ret1 | (tmp << 8); 32 33 return ret1; 34 }
如果验证上述程序,可得返回值ret1为 “0xBB3D” 。
另外再给出一个查找资料时搜到的比较简洁的CRC16-Modbus实现程序,不同于前一种算法类似于计算步骤直译,这种算法基于查表法:
1 const uint16_t crctalbeabs[] = { 2 0x0000, 0xCC01, 0xD801, 0x1400, 0xF001, 0x3C00, 0x2800, 0xE401, 3 0xA001, 0x6C00, 0x7800, 0xB401, 0x5000, 0x9C01, 0x8801, 0x4400 4 }; 5 6 uint16_t crc16tablefast(uint8_t *ptr, uint16_t len) 7 { 8 uint16_t crc = 0xffff; 9 uint16_t i; 10 uint8_t ch; 11 12 for (i = 0; i < len; i++) { 13 ch = *ptr++; 14 crc = crctalbeabs[(ch ^ crc) & 15] ^ (crc >> 4); 15 crc = crctalbeabs[((ch >> 4) ^ crc) & 15] ^ (crc >> 4); 16 } 17 18 ret1 = crc>> 8; 19 ret1 = ret1 | (crc<< 8); 20 21 return ret1 ; 22 }
两段代码计算结果相同,需要时直接调用两者中的任意一种程序即可。