【知识分享】Modbus通信协议详解

协议

        这里分两部分,Modbus和协议,首先什么是协议?百度解释下就是:意思是共同计议,协商;经过谈判、协商而制定的共同承认、共同遵守的文件。比如大学毕业找工作的时候,一般要签一份叫“三方协议”的,三方指自己、校方、企业,这份协议里规定了三方需要遵守的一些事项。

通信协议

        那通信协议又是什么呢?通信就是双方或多方的交流,通信协议就是规定双方或多方需要共同遵守的交流方式。比如现在规定两个人需要用数字来代表文字,目前只定义了1表示“我”,2表示“你”,3表示“他”,然后现在两个人中有个人说了1,另一个立即就知道说的是“我”,但假如有个人不按规定来说话,说了一个4,那另一个人就不明白他在说什么了。这个例子里面,定义的1、2、3的表示,就是一份简单的通信协议。一般的通信协议,如TCP/IP、蓝牙协议等比Modbus复杂,但万变不离其宗。

Modbus

概述

        接下来就是重头戏了,Modbus通信协议。

        Modbus是一种串行通信协议,是Modicon公司(现在的施耐德电气 Schneider Electric)于1979年为使用可编程逻辑控制器(PLC)通信而发表。Modbus已经成为工业领域通信协议的业界标准(De facto),并且现在是工业电子设备之间常用的连接方式。--摘自百度百科

        Modbus是一种一主一从的一对一通信方式(主机发一帧,从机回一帧的形式),当然也一主多从,但实际也是一对一通信,同一时刻只能有一个从机进行响应。如果需要和多个从机同时通信,这里也支持使用广播,即主机发送指令,所有从机接收指令并执行,但不进行应答。可以参考国际标准(以下简称国标),GBT 19582-2。

        当进行一主多从通信时,主机通过从机ID号来区分要通信的从机设备。从机ID范围为1~247,0为广播地址,248~255为用户自定义地址。

通信形式

        目前总共有4种通信形式,RTU、ASCII、TCP、Plus。

RTU:

        RTU是一种远程终端控制系统,这里指的是Modbus的一种通信形式。一般是基于串口进行通信。其报文格式是十六进制的,由Slave ID+数据+CRC校验三部分组成。数据部分详见上面报文解析。剩下的就是数据校验了,这里用的是CRC校验(循环冗余校验,Cyclic Redundancy Check,简称CRC)。要注意的是,数据部分高位数据在前,低位数据在后,而CRC校验则是低位在前,高位在后

名称

从机ID

数据部分

CRC低位

CRC高位

长度 1字节 n字节 1字节 1字节

CRC校验

        CRC(Cyclic Redundancy Checksum)是一种纠错技术,代表循环冗余校验和。CRC的计算原理这里就不讲了,直接上代码。

C语言实现--摘自FreeModbus里的实现

static const UCHAR aucCRCHi[] = {
    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, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
    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, 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,
    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
};

static const UCHAR aucCRCLo[] = {
    0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7,
    0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E,
    0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9,
    0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC,
    0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
    0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32,
    0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D,
    0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38,
    0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF,
    0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
    0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1,
    0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4,
    0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB,
    0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA,
    0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
    0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0,
    0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97,
    0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E,
    0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89,
    0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
    0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83,
    0x41, 0x81, 0x80, 0x40
};

USHORT
usMBCRC16( UCHAR * pucFrame, USHORT usLen )
{
    UCHAR           ucCRCHi = 0xFF;
    UCHAR           ucCRCLo = 0xFF;
    int             iIndex;

    while( usLen-- )
    {
        iIndex = ucCRCLo ^ *( pucFrame++ );
        ucCRCLo = ( UCHAR )( ucCRCHi ^ aucCRCHi[iIndex] );
        ucCRCHi = aucCRCLo[iIndex];
    }
    return ( USHORT )( ucCRCHi << 8 | ucCRCLo );
}

帧的完整性判断

        国标里规定:一帧中两个字符之间间隔时间不超过1.5字符,以大于3.5字符间隔时间作为一帧的结束,波特率高于19200时,间隔时间固定为1.7ms。

        比如现在串口设置的波特率为9600,1个停止位,无校验位,8个数据位(这种设置以下会简写成9600-1N8的形式),那么代入如下公式:

可以算出,此设置下,断帧时间应该为3.645833……ms。

