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)和校验码(Checksum)则因网络底层的实现方式不同而有所区别。协议头包含了从站的地址,功能码告诉从站要执行何种功能,数据区是具体的数据内容。
报文定义了一个与物理层无关的协议数据单元(简称 PDU),PDU 协议字段组成如下:
PDU = 功能码(Function Code) + 数据域(Data)(功能码 1 Byte,数据域不确定)
对于不同类型的网络,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)(功能码 1 Byte,数据域长度不确定)。
ADU = MBAP+PDU
数据帧组成如下图:
其中 MBAP(报文头)占 7 Bytes,功能码 1 Byte,数据域长度不确定,由具体功能决定,如下所示:
事务处理标识 | 协议标识 | 长度 | 单元标识符 |
---|---|---|---|
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 协议程序,从站是运行 Slave 协议程序。
起始位 | 设备地址 | 功能代码 | 数据数量 | 数据 | LRC 高字节 | LRC 低字节 | 结束符 |
---|---|---|---|---|---|---|---|
: | 2 Bytes | 2 Bytes | N | N Bytes | 1 Byte | 1 Byte | CR, LF(回车换行) |
Modbus 以 ASCII 模式通信,在消息中的每个 8 Bit 字节都作为两个 ASCII 字符发送,也就是说使用两个字符来表示 1 个字节的十六进制值,可见所见即所得。
例如表示十六进制 35
需要使用 3
和 5
这两个字符,而每个字符都占用 1 个字节,字符 3
实际值为十六进制 33
,字符 5
实际值为 35
,所以为了表示 30
需要使用 16 Bits 数据,所以实际发送的数据为 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 协议程序,从站是运行 Slave 协议程序。
起始位 | 设备地址 | 功能代码 | 数据数量 | 数据 | 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 个字节(8 bits),所以传输一个字符需要花费传输 8 个数据位的时间(所以这里字符传输时间并不是真的字面意思传输字符,而是指代传输字节)。
然而实际上传输 1 个字节数据需要花费的时间并不只 8 个位时间,因为除了传输固有的 1 字节数据,还需要传输一些辅助功能位。例如发送 1 个字节需要固定起始位 1 位,数据位 8 位,校验位 1 位(可选的),停止位 1 位,其中 8 位数据位才是真正的有效数据,所以有如下公式来计算字符时间。
字符时间 = 1 s 波特率 × 字符总位数。 字符时间 = \frac{1s}{波特率} \times 字符总位数。 字符时间=波特率1s×字符总位数。
例如:固定起始位 1 位,数据位 8 位,奇/偶校验位 1 位,停止位 1 位,波特率为9600 bps,计算单个字符传输时间为:
字符时间 = 1000 m s 9600 × ( 1 + 8 + 1 + 1 ) = 1.145833 m s 字符时间 = \frac{1000ms}{9600} \times (1+8+1+1)=1.145833ms 字符时间=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 方法是将消息中的 8 Bit 的字节连续累加,累加时丢弃累加进位。
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),大小只有 1 Bit,属于开关量,数值范围 ON
或 OFF
,权限是 可读可写
,既可以是一个输出量输出点,也可以是数字量输入点。
(2) 输入离散量(Discrete Input),属于离散量,大小只有 1 Bit,数值范围 ON
或 OFF
,权限是 只读
,即数字量输出点。
(3) 输入寄存器(Input Registers),16 Bit 的寄存器,权限是 只读
,可以用作模拟量或 16 位打包输入点。
(4) 保持寄存器(Holding Registers),16 Bit 的寄存器,权限是 可读可写
,既可以是一个模拟量或 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 |
注意,主设备写入数据时主机的发送,和主设备读数据时从机的应答,这两种数据帧都需要指定数据长度(数据字节数)和实际数据。总结为写数据都需要 “数据长度” 和 “实际数据”,所以可以这样理解:主机写数据时为主机向从机写入数据,读数据时为从机向主机写入数据。
后续还会继续更新和完善 Modbus 协议相关内容,这篇文章篇幅较长,所以其他内容比如 Modbus 调试工具使用方法会在下一篇文章展开。
文章内容如果有帮助的话赶紧点个赞给我一些鼓励吧,如果内容有问题感谢留言。