modbus-工业通讯总线协议

发布于 2022-10-18  1077 次阅读


一、简介

1.modbus是一种串行通信协议,是Modicon公司(现在的施耐德电气 Schneider Electric)于1979年为使用可编程逻辑控制器(PLC)通信而发表。

2.modbus是工业领域通信协议的业界标准,并且现在是工业电子设备之间常用的连接方式。

3.包括RTU、ASCII、TCP三种报文类型,采用poll/slave(主/从)方式通信。

二、主机/从机

主机:处理业务逻辑,发出指令,接收从机反馈,一般只有一个

从机:接收并执行指令,对主机反馈执行结果,可以有多个,但存在数量限制(两个字节表示数量有限)

过程:主机处理业务逻辑后,发出当前所需要执行的指令,对应的从机接收到指令后开始执行,并将执行结果反馈回主机,一次完整的modbus通讯结束

三、Modbus-RTU

1.rtu通讯协议使用二进制数据,优势是信息密度高

2.数据帧

地址码功能码数据校验码
1字节1字节n字节2字节(CRC校验)

地址码-1字节 表示对应的从机

功能码-1字节 表示需执行的功能

数据块-n字节 表示此指令参数

校验码-2字节 一般使用crc循环校验算法计算

3.RTU没有帧头和帧尾,所以协议里明确两帧之间要大于3.5个字节时间间隔,作为一帧结束的判断依据

4.crc-modbus循环校验算法

void XCRC::InvertUint8(unsigned char* DesBuf, unsigned char* SrcBuf)
{
    {
        int i;
        unsigned char temp = 0;

        for (i = 0; i < 8; i++)
        {
            if (SrcBuf[0] & (1 << i))
            {
                temp |= 1 << (7 - i);
            }
        }
        DesBuf[0] = temp;
    }
}

void XCRC::InvertUint16(unsigned short* DesBuf, unsigned short* SrcBuf)
{
    int i;
    unsigned short temp = 0;

    for (i = 0; i < 16; i++)
    {
        if (SrcBuf[0] & (1 << i))
        {
            temp |= 1 << (15 - i);
        }
    }
    DesBuf[0] = temp;
}

unsigned short XCRC::CRC16_MODBUS(unsigned char* puchMsg, unsigned int usDataLen)
{
    unsigned short wCRCin = 0xFFFF;
    unsigned short wCPoly = 0x8005;
    unsigned char wChar = 0;

    while (usDataLen--)
    {
        wChar = *(puchMsg++);
        InvertUint8(&wChar, &wChar);
        wCRCin ^= (wChar << 8);

        for (int i = 0; i < 8; i++)
        {
            if (wCRCin & 0x8000)
            {
                wCRCin = (wCRCin << 1) ^ wCPoly;
            }
            else
            {
                wCRCin = wCRCin << 1;
            }
        }
    }
    InvertUint16(&wCRCin, &wCRCin);
    return (wCRCin);
}

5.示例

主机发送: 01 03 00 00 00 01 XX XX (X需要CRC计算)
地址码:01  //01从机执行
功能码:03  //查询指令
数据块:00 00 //起始寄存器地址  00 01 //向后查询1个寄存器
校验码:XX XX //前6字节通过crc算法计算出的值

从机回复: 01 03 02 12 34 XX XX (X需要CRC计算)
地址码:01 //01从机的反馈
功能码:03 //查询指令的反馈
数据块:02 //数据长度2字节(1个寄存器长度就是2字节)  12 34 //查询的寄存器储存的数据
校验码:XX XX //前5字节通过crc算法计算出的值

四、Modbus-ASCII

1.ascii通讯协议使用ascii码传输数据,优势是可读性高,人可以看懂

2.数据帧

起始地址码功能码数据校验终止
字符  ':'(3A)2字节2字节0到2 * 252字节2字节(LRC校验)2字节(CR,LF)

起始码-1字节:固定字符 ':' ,十六进制为 3A

地址码、功能码和数据块:计算和rtu一致,但在最后二进制需转为ASCII码,此时字节长度会变为两倍

校验码-2字节:一般使用lrc校验算法计算

终止码-2字节:回车(CR) 十六进制为 0D 换行(LF) 十六进制为 0A

3.ASCII多了帧头和帧尾,也就是说可以有用这个头尾判断一帧字节来判断是否结束

4.lrc-modbus循环校验算法

unsigned char calculateLRC(unsigned char * auchMsg, unsigned short usDataLen)
{
    unsigned char uchLRC = 0 ; /* LRC 初始化*/
    while (usDataLen--) /* 完成整个报文缓冲区*/
        uchLRC += *auchMsg++ ; /* 缓冲区字节相加,无进位*/
    return ((unsigned char)(-((char)uchLRC))) ; /* 返回二进制补码*/
}

5.ascii码与十六进制的互相转换

void xcode::tosl(char data, char* txtdata) {
	txtdata[0] = (data & 0xf0) >> 4;
	txtdata[1] = (data & 0x0f) >> 0;
	txtdata[0] = (txtdata[0] < 10 ? txtdata[0] + '0' : txtdata[0] + 'A' - 10);
	txtdata[1] = (txtdata[1] < 10 ? txtdata[1] + '0' : txtdata[1] + 'A' - 10);
	txtdata[2] = '\0';
}