另外国标还规定,串口配置中,默认应该配置有偶校验。

tips:在9600-1N8的串口设置下,可以快速估算数据发送时间,即1个字节发送时间为1ms。

ASCII:

        ASCII是一种字符型的通信方式,一般也是基于串口进行通信。其报文格式是以ASCII码编码的,由帧头(:)+Slave ID+数据+LRC校验+帧尾(/r/n)五部分组成,其中Slave ID、数据部分跟RTU完全一样,只不过是以ASCII编码形式,如Slave ID,RTU是01一个字节的时候,ASCII表示就是30 31两个字节。所以实际工业应用场合很少会用到Modbus/ASCII,因为通信效率太低。

        另外跟RTU还有个不同的地方,就是这里使用的校验不是CRC校验,而是LRC校验。

LRC校验

        纵向冗余校验,Longitudinal Redundancy Check,简称:LRC。

LRC具体算法如下:

1、对需要校验的数据(2n个字符)两两组成一个16进制的数值求和。

2、将求和结果与256求模。

3、用256减去所得模值得到校验结果(另一种方法:将模值按位取反然后加1)。

C语言实现--摘自FreeModbus里的实现

static          UCHAR
prvucMBLRC( UCHAR * pucFrame, USHORT usLen )
{
    UCHAR           ucLRC = 0;  /* LRC char initialized */

    while( usLen-- )
    {
        ucLRC += *pucFrame++;   /* Add buffer byte without carry */
    }

    /* Return twos complement */
    ucLRC = ( UCHAR ) ( -( ( CHAR ) ucLRC ) );
    return ucLRC;
}

        因为整个报文都是ASCII的编码形式,所以如果使用单片机串口通信,可以把串口的数据位设置为7位(ASCII的码表范围就是0~0x7F,只用低7位)。

名称

帧头:

从机ID

数据部分

LRC

帧尾/r

帧尾/n

长度 1字节 2字节 n字节 2字节 1字节 1字节

TCP:

        TCP是一种网络协议,而Modbus/TCP就是基于网络协议上的一种应用层协议。其报文格式是十六进制的,由报头(2字节的帧序号+2字节的协议类型+2字节的数据长度+1字节的Slave ID)+数据两部分组成。由于该通信方式是基于TCP/IP这种可靠协议上,所以通信不需要有额外的校验机制。

名称

帧序号

协议类型

数据长度

从机ID

数据部分

长度

2字节

2字节

2字节

1字节

n字节

Plus:

        Modbus Plus(又称MB+)是一种高速现场总线网络,也是一种典型的令牌总线网。

报文解析

Modbus功能码表如下:

功能码

名称

作用

01

读取线圈状态

取得一组逻辑线圈的当前状态(ON/OFF)

02

读取输入状态

取得一组开关输入的当前状态(ON/OFF)

03

读取保持寄存器

在一个或多个保持寄存器中取得当前的二进制值

04

读取输入寄存器

在一个或多个输入寄存器中取得当前的二进制值

05

强置单线圈

强置一个逻辑线圈的通断状态

06

预置单寄存器

把具体二进值装入一个保持寄存器

07

读取异常状态

取得8个内部线圈的通断状态,这8个线圈的地址由控制器决定,用户逻辑可以将这些线圈定义,以说明从机状态,短报文适宜于迅速读取状态

08

回送诊断校验

把诊断校验报文送从机,以对通信处理进行评鉴

09

编程(只用于484)

使主机模拟编程器作用,修改PC从机逻辑

10

控询(只用于484)

可使主机与一台正在执行长程序任务从机通信,探询该从机是否已完成其操作任务,仅在含有功能码9的报文发送后,本功能码才发送

11

读取事件计数

可使主机发出单询问,并随即判定操作是否成功,尤其是该命令或其他应答产生通信错误时

12

读取通信事件记录

可是主机检索每台从机的ModBus事务处理通信事件记录。如果某项事务处理完成,记录会给出有关错误

13

编程(184/384 484 584)

可使主机模拟编程器功能修改PC从机逻辑

14

探询(184/384 484 584)

可使主机与正在执行任务的从机通信,定期控询该从机是否已完成其程序操作,仅在含有功能13的报文发送后,本功能码才得发送

15

强置多线圈

强置一串连续逻辑线圈的通断

16

预置多寄存器

把具体的二进制值装入一串连续的保持寄存器

17

