Modbus协议详解
通信协议是指双方实体完成通信或服务所必须遵循的规则和约定,例如我们为实现人与人之间的交流需要约定统一的语言,统一的文字,规定语速等。
而对于设备之间,协议定义了数据单元使用的格式(例如大端小端模式,编码解码方式,加密解密规则),信息单元应该包含的信息与含义,连接方式,信息发送和接收的时序,从而确保数据顺利地传送到对方。
1.前言
Modbus属于串行通信协议,是Modicon(即现在的施耐德)公司于1979年为可编程逻辑控制器PLC通信而发表,所以现在知道为什么以Mod开头了吧,目前不仅PLC使用,实际已经成为工业领域通信协议的标准,是现代工业电子设备之间常用的连接方式。Modbus公开发表且无版权要求,易于部署和维护。
下面我们来了解一下Modbus协议相关的细节,以及Modbus协议的应用方式。
并行通信:数据各位同时传送。
串行通信:数据一位一位顺序依次发送。
2.物理接口
Modbus协议属于应用层协议,协议本身并没有像SPI,I2C,TCP/IP定义物理层,只定义了数据包组织结构和内容的公共格式。所以它没有自己专属的数据链路层和物理层,所以需要依赖其他可用的物理层和数据链路层来传送数据。
例如可以选用串口(例如RS232,RS485和RS422),也可以选择以太网口,但目前大多数应用都是通过串口RS-485作为物理层,如下图:
实际上不仅限于以上物理接口,由于Modbus属于应用层协议所以协议本身不管数据是经过何种网络进行通信。所以还能以USB,蓝牙,WIFI等其他一切能够串行传送数据的总线作为物理层和数据链路层传送数据。
标准的Modbus口是使用RS-232C兼容串行接口,该接口定义了连接口的针脚,电缆,信号位,传输波特率,奇偶校验,这样控制器能直接经由Modem组网。
3.协议版本
Modbus协议目前分别定义了基于串口和以太网传输数据的规则,其中串口规则两种Modbus RTU和Modbus ASCII,以太网规则一种Modbus TCP/IP。
其中Modbus RTU和Modbus ASCII协议应用于串口链接(RS232,RS485,RS422,光纤,无线),Modbus TCP/IP协议应用于以太网链接。
Modbus还有一个扩展版本Modbus PLUS(也叫Modbus+或者MB+),不过此协议是Modicon公司专有的,和Modbus不同。它需要一个专门的协议处理器来处理类似HDLC的高速令牌旋转。
我们不使用MB+所以不去深入了解,我们只要了解Modbus-TCP/IP,Modbus-RTU,Modbus-ASCII这三种模式即可。
4.工作模式
Modbus协议是属于主从(Master/Slave)通信方式的协议。在所有节点中其中一个位Master节点,其余为Slave节点,在整个通信网络中Master节点至少且只有1个。
主从通信以请求/应答为主,每次通讯都是主站先发送指令(可以是广播指令,或是向特定从站的单播指令),从站响应指令,并按要求应答,或者报告异常。当主站不发送请求时,从站不会自己发出数据,从站和从站之间不能直接通讯(也就是说从站之间不能相互发送指令或访问)。
无论主站发送的是广播指令还是单播指令,实际上所有从设备都会完整接收命令。但单播指令只有指令中指明的编号/地址与设备编号/地址相同时设备才会执行及回应指令,编号/地址与指令指明的编号/地址不同的从机将接收到的所有内容丢弃,而广播指令所有收到指令的设备都会执行指令,但不会给主机回应指令。
半双工通信
Modbus由于请求/应答机制所以不能同步通信(同步通信需要收发双方以相同的节奏发送和接收数据),总线上每次只有一帧数据进行传输,属于半双工通信。
Modbus没有支持忙机制处理,例如主机给从机发送命令,如果从机正在处理其他业务,此时从机将无法响应主机,所以需要通过软件的方法来判断是否正常接收。
5.协议概述
1)控制器通信使用主/从模式,即仅主设备能发送查询和操作命令,其他从设备根据主设备查询/操作命令作出相应反应。
2)主设备可单独和从设备通信,也能以广播方式和所有从设备通信。如果单独通信,从设备必须返回一消息作为给主机的回应,如果是以广播方式查询的,则从机不作任何回应。
3)Modbus协议建立了主设备查询的协议格式,内容包含:从设备(或广播)地址,功能代码,所有要发送的数据,错误检测域(协议校验码)。
4)从设备回应消息也要遵循Modbus协议,内容包括确认要行动的域,任何要返回的数据,和错误检测域(协议校验码)。如果在消息接收过程中发生一错误,或从设备不能执行其命令,从设备需要生成一条错误消息并把它作为回应发送给主设备,主设备接收后就知道从设备发生了错误,以及可知道发生了何种错误。
6.报文格式
Modbus协议的报文(或帧)的基本组成格式是:
协议头(Header)+功能码(Function Code)+数据区(Data)+校验码(Checksum)
功能码(Function Code)和数据区(Data)在不同类型的网络都是固定不变的。
协议头(Header)和校验码(Checkesum)则因网络底层的实现方式不同而有所区别。协议头包含了从站的地址,功能码告诉从站要执行何种功能,数据区是具体的数据内容。
报文定义了一个与物理层无关的协议数据单元(简称PDU),PDU协议字段组成如下:
PDU=功能码(Function Code)+数据域(Data)(功能码1byte,数据域不确定)
对于不同类型的网络,Modbus的协议层实现是一样的,区别在于下层的实现方式,常见的有TCP/IP和串行通讯两种。
如上图所示,Modbus串行传输的物理层是RS-485/422或RS-232,数据链路层是Modbus的串行传输协议。
Modbus TCP传输的1-4层和我们日常使用的以太网,因特网一样,分别为物理层,数据链路层,网络层,传输层。同时标准规定Modbus-TCP默认采用的TCP端口号是502。
6.1 Modbus-TCP/IP
在Modbus TCP模式下,主站被成为客户端(Client),从站被称为服务器(Server)。因为Modbus主站总是向Modbus从站查询操作数据,这和客户端与服务器的运作模式是相同的。
6.1.1 数据帧
对于TCP/IP通信Modbus协议引入一些附加域映射成应用数据单元(ADU)而ADU可分为MBAP和PDU两部分,这两部分如下关系:
PDU=功能码(Function Code)+数据域(Data)(功能码1byte,数据长度不确定)。
ADU=MBAP+PDU
数据帧组成如下图:
其中MBAP(报文头)占7Bytes,功能码1Byte,数据长度不确定,由具体功能决定,如下所示:
事务处理标识
协议标识
长度
单元标识符
2 Byte
2 Byte
2 Byte
1 Byte
事务处理标识,可以理解为报文的序列号,一般每次通信之后就要加1以区别不同的通信数据报文。
协议标识符:例如00 00表示的是Modbus-TCP/IP协议。
长度,表示接下来的数据长度,单位为字节。
单元标识符,可以理解为设备地址。
将ADU报文详细展开后如下图:
6.1.2 数据帧变化
1)取消了校验位,TCP/IP协议的数据链路层上就进行了CRC-32的校验,同时TCP/IP是面向连接的可靠性的协议,因此应用层没必要再加上校验位。
2)Slave地址变成了单元标识符,当网络中的设备都使用了TCP/IP协议,该地址是没有意义的,因为使用IP地址就能进行路由寻址,如果网络里还有串行通讯的设备,则需要使用网关来实现Modbus-TCP/IP到Modbus-RTU/ASCII之间的协议切换,这时用Unit Identifier来标识网关后面的每个串行通讯设备。
3)长度是指后面的字节总数,实际上数据区的长度是能确定的,有的功能码就可以确定数据区的长度(例如读寄存器功能码),有的功能码虽然不能确定数据区长度,但是Modbus协议数据区包含有字节计数。表头增加的长度是为了应对有些情况下TCP/IP协议会将应用层的数据拆包传输的场景。
4)事物处理标识符和协议标识符由Client生成,Server的响应将复制这些参数。
6.1.3 端口号
IANA(Internet Assigned Numbers Authority)互联网编号分配管理机构给Modbus协议赋予TCP端口号为502,这是目前Modbus在仪表与自动化行业中唯一分配到的端口号。
6.1.4 通信流程
在使用TCP/IP通信时,主站为Client端,主动与服务器建立连接。从站为Server端,等待Client连接。
注意,在使用Modbus通信之前需要先建立TCP连接,通信任务结束时,需要关闭TCP连接,这和PC客户端连接服务器的规则是相同的。
6.2 Modbus-串行
Modbus使用异步串行链路传输时,可以选择传输模式ASCII或RTU两种中的任意一种。使用异步串行传输需要确保每个设备的串口通信硬件配置参数(波特率,校验方式等)是相同的。对于Modbus串行来说通常单帧最大能够传输的字节数为256字节。如果设备支持更多的数据,则可调整Modbus单帧最大字节数的值(这需要主从双方进行约定)。
6.2.1 Modbus-ASCII
在Modbus ASCII模式下,主站是运行Master协议程序,从站是运行Stave协议程序。
起始位
设备地址
功能代码
数据数量
数据
LRC 高字节
LRC 低字节
结束符
:
2 Bytes
2 Bytes
N
N Bytes
1 Byte
1 Byte
CR, LF(回车换行)
Modbus以ASCII模式通信,在消息中的每个8Bit字节都作为两个ASCII字符发送,也就是说使用两个字符来表示1个字节的十六进制值,可见所见即所得。
例如表示十六进制35需要使用3和5这两个字符,而每个字符都占用1个字节,字符3实际值为十六进制33,字符5实际值为35,所以为了表示30需要使用16Bits数据,所以实际发送的数据为00110011,00110101,但这并不是最终使用的数据,接收端需要将其转换为原始值才能够使用。
ASCII帧间隔
使用ASCII模式,消息以:冒号字符(ASCII码3A)开始,以回车换行符结束ASCII码0D,0A。
其它域可以用于表达十六进制的字符是0-9,A-F。网络上的设备不断侦测:冒号字符,当每个节点设备接收到一个:冒号时,每个设备都解码下个域(设备地址域)来判断是否发给自己的。
消息中字符间发送的时间间隔最长不能超过1秒,否则接收的设备将认为传输错误,一个典型的ASCII消息帧如下所示:
起始位
设备地址
功能代码
数据
LRC 校验
结束符
1 个字符
2 个字符
2 个字符
n 个字符
2 个字符
2 个字符
6.2.2 Modbus-RTU
在Modbus RTU模式下,主站是运行Master协议程序,从站是运行Stave协议程序。
起始位
设备地址
功能代码
数据数量
数据
CRC 低字节
CRC 高字节
结束符
无
1 Byte
1 Byte
N
N Bytes
1 Byte
1 Byte
无
Modbus以RTU模式通信,其发送的字节数据即为原始字节数据,接收端接收后无需再次转换。
例如,要传输十六进制35,实际发送的数据就为原始值0011,0101。
RTU帧间隔
使用RTU模式,消息发送至少要以3.5个字符时间的停顿间隔开始。在网络波特率下多样的字符时间,这是最容易实现的(如下图的T1-T2-T3-T4所示)。传输的第一个域是设备地址,可以使用的传输字符是十六进制的0-9,A-F。网络设备不断侦测网络总线,包括停顿间隔时间内。当第一个域(地址域)接收到,每个设备都进行解码以判断是否发往自己的。在最后一个传输字符之后,一个至少3.5个字符时间的停顿标定了消息的结束。一个新的消息可在此停顿后开始。
整个消息帧必须作为一连续的传输流。如果在帧完成之前有超过1.5个字符时间的停顿时间,接收设备将刷新不完整的消息并假定下一字节是一个新消息的地址域。同样地,如果一个新消息在小于3.5个字符时间内接着前个消息开始,接收的设备将认为它是前一消息的延续。这将导致一个错误,因为在最后的CRC域的值不可能是正确的,一典型的RTU消息帧如下所示:
起始位
设备地址
功能代码
数据
CRC 校验
结束符
T1-T2-T3-T4
1 Byte
1 Byte
N Bytes
2 Bytes
T1-T2-T3-T4
字符时间
所谓字符传输时间指的是传输一个ASCII字符需要花费的时间,一个ASCII字符包含1个字节(8bits),所以传输一个字符需要花费传输8个数据位的时间(所以这里字符传输时间并不是真的字面意思传输字符,而是代指传输字节)。
然而实际上传输1个字节数据需要花费的时间并不只8个位时间,因为除了传输固有的1字节数据,还需要传输一些辅助功能位。例如发送1个字节需要固定起始位1位,数据位8位,校验位1位(可选的),停止位1位,其中8位数据位才是真正的有效数据,所以有如下公式来计算字符时间:
字符时间=波特率1s×字符总位数。
例如:固定起始位1位,数据位8位,奇/偶校验位1位,停止位1位,波特率为9600bps,计算单个字符传输时间为:
字符时间=96001000ms×(1+8+1+1)=1.145833ms
7.帧字段分析
7.1 地址域
消息帧的地址域可能的从设备地址是0-247.单个设备的地址范围是1-247。
主设备通过将要联络的从设备的地址放入消息中的地址域来选从设备。当从设备发送回应消息时,需要把自己的地址让入回应的地址域中,以便主设备知道是哪一个设备作出的回应。
地址0被用作广播地址,以便所有的从设备都能认识。当Modbus协议用于更高水准的网络,广播可能不允许或以其它方式代替。
7.2 功能域
消息帧中的功能代码域可能的代码范围是十进制的1-255.当然,有些代码是适用于所有控制器,有些是应用于某种控制器,还有些保留以备后用。
当消息从主设备发往从设备时,功能代码域将告知从设备需要执行哪些行为。例如去读取输入的开关状态,读一组寄存器的数据内容,读从设备的诊断状态,允许调入、记录、校验在从设备中的程序等。
当从设备回应时,它使用功能代码域来指示是正常回应(无误)还是有某种错误发生(称作异议回应)。对正常回应,从设备仅回应相应的功能代码。对异议回应,从设备返回以等同于正常代码的代码,但最重要的位(功能码最高位)置为逻辑1。
例如:一从主设备发往从设备的消息要求读一组保持寄存器,将产生如下功能代码:0000,0011(十六进制03)对正常回应,从设备仅回应同样的功能代码。对异议回应,它返回:1000,0011(十六进制83)。
除功能代码因异议错误作了修改外,从设备将一独特的代码放到回应消息的数据域中,这能告诉主设备发生了什么错误。
主设备应用程序得到异议的回应后,将重发消息或者诊断发给从设备的消息并报告给设备管理员。
7.3 数据域
数据域是由两个十六进制数集合构成的,每个字节数据取值范围00-FF。
从主设备发给从设备消息的数据域包含附加的信息:从设备必须用于进行执行由功能代码所定义的所为。这包括了像不连续的寄存器地址,要处理项的数目,域中实际数据字节数。
例子
如果主设备需要从设备读取一组保持寄存器(功能代码03),数据域指定了起始寄存器以及要读的寄存器数量(这里不等同于字节数量)。
如果主设备写一组从设备的寄存器(功能代码10),数据域则指明了要写的起始寄存器以及要写的寄存器数量(这里不等同于字节数量),数据域的数据字节数,要写入寄存器的数据。
如果没有错误发生,从从设备返回的数据域包含请求的数据。如果有错误发生,此域包含一条异议代码,主设备应用程序可以用异议代码来判断发生的问题以采取下一步行动。
在某种消息中数据域可以是不存在的(即0长度)。例如,主设备要求从设备回应通信事件记录(功能代码0B),从设备不需主设备提供任何附加的信息。
7.4 错误检测域
标准的Modbus网络有两种错误检测方法,错误检测域的内容视所选的检测方法而定。
ASCII模式,错误检测域包含两个ASCII字符。这是使用LRC(纵向冗长检测)方法对消息内容计算得出的,不包括开始的冒号符及回车换行符。LRC字符附加在回车换行符前面。
RTU模式,错误检测域包含16Bits值,并被拆分为两个字节,分为高字节和低字节。错误检测域的内容是通过对消息内容进行循环冗长检测方法得出的。CRC域附加在消息的最后,添加时先是低字节然后是高字节。故CRC的高位字节是发生消息的最后一个字节。
7.4.1 LRC校验
LRC域检测了消息域中除开始的冒号及结束的回车换行号外的内容,LRC域是一个包含一个8位二进制的字节。LRC值由传输设备来计算并放到消息帧中,接收设备在接收消息的过程中计算LRC,并与接收到报文中的LRC值比较,如果两值不等,说明有错误。
LRC方法是将消息中的8Bit的字节连续累加,累加时丢弃累加进位。
unsigned char LRC(unsigned char * auchMsg, unsigned short dataLen)
{
unsigned char authLRC = 0; /* LRC 字节初始化 */
while (dataLen--) { /* 传送消息 */
authLRC += (*auchMsg)++; /* 累加 */
}
return ((unsigned char)(-authLRC));
}
7.4.2 CRC校验
CRC域检测了整个消息的内容,CRC域是两个字节,包含一个16位的二进制值。它由传输设备计算后加入到消息中。接收设备重新计算收到消息的CRC,并与接收到报文中的CRC域中的值比较,如果两值不同,则有误。
CRC添加到消息中时,低字节先加入报文,然后高字节。
8、数据模型
Modbus协议最开始被用于PLC通信中,为了抽象PLC中可访问的数据Modbus协议定义了数据模型概念,数据模型定义了四种可访问的数据类型,分别是:
1)输出线圈(Coils),大小只有1Bit,属于开关量,数值范围ON或OFF,权限是可读可写,既可以是一个输出量输出点,也可以是数字量输入点。
2)输入离散量(Discrete Input),属于离散量,大小只有1Bit,数值范围ON或OFF,权限是只读,及数字量输出点。
3)输入寄存器(Input Registers),16Bit的寄存器,权限是只读,可以用作模拟量或16位打包输入点。
4)保持寄存器(Holding Registers),16Bit的寄存器,权限是可读可写,既可以是一个模拟量或16位打包输入点,也可以是模拟量或16位打包输出点。
实际上以上的数据类型都属于可编程逻辑控制器(PLC)中的术语,所以表述上总是不符合我们的直觉,搞得我们使用MCU单片机思维难以理解。
9、数据地址模型
数据模型是一种抽象,在实际使用时必须将其映射到真实的物理存储区才能被访问。
Modbus协议允许设备将四种数据分别映射到不同的存储区块中,各个区块之间相互独立,使用不同的功能码可读取到不同的数值,如下图所示:
数据模型中的每一种数据类型都最多允许有65536个元素,元素编号从1开始,因此元素编号范围为1-65536.
注意:65536是每种数据类型允许的元素最大个数,但并不要求我们全部实现,Modbus协议允许设备根据自己的实际情况实现部分元素,甚至不要求实现模型中全部四种数据(比如只实现保持寄存器类型数据的读写)。
为了简化数据模型与设备存储区的对应关系,引入了一种地址模型。该模型将存储区按大块划分,并且将不同的大块用于存储不同的数据类型,并给每个大块分配编号(这样也相当于给数据类型编号),如下:
线圈(Coils),Modbus地址编号0。
离散输入(Discrete Input)Modbus地址编号1。
输入寄存器(Input Registers)Modbus地址编号3。
保持寄存器(Holding Registers)Modbus地址编号4。
现在将每种数据类型的元素编号(1-65536)与地址编号(0,1,3,4)组合起来,得到:
1)输出线圈,数据类型编号为0,地址范围为:000001-065536。
2)离散量输入,数据类型编号为1,地址范围为:100001-165536。
3)输入寄存器,数据类型编号为3,地址范围为:300001-365536。
4)保持寄存器,数据类型编号为4,地址范围为:400001-465536。
虽然每一种数据类型都最多允许有65536个元素,但是在实际应用中仅某一种数据类型通常很难达到这么多的数量,除非是多种数据类型数据的总和,所以实际应用上每种数据类型允许的元素最大个数通常定义为9999个(1-9999),与地址编号(0,1,3,4)组合得到:
1)输出线圈:数据类型编号为0,其地址范围为:00001-09999。
2)离散量输入:数据类型编号为1,其地址范围为:10001-19999。
3)输入寄存器:数据类型编号为3,其地址范围为:30001-39999。
4)保持寄存器,数据类型编号为4,其地址范围为:40001-49999。
单片机映射方法
对于我们使用单片机我们可以通过以下方式来映射Modbus的虚拟地址,定义一个数组,定义寄存器起始地址,寄存器数量,寄存器数量最多可以有9999个,但是我们实际情况下通常没有那么多,按照实际使用数量来定义,比如这里定义了9个(即9999-9990)。
#define COILS_ADDR_START (9990)
#define COILS_ADDR_END (9999)
#define COILS_COUNT (9999 - 9990)
#define DISCRETE_INPUT_ADDR_START (19990)
#define DISCRETE_INPUT_ADDR_END (19999)
#define DISCRETE_INPUT_COUNT (19999 - 19990)
#define INPUT_REGISTERS_ADDR_START (39990)
#define INPUT_REGISTERS_ADDR_END (39999)
#define INPUT_REGISTERS_COUNT (39999 - 39990)
#define HOLDING_REGISTERS_ADDR_START (49990)
#define HOLDING_REGISTERS_ADDR_END (49999)
#define HOLDING_REGISTERS_COUNT (49999 - 49990)
bool coilsBuf[COILS_COUNT];
bool discreteInputBuf[COILS_COUNT];
unsigned short inputRegistersBuf[COILS_COUNT];
unsigned short holdingRegistersBuf[COILS_COUNT];
根据主机提供的读地址或写地址减去我们定义的寄存器起始地址,就可以转化为对应数据的数组索引,再根据主机提供的读数量或写数量(注意数量是寄存器个数,而不是字节数)就可以知道数组索引范围。
10、功能码
前面我们了解到主设备可以访问或修改从机设备中的存储的数据,为了便于主设备使用Modbus协议访问和修改从设备中存储的数据,Modbus协议根据数据模型和功能制定了一系列的功能代码,功能码和描述如下所示:
功能码 名称 功能描述
01 读线圈状态 读位(读 N 个 bit)读从机线圈寄存器,位操作
02 读输入离散量 读位(读 N 个 bit)读离散输入寄存器,位操作
03 读多个寄存器 读整型,字符型,状态字,浮点型(读N 个 word)读保持寄存器,字节操作 04 读输入寄存器 读整型,状态字,浮点型(读 N 个word)读输入寄存器,字节操作
05 写单个线圈 写位(写 1 个 bit)—写线圈寄存器,位操作
06 写单个保持寄存器 写整型,字符型,状态字,浮点型(写一个 word )写保持寄存器,字节操作
07 读取异常状态 取得 8 个内部线圈的通断状态,这 8 个线圈的地址由控制器决定,用户逻辑可以将这些线圈定义,以说明从机状态,短报文适宜于迅速读取状态
08 回送诊断校验 把诊断校验报文送从机,以对通信处理进行评鉴
09 编程(只用于 484) 使主机模拟编程器作用,修改 PC 从机逻辑
0A 控询(只用于 484) 可使主机与一台正在执行长程序任务从机通信,探询该从机是否已完成其操作任务,仅在含有功能码 9 的报文发送后,本功能码才发送
0B 读取事件计数 可使主机发出单询问,并随即判定操作是否成功,尤其是该命令或其他应答产生通信错误时
0C 读取通讯事件记录 可是主机检索每台从机的 ModBus 事务处理通信事件记录。如果某项事务处理完成,记录会给出有关错误
0D 编程(184/384/484/584) 可使主机模拟编程器功能修改 PC 从机逻辑
0E 探询(184/384/484/584) 可使主机与正在执行任务的从机通信,定期控询该从机是否已完成其程序操作,仅在含有功能 13 的报文发送后,本功能码才得发送
0F 写多个线圈 可以写多个线圈强置一串连续逻辑线圈的通断
10 写多个保持寄存器 写多个保持寄存器把具体的二进制值装入一串连续的保持寄存器
11 报告从机标识 可使主机判断编址从机的类型及该从机运行指示灯的状态
12 (884 和 MICRO84) 可使主机模拟编程功能,修改 PC 状态逻辑
13 重置通信链路 发生非可修改错误后,是从机复位于已知状态,可重置顺序字节
14 读取通用参数(584L) 显示扩展存储文件中的数据信息
15 写入通用参数(584L) 把通用参数写入扩展存储文件
16~40 保留做扩展功能备用
41~48 保留以备用户功能所用 留作用户功能的扩展编码
49~77 非法功能
78~7F 保留 留作内部作用
80~FF 保留 用于异常应答
Modbus定义了大量的功能代码,但是更为常用的功能代码只有如下部分功能代码:
功能码 名称 功能 对应的地址类型
01 读线圈状态 读位(读 N 个 bit)读从机线圈寄存器,位操作 0x
02 读输入离散量 读位(读 N 个 bit)读离散输入寄存器,位操作 1x
03 读多个寄存器 读整型,字符型,状态字,浮点型(读 N 个 word)读保持寄存器,字节操作 4X
04 读输入寄存器 读整型,状态字,浮点型(读 N 个word)读输入寄存器,字节操作 3x
05 写单个线圈 写位(写 1 个 bit)写线圈寄存器,位操作 0x
06 写单个保持寄存器 写整型,字符型,状态字,浮点型(写一个 word)写保持寄存器,字节操作 4x
0F 写多个线圈 写位(写 N 个 bit)强置一串连续逻辑线圈的通断 0x
10 写多个保持寄存器 写整形,字符型,状态字,浮点型(写 N 个 word)把具体的二进制值装入一串连续的保持寄存器 4x
11、错误码
前面说过了当主从设备通信出现错误后,从设备可以将一个独特的代码放入到回应消息帧的数据域中,主设备接收到消息后能够大概判断从设备发生了什么错误,Modbus定义的常用错误代码如下所示:
异常码 名称 描述
01 (01H) 非法功能 在请求中接收的功能代码不是从设备的一个授权操作。从设备可能处于错误状态,无法处理特定请求。
02 (02H) 非法数据地址 从设备接收的数据地址不是从设备的一个授权地址。
03 (03H) 非法数据值 指定的数据超过范围或者不允许使用。
04 (04H) 从站设备故障 从设备未能执行一个请求的操作,因为出现了一个无法修复的错误。
05 (05H) 确认 从站设备已经接受请求,并且正在处理这个请求,但是需要长持续时间进行这些操作,返回这个响应防止在客户机(或主站)中发生超时错误,客户机(或主机)可以继续发送轮询程序完成报文来确认是否完成处理。
06 (06H) 从站设备忙 从设备忙于处理另一个命令。主设备必须在从设备空闲后发送请求。
07 (07H) 否定确认 从站设备无法执行主站设备发送的请求。
08 (08H) 存储奇偶性差错 从设备在尝试读取扩展存储器的时候从存储器中检测到一个奇偶校验错误。
10 (0AH) 不可用的网关路径 与网关一起使用,指示网关不能为处理请求分配输入端口值输出端口的内部通信路径。通常意味着网关是错误配置的或过载的。
11 (0BH) 网关目标设备响应失败 与网关一起使用,指示没有从目标设备中获得响应,通常意味着设备未在网络中。
12、注意事项
1)注意Modbus RTU协议均采用16位数据传输(无论是读写线圈,读离散输入,读输入寄存器,读写保持寄存器),所以利用Modbus协议传输多个单字节数据时需要合并成16位(两字节)的数据来传输或存储,同时注意合并的字节序(大小端模式)。
2)Modbus主要处理16位的寄存器,需要传输更大的数据(如32位的浮点数)时,可以用两个连续的16位寄存器来表示这个值。
3)而读寄存器时从设备应答则较为直观,从机返回数据既作为主设备请求的数据也作为应答。
4)写寄存器时从机需要应答,以提示主设备数据是否写入成功,从机应答有时返回与主机请求相同内容,而有时则不同(具体看写线圈,还是写多线圈,还是写单个保持寄存器,或是写多个保持寄存器,这几个操作的应答数据帧包含的内容/字节数是不同的,具体看协议参考)。
实例:
可以看到写多个保持寄存器时从机应答的数据帧和主机请求时发送的数据帧不同,如下图:
如果是写单个保持寄存器,那么从机应答时返回给主机的数据帧与主机请求时发送给从机的数据帧相同。
5)将第4)点整理得到较为通用的主设备读写数据操作而向从机发送的数据帧格式,以及从机的应答数据帧格式,如下:
主设备向从机读取(Read)数据,主机发送格式:
从设备地址
功能代码
起始寄存器地址
寄存器个数
CRC 校验
1 Byte
1 Byte
2 Bytes,高字节在前
2 Bytes,高字节在前
2 Bytes
从机应答格式:
从设备地址
功能代码
数据长度(字节数)
实际数据
CRC 校验
1 Byte
1 Byte
1 Byte
数据长度指定的 N Bytes 数据,即寄存器个数 x 2
2 Bytes
主设备向从机写入(Write)数据,主机发送格式:
从设备地址
功能代码
起始寄存器地址
寄存器个数
数据长度(字节数)
实际数据
CRC 校验
1 Byte
1 Byte
2 Bytes,高字节在前
2 Bytes,高字节在前
1 Byte
数据长度指定的 N Bytes 数据,即寄存器个数 x 2
2 Bytes
从机应答格式:
从设备地址
功能代码
起始寄存器地址
寄存器个数
CRC 校验
1 Byte
1 Byte
2 Bytes,高字节在前
2 Bytes,高字节在前
2 Bytes
注意,主设备写入数据时主机的发送,和主设备读数据时从机的应答,这两种数据帧都需要指定数据长度(数据字节数)和实际数据。总结为写数据都需要“数据长度”和“实际数据”,所以可以这样理解:主机写数据时为主机向从机写入数据,读数据时为从机向主机写入数据。