void xcode::tonu(char* txtdata, char& data) {
	data = 0x00;
	data |= (txtdata[1] < 'A' ? (txtdata[1] - '0') << 0 : (txtdata[1] - 'A' + 10) << 0);
	data |= (txtdata[0] < 'A' ? (txtdata[0] - '0') << 4 : (txtdata[0] - 'A' + 10) << 4);
}

6.示例

rtu: 01 03 00 00 00 01 XX XX (X需要LRC计算)
转ascii并添加起始码和终止码:“:3031303330303030303030313X3X3X3X\r\n”
所以
主机发送的ASCII数据为:“:3031303330303030303030313X3X3X3X\r\n”
主机发送的十六进制数据为:3A 01 03 00 00 00 01 XX XX 0D 0A

rtu: 01 03 02 12 34 XX XX (X需要LRC计算)
转ascii并添加起始码和终止码:“:303130333032313233343X3X3X3X\r\n”
所以
从机反馈的ASCII数据为:“:303130333032313233343X3X3X3X\r\n”
从机反馈的十六进制数据为:3A 01 03 02 12 34 XX XX 0D 0A

五、Modbus-TCP

1.主站为client端,主动建立连接;从站为server端,等待连接。

2.ModbusTCP的数据帧可分为两部分:MBAP+PDU

MBAP部分

事务处理标识协议标识长度单元标识(地址码)
2字节2字节2字节1字节

事务处理标识 :可以理解为报文的序列号,一般每次通信之后就要加1以区别不同的通信数据报文。
协议标识符 :00 00表示ModbusTCP协议。
长度 :表示接下来的数据长度,单位为字节。
单元标识符 :可以理解为设备地址,和rtu中地址码一样。

PDU部分

功能码数据
1字节n字节

功能码和数据:rtu中含义一致。

3.ModbusTCP不需要crc或lrc验证

4.示例

主机发送: 01 C8 00 00 00 06 01 03 00 00 00 01
事务处理标识:01 C8 //每次通信之后就要加1以区别不同的通信数据报文
协议标识:00 00  //ModbusTCP协议
长度:00 06 //指令长度6个字节
地址码:01  //01从机执行
功能码:03  //查询指令
数据块:00 00 //起始寄存器地址  00 01 //向后查询1个寄存器

从机回复: 01 C8 00 00 00 05 01 03 02 12 34
事务处理标识:01 C8 //01 C8的通信数据报文的反馈
协议标识:00 00  //ModbusTCP协议
长度:00 05 //反馈长度5个字节
地址码:01 //01从机的反馈
功能码:03 //查询指令的反馈
数据块:02 //数据长度2字节(1个寄存器长度就是2字节)  12 34 //查询的寄存器储存的数据

六、常见指令

查询
主机发送: 01 03 00 00 00 01
从机回复: 01 03 02 12 34
/*发送解析*/
01-地址
03-功能码,代表查询功能,其他功能后面再说
00 00-代表查询的起始寄存器地址.说明从0x0000开始查询.
(这里需要说明以下,Modbus把数据存放在寄存器中,通过查询寄存器来得到不同变量的值,一个寄存器地址对应2字节数据;)
00 01-代表查询了一个寄存器.结合前面的00 00,意思就是查询从0开始的1个寄存器值;
/*回复解析*/
01-地址
03-功能码
02-代表后面数据的字节数,因为上面说到,一个寄存器有2个字节,所以后面的字节数肯定是2*查询的寄存器个数;
12 34-寄存器的值是12 34,结合发送的数据看出,01这个寄存器的值为12 34


修改
主机发送: 01 06 00 00 00 01
从机回复: 01 06 00 00 00 01
*发送解析*/
01-主机要查的地址
06-功能码,代表修改单个寄存器功能,修改有些不同,有修改一个寄存器和修改多个寄存器;
00 00-代表修改的起始寄存器地址.说明从0x0000开始.
00 01-代表修改的值为00 01.结合前面的00 00,意思就是修改0号寄存器值为00 01;
/*回复解析*/
01-从机返回的地址,说明这就是主机查的从机
06-功能码,代表修改单个寄存器功能;
00 00-代表修改的起始寄存器地址.说明是0x0000.
00 01-代表修改的值为00 01.结合前面的00 00,意思就是修改0号寄存器值为00 01;


主机发送: 01 10 00 00 00 01 02 11 22
从机回复: 01 10 00 00 00 01
/*发送解析*/
01-主机要查的地址
10-功能码,代表修改多个寄存器功能;
00 00-代表修改的起始寄存器地址.说明从0x0000开始.
00 01-代表修改的寄存器数量,这里开始于0x06的修改不同;
02 -表示修改的总字节数,由于只修改了1个寄存器,所以数据要有两个字节;
11 22-表示修改的值,结合上面,就是从第0000寄存器开始修改一个寄存器值为11 22,就是把0000寄存器改为11 22;

/*回复解析*/
01-从机返回的地址,说明这就是主机查的从机
10-功能码
00 00-代表修改的起始寄存器地址.说明是0x0000.
00 01-代表修改的寄存器数量,只需要回复这么多久足够了,从机告诉主机,你修改了哪几个寄存器就足够了;

七、参考

1.Modbus协议解析--小白一看就懂的协议 http://t.csdn.cn/dIRtk

2.MODBUS三种通讯模式RTU,ASCII,TCP,功能码,RCR校验 http://t.csdn.cn/F4Qz1

3.Modbus的CRC16和LRC计算方式 http://t.csdn.cn/j2bWY

————星辉20221018