报告从机标识

可使主机判断编址从机的类型及该从机运行指示灯的状态

18

(884和MICRO 84)

可使主机模拟编程功能,修改PC状态逻辑

19

重置通信链路

发生非可修改错误后,是从机复位于已知状态,可重置顺序字节

20

读取通用参数(584L)

显示扩展存储器文件中的数据信息

21

写入通用参数(584L)

把通用参数写入扩展存储文件,或修改之

22~64

保留作扩展功能备用

65~72

保留以备用户功能所用

留作用户功能的扩展编码

73~119

非法功能

120~127

保留

留作内部作用

128~255

保留

用于异常应答

        其中常用的功能码有8个(01、02、03、04、05、06、15、16),可以分为位操作和字操作两类,其中,位操作的是线圈和离散输入,两者区别在于,线圈是可读可写的,而离散输入是只读。字操作的是保持寄存器和输入寄存器,两者区别在于,保持寄存器是可读可写的,而输入寄存器是只读的。

线圈

读线圈功能码01,可读单个或多个

主节点发送帧格式(其中MSB为高字节,LSB为低字节)

序号 0 1 2 3 4 5 6 7
字段定义 ADDR CMD MSB LSB MSB LSB LSB MSB
解释 从节点地址 命令类型 线圈起始地址 线圈个数 CRC校验

从节点正常应答帧格式:

序号 0 1 2 3 4 L+2 L+3 L+4
字段定义 ADDR CMD Length Data1 Data2 DataN LSB MSB
解释 从节点地址 命令类型 发送字节数L=n/8+(1) 第一个字节数据值 第二个字节数据值 第N个字节数据值 CRC校验

字节数据值的定义:

7 6 5 4 3 2 1 0
线圈值 首地址+7 首地址+6 首地址+5 首地址+4 首地址+3 首地址+2 首地址+1 首地址

注:数据从低位开始填充,填充完成后还有剩余的高位则全部补0。

从节点异常应答格式:

序号 0 1 2 3 4
字段定义 ADDR CMD+128 ErrCode LSB MSB
解释 从节点地址 命令类型+128 错误码 CRC校验

写单个线圈功能码05

主节点发送帧格式:

序号 0 1 2 3 4 5 6 7
字段定义 ADDR CMD MSB LSB MSB LSB LSB MSB
解释 从节点地址 命令类型 线圈地址 数据 CRC校验

注:数据为0xFF00表示设置线圈状态为ON,数据为0x0000表示设置线圈状态为OFF。

从节点正常应答帧格式(同发送帧):

序号 0 1 2 3 4 5 6 7
字段定义 ADDR CMD MSB LSB MSB LSB LSB MSB
解释 从节点地址 命令类型 线圈地址 数据 CRC校验

从节点异常应答格式:

序号 0 1 2 3 4
字段定义 ADDR CMD+128 ErrCode LSB MSB
解释 从节点地址 命令类型+128 错误码 CRC校验

写多个线圈功能码15

主节点发送帧格式:

序号 0 1 2 3 4 5 6 7 L+6 L+7 L+8
字段定义 ADDR CMD MSB LSB MSB LSB Length Data1 DataN LSB MSB
解释 从节点地址 命令类型 起始线圈地址 线圈数n 发送字节数L=n/8+(1) 第一个字节数据值 第N个字节数据值 CRC校验

字节数据值的定义:

7 6 5 4 3 2 1 0
线圈值 首地址+7 首地址+6 首地址+5 首地址+4 首地址+3 首地址+2 首地址+1 首地址

注:数据从低位开始填充,填充完成后还有剩余的高位则全部补0。

从节点正常应答帧格式:

序号 0 1 2 3 4 5 6 7
字段定义 ADDR CMD MSB LSB MSB LSB LSB MSB
解释 从节点地址 命令类型 线圈起始地址 线圈个数 CRC校验

从节点异常应答格式:

序号 0 1 2 3 4
字段定义 ADDR CMD+128 ErrCode LSB MSB
解释 从节点地址 命令类型+128 错误码 CRC校验

保持寄存器

读保持寄存器功能码03,可读单个或多个

主节点发送帧格式:

序号 0 1 2 3 4 5 6 7
字段定义 ADDR CMD MSB LSB MSB LSB LSB MSB
解释 从节点地址 命令类型 寄存器起始地址 寄存器个数 CRC校验

