目录
- 1 Modbus 介绍
- 2 Modbus RTU协议传输方式
- 2.1 地址码
- 2.2 功能码
- 2.3 数据码
- 2.4 校验码
- 3 CRC算法实现
- 2.1 代码
- 3.2 运行结果
1 Modbus 介绍
Modbus是一种串行通信协议,是Modicon公司(现在的施耐德电气 Schneider Electric)于1979年为使用可编程逻辑控制器(PLC)通信而发表。Modbus已经成为工业领域通信协议的业界标准(De facto),并且现在是工业电子设备之间常用的连接方式。
Modbus协议目前存在用于串口、以太网以及其他支持互联网协议的网络的版本。大多数Modbus设备通信通过串口EIA-485物理层进行。
对于串行连接,存在两个变种,它们在数值数据表示不同和协议细节上略有不同。Modbus RTU是一种紧凑的,采用二进制表示数据的方式,Modbus ASCII是一种人类可读的,冗长的表示方式。这两个变种都使用串行通信(serial communication)方式。RTU格式后续的命令/数据带有循环冗余校验的校验和,而ASCII格式采用纵向冗余校验的校验和。被配置为RTU变种的节点不会和设置为ASCII变种的节点通信,反之亦然。
本文描述是正是Modbus RTU中使用的校验和算法。
MODBUS 通讯方式采用主从方式的查询相应机制,只有主站发出查询时,从站才能给出响
应,从站不能主动发送数据。主站可以向某一个从站发出查询,也可以向所有从站广播信息。
从站只响应单独发给它的查询,而不响应广播消息。
2 Modbus RTU协议传输方式
串口配置每个字节的数据位:1 个起始位、8 个数据位、1 个停止位(无奇偶校验位)。
MODBUS-RTU 数据帧的结构:
说明:
- 数据字段中多字节成员是按网络字节序即高位在前。
- 校验字段两个字节是小端字节序即低位在前。
- 校验码是对其前面地址码,功能码和数据码的校验值。
2.1 地址码
由一个字节(8 位二进制码)组成,各从机设备的寻址范围 01~F7(转成十进制 1~247),
其它地址保留。
2.2 功能码
功能码告诉了被寻址到的终端执行何种功能。
2.3 数据码
数据码包含了 终端执行特定功能所需要的数据或者终端响应查询时采集到的数据。这些数据的内容可能是数值、参考地址或者设置值。例如:功能域码告诉终端 读取一个寄存器,数据域则需要反映明从哪个寄存器开始及读取多少个数据,而从机数据码回送内容则包含了数据长度和相应的数据。
2.4 校验码
校验码错误校验(CRC)域占用两个字节,包含了一个 16 位的二进制值。CRC 值由传输设
备计算出来,然后附加到数据帧上,接收设备在接收数据时重新计算 CRC 值,然后与接收到的
CRC 域中的值进行比较。如果这两个值不相等,就发生了错误。
生成一个 CRC 的流程为:
- 预置一个 16 位寄存器为 OFFFFH(16 进制,全 1),称之为 CRC 寄存器。
- 把数据帧中的第一个字节的 8 位与 CRC 寄存器中的低字节进行异或运算,结果存回 CRC
寄存器。 - 将 CRC 寄存器向右移一位,最高位填以 0,最低位移出并检测。
- 上一步中被移出的那一位如果为 0:重复第三步(下一次移位);为 1:将 CRC 寄存器与
一个预设的固定值(多项式 0A001H)进行异或运算。 - 重复第三点和第四步直到 8 次移位。这样处理完了一个完整的八位。
- 重复第 2 步到第 5 步来处理下一个八位,直到所有的字节处理结束。
- 最终 CRC 寄存器的值就是 CRC 的值。
3 CRC算法实现
2.1 代码
#include <cstdint>
#include <cstdio>
//MODBUS-RTU协议使用的CRC16算法
uint16_t modbus_crc16(uint8_t *data, uint16_t size)
{
uint16_t crc = 0xFFFF;
for(uint16_t i = 0; i < size; ++i)
{
crc ^= (*data++);
for(uint8_t b = 0; b < 8; ++b)
{
if(!(crc & 0x1))
crc >>= 1;
else
{
crc >>= 1;
crc ^= 0xA001;
}
}
}
return crc;
}
int main()
{
uint8_t data1[] = { 0x01, 0x06, 0x00, 0x10, 0x00, 0x03 };
uint8_t data2[] = { 0x01, 0x06, 0x00, 0x10, 0x00, 0x01 };
uint16_t crc1 = modbus_crc16(data1,sizeof(data1));
uint16_t crc2 = modbus_crc16(data2,sizeof(data2));
printf("crc1: %02X %02X\n", (crc1 & 0xFF), (crc1 >> 8));
printf("crc2: %02X %02X\n", (crc2 & 0xFF), (crc2 >> 8));
return 0;
}
3.2 运行结果
运行结果:
$ ./modbuscrc
crc1: C8 0E
crc2: 49 CF