从节点正常应答帧格式:

序号 0 1 2 3 4 L+1 L+2 L+3 L+4
字段定义 ADDR CMD Length MSB LSB MSB LSB LSB MSB
解释 从节点地址 命令类型 发送字节数L=n*2 第一个寄存器值 第N个寄存器值 CRC校验

从节点异常应答格式:

序号 0 1 2 3 4
字段定义 ADDR CMD+128 ErrCode LSB MSB
解释 从节点地址 命令类型+128 错误码 CRC校验

写单个保持寄存器功能码06

主节点发送帧格式:

序号 0 1 2 3 4 5 6 7
字段定义 ADDR CMD MSB LSB MSB LSB LSB MSB
解释 从节点地址 命令类型 寄存器地址 数据 CRC校验

从节点正常应答帧格式(同发送帧):

序号 0 1 2 3 4 5 6 7
字段定义 ADDR CMD MSB LSB MSB LSB LSB MSB
解释 从节点地址 命令类型 寄存器地址 数据 CRC校验

从节点异常应答格式:

序号 0 1 2 3 4
字段定义 ADDR CMD+128 ErrCode LSB MSB
解释 从节点地址 命令类型+128 错误码 CRC校验

写多个保持寄存器功能码16

主节点发送帧格式:

序号 0 1 2 3 4 5 6 7 8 L+5 L+6 L+7 L+8
字段定义 ADDR CMD MSB LSB MSB LSB Length MSB LSB MSB LSB LSB MSB
解释 从节点地址 命令类型 起始寄存器地址 寄存器数n 发送字节数L=n*2 第一个寄存器值 第N个寄存器值 CRC校验

从节点正常应答帧格式:

序号 0 1 2 3 4 5 6 7
字段定义 ADDR CMD MSB LSB MSB LSB LSB MSB
解释 从节点地址 命令类型 寄存器起始地址 寄存器个数 CRC校验

从节点异常应答格式:

序号 0 1 2 3 4
字段定义 ADDR CMD+128 ErrCode LSB MSB
解释 从节点地址 命令类型+128 错误码 CRC校验

离散输入--只读

读离散输入的功能码02,可读单个或多个(协议格式跟01功能码完全一致)

主节点发送帧格式:

序号 0 1 2 3 4 5 6 7
字段定义 ADDR CMD MSB LSB MSB LSB LSB MSB
解释 从节点地址 命令类型 线圈起始地址 线圈个数 CRC校验

从节点正常应答帧格式:

序号 0 1 2 3 4 L+2 L+3 L+4
字段定义 ADDR CMD Length Data1 Data2 DataN LSB MSB
解释 从节点地址 命令类型 发送字节数L=n/8+(1) 第一个字节数据值 第二个字节数据值 第N个字节数据值 CRC校验

字节数据值的定义:

7 6 5 4 3 2 1 0
线圈值 首地址+7 首地址+6 首地址+5 首地址+4 首地址+3 首地址+2 首地址+1 首地址

注:数据从低位开始填充,填充完成后还有剩余的高位则全部补0。

从节点异常应答格式:

序号 0 1 2 3 4
字段定义 ADDR CMD+128 ErrCode LSB MSB
解释 从节点地址 命令类型+128 错误码 CRC校验

输入寄存器--只读

读输入寄存器的功能码04,可读单个或多个(协议格式跟03功能码完全一致)

主节点发送帧格式:

序号 0 1 2 3 4 5 6 7
字段定义 ADDR CMD MSB LSB MSB LSB LSB MSB
解释 从节点地址 命令类型 寄存器起始地址 寄存器个数 CRC校验

从节点正常应答帧格式:

序号 0 1 2 3 4 L+1 L+2 L+3 L+4
字段定义 ADDR CMD Length MSB LSB MSB LSB LSB MSB
解释 从节点地址 命令类型 发送字节数L=n*2 第一个寄存器值 第N个寄存器值 CRC校验

从节点异常应答格式:

序号 0 1 2 3 4
字段定义 ADDR CMD+128 ErrCode LSB MSB
解释 从节点地址 命令类型+128 错误码 CRC校验

错误码定义

功能码 说明
01 非法功能。对于服务器(或从站)来说,询问中接收到的功能码是不可允许的操作,可能是因为功能码仅适用于新设备而被选单元中不可实现同时,还指出服务器(或从站)在错误状态中处理这种请求,例如:它是未配置的,且要求返回寄存器值。
02 非法数据地址。对于服务器(或从站)来说,询问中接收的数据地址是不可允许的地址,特别是参考号和传输长度的组合是无效的。对于带有100个寄存器的控制器来说,偏移量96和长度4的请求会成功,而偏移量96和长度5的请求将产生异常码02。
03 非法数据值。对于服务器(或从站)来说,询问中包括的值是不可允许的值。该值指示了组合请求剩余结构中的故障。例如:隐含长度是不正确的。modbus协议不知道任何特殊寄存器的任何特殊值的重要意义,寄存器中被提交存储的数据项有一个应用程序期望之外的值。
04 从站设备故障。当服务器(或从站)正在设法执行请求的操作时,产生不可重新获得的差错。
05 确认。与编程命令一起使用,服务器(或从站)已经接受请求,并且正在处理这个请求,但是需要长持续时间进行这些操作,返回这个响应防止在客户机(或主站)中发生超时错误,客户机(或主机)可以继续发送轮询程序完成报文来确认是否完成处理。
06 从属设备忙。与编程命令一起使用。服务器(或从站)正在处理长持续时间的程序命令。当服务器(或从站)空闲时,用户(或主站)应该稍后重新传输报文。
08 存储奇偶差错。与功能码20和21以及参考类型6一起使用,指示扩展文件区不能通过一致性校验。服务器(或从站)设法读取记录文件,但是在存储器中发现一个奇偶校验错误。客户机(或主方)可以重新发送请求,但可以在服务器(或从站)设备上要求服务。
10 不可用网关路径。与网关一起使用,指示网关不能为处理请求分配输入端口至输出端口的内部通信路径。通常意味着网关是错误配置的或过载的。
11 网关目标设备响应失败。与网关一起使用,指示没有从目标设备中获得响应。通常意味着设备未在网络中。

Modbus的PLC应用

        这是一种基于标准Modbus上的PLC应用协议,目前貌似没有一个绝对的标准,不同的PLC厂家的自己不同的定义,但实际通信链路层走的协议还是按标准的Modbus协议来的。比如某PLC厂家规定,读取保持寄存器的地址是40001~49999,当PLC读取40001地址时,实际链路层读的是0000地址(就是在标准的基准地址前加上一个分段标识符,并且地址偏移1)。这个就不细讲了,具体用到哪家的PLC再去查看对应的资料。

C语言源码推荐

        Free Modbus,Github上链接https://github.com/armink/FreeModbus_Slave-Master-RTT-STM32,目前更新至V1.6。原本只有从机是开源的,主机收费,但这个作者自己写了主机部分,实现了带操作系统和裸机的接口。其设计架构也是根据操作的寄存器类型和通信方式进行区分。如从机的保持寄存器的实现,则封装在HoldingReg_S文件中,RTU的通信形式,则在mb_rtu中。架构清晰明了,使用起来也很简单,只需要实现mbport.h里的几个接口就行。

        事件类:xMBPortEventInit、xMBPortEventPost、xMBPortEventGet。

        串口类:xMBPortSerialInit、vMBPortClose、xMBPortSerialClose、vMBPortSerialEnable、xMBPortSerialGetByte、xMBPortSerialPutByte。

        定时类:xMBPortTimersInit、xMBPortTimersClose、vMBPortTimersEnable、vMBPortTimersDisable。

        状态回调:pxMBFrameCBByteReceived、pxMBFrameCBTransmitterEmpty、pxMBPortCBTimerExpired。

        具体实现就不讲了,github上有例程,如果有需要的小伙伴可以留下言,这边可以单独出一篇详细解析FreeModbus。

常用调试工具

        Modbus Poll,可模拟主机,跟从机进行通信。

        Modbus Slave,可模拟从机,响应主机的通信。

        Modbus Scan,更偏向于PLC的操作界面,个人觉得没有Modbus Poll灵活。

        串口调试助手,可以完全从指令层面上进行深入了解,而且也没有上述工具一些问题(因为一般集成的功能越多,问题可能越多),但有个缺点就是如果作为从机应答,没办法做到及时响应主机的指令。

相关知识

        串口通信协议、TCP/IP、RS485/RS232/RS422/ttl、STM32单片机、Github、CRC校验、LRC校验

你可能感兴趣的:(知识分享,#,Modbus,网络协议,c语言)