第一部分:Modbus协议
1 引言
1.1 范围
MODBUS是OSI模型第7层上的应用层报文传输协议,它在连接至不同类型总线或网络的设备之间提供客户机/服务器通信。
自从1979年出现工业串行链路的事实标准以来,MODBUS使成千上万的自动化设备能够通信。目前,继续增加对简单而雅观的MODBUS结构支持。互联网组织能够使TCP/IP栈上的保留系统端口502 访问MODBUS。
MODBUS 是一个请求/应答协议,并且提供功能码规定的服务。MODBUS 功能码是MODBUS
请求/应答PDU 的元素。本文件的作用是描述MODBUS 事务处理框架内使用的功能码。
1.2 规范性引用文件
1.RFC791,互联网协议,Sep81 DARPA
2.MODBUS 协议参考指南Rev J,MODICON,1996 年6 月,doc#PI_MBUS_300
MODBUS是一项应用层报文传输协议,用于在通过不同类型的总线或网络连接的设备之间的客户机/服务器通信。目前,使用下列情况实现MODBUS:
以太网上的TCP/IP。
各种媒体(有线:EIA/TIA-232-E、EIA-422、EIA/TIA-485-A;光纤、无线等等)上的异步串行传输。
MODBUS PLUS,一种高速令牌传递网络。
图1.1.1 Modbus通信栈
2.缩略语
ADU | 应用数据单元 | MAC | 介质访问控制 |
HDLC | 高级数据链路控制 | MB | MODBUS 协议 |
HMI | 人机界面 | MBAP | MODBUS 协议 |
IETF | 因特网工程工作组 | PDU | 协议数据单元 |
I/O | 输入/输出设备 | PLC | 可编程逻辑控制器 |
IP | 互联网协议 | TCP | 传输控制协议 |
3.背景概要
MODBUS 协议允许在各种网络体系结构内进行简单通信。
图1.3.1 MODBUS网络体系结构的实例
4. 总体描述
4.1 协议描述
MODBUS 协议定义了一个与基础通信层无关的简单协议数据单元(PDU)。特定总线或网络上
的MODBUS 协议映射能够在应用数据单元(ADU)上引入一些附加域。
图1.4.1 通用MODBUS帧
启动MODBUS 事务处理的客户机创建MODBUS应用数据单元。功能码向服务器指示将执行哪种操作。
MODBUS 协议建立了客户机启动的请求格式。
用一个字节编码MODBUS 数据单元的功能码域。有效的码字范围是十进制1-255(128-255 为异常响应保留)。当从客户机向服务器设备发送报文时,功能码域通知服务器执行哪种操作。
向一些功能码加入子功能码来定义多项操作。
从客户机向服务器设备发送的报文数据域包括附加信息,服务器使用这个信息执行功能码定义的操作。这个域还包括离散项目和寄存器地址、处理的项目数量以及域中的实际数据字节数。
在某种请求中,数据域可以是不存在的(0长度),在此情况下服务器不需要任何附加信息。功能码仅说明操作。
如果在一个正确接收的MODBUS ADU 中,不出现与请求MODBUS 功能有关的差错,那么服务器至客户机的响应数据域包括请求数据。如果出现与请求MODBUS 功能有关的差错,那么域包括一个异常码,服务器应用能够使用这个域确定下一个执行的操作。
例如,客户机能够读一组离散量输出或输入的开/关状态,或者客户机能够读/写一组寄存器的数据内容。
当服务器对客户机响应时,它使用功能码域来指示正常(无差错)响应或者出现某种差错(称为异常响应)。对于一个正常响应来说,服务器仅对原始功能码响应。
图1.4.2 MODBUS事务处理(无差错)
对于异常响应,服务器返回一个与原始功能码等同的码,设置该原始功能码的最高有效位为逻辑1。
图1.4.3 MODBUS事务处理(异常响应)
👉注释:需要管理超时,以便明确地等待可能不会出现的应答。
串行链路上第一个MODBUS执行的长度约束限制了MODBUS PDU大小(最大RS485ADU=256字节)。
因此,对串行链路通信来说,MODBUS PDU=256-服务器地址(1字节)-CRC(2 字节)=253字节。
从而:
RS232 / RS485 ADU = 253 字节+服务器地址(1 byte) + CRC (2 字节) = 256 字节。
TCP MODBUS ADU = 249 字节+ MBAP (7 字节) = 256 字节。
MODBUS 协议定义了三种PDU。它们是:
MODBUS请求PDU,mb_req_pdu
MODBUS响应PDU,mb_rsp_pdu
MODBUS异常响应PDU,mb_excep_rsp_pdu
定义mb_req_pdu 为:
mb_req_pdu = { function_code, request_data},其中
function_code - [1个字节] MODBUS 功能码
request_data - [n个字节],这个域与功能码有关,并且通常包括诸如可变参考、变量、数据偏移量、子功能码等信息。
定义mb_rsp_pdu 为:
mb_rsp_pdu = { function_code, response_ data},其中
function_code - [1个字节] MODBUS 功能码
response_data - [n个字节],这个域与功能码有关,并且通常包括诸如可变参考、变量、数据偏移量、子功能码等信息。
定义mb_excep_rsp_pdu 为:
mb_excep_rsp_pdu = { function_code, request_data},其中
function_code - [1个字节] MODBUS 功能码+ 0x80
exception_code - [1个字节],在下表中定义了MODBUS 异常码。
4.2 数据编码
MODBUS 使用一个‘big-Endian’ 表示地址和数据项。这意味着当发射多个字节时,首先发送最高有效位。例如:
寄存器大小 | 值 |
16 -比特 | 0x1234 (发送的第一字节为0x12 然后0x34) |
👉注释:更详细的信息参见[1]。
4.3 MODBUS数据模型
MODBUS 以一系列具有不同特征表格上的数据模型为基础。四个基本表格为:
基本表格 | 对象类型 | 访问类型 | 内容 |
离散量输入 | 单个比特 | 只读 | I/O系统提供这种数据类型 |
线圈 | 单个比特 | 读写 | 通过应用程序改变这种数据类型 |
输入寄存器 | 16-比特字 | 只读 | I/O系统提供这种数据类型 |
输出寄存器 | 16-比特字 | 读写 | 通过应用程序改变这种数据类型 |
输入与输出之间以及比特寻址的和字寻址的数据项之间的区别并没有暗示任何应用操作。如果这是对可疑对象核心部分最自然的解释,那么这种区别是可完全接受的,而且很普通,以便认为四个表格全部覆盖了另外一个表格。
对于基本表格中任何一项,协议都允许单个地选择65536 个数据项,而且设计那些项的读写操作可以越过多个连续数据项直到数据大小规格限制,这个数据大小规格限制与事务处理功能码有关。
很显然,必须将通过MODBUS 处理的所有数据放置在设备应用存储器中。但是,存储器的物理地址不应该与数据参考混淆。要求仅仅是数据参考与物理地址的链接。
MODBUS 功能码中使用的MODBUS 逻辑参考数字是以0 开始的无符号整数索引。
MODBUS模型实现的实例
下例实例示出了两种在设备中构造数据的方法。可能有不同的结构,这个文件中没有全部描述出来。每个设备根据其应用都有它自己的数据结构。
实例1:有4 个独立块的设备
下例实例示出了设备中的数据结构,这个设备含有数字量和模拟量、输入量和输出量。由于不同块中的数据不相关,每个块是相互独立。按不同MODBUS 功能码访问每个块。
图1.4.4 带有独立块的MODBUS数据模型
实例2:仅有1 个块的设备
在这个实例中,设备仅有1 个数据块。通过几个MODBUS 功能码可能得到一个相同数据,或者通过16 比特访问或1 个访问比特。
图1.4.5 仅带有1个块的MODBUS数据模型
4.4 MODBUS 事务处理的定义
下列状态图描述了在服务器侧MODBUS 事务处理的一般处理过程。
图1.4.6 MODBUS事务处理的状态图
一旦服务器处理请求,使用合适的MODBUS服务器事务建立MODBUS 响应。
根据处理结果,可以建立两种类型响应:
一个正MODBUS响应:
响应功能码= 请求功能码
一个MODBUS 异常响应(参见第6.14节):
用来为客户机提供处理过程中与被发现的差错相关的信息;
响应功能码= 请求功能码+ 0x80;
提供一个异常码来指示差错原因。
5. 功能码分类
有三类MODBUS 功能码。它们是:
公共功能码
是较好地被定义的功能码,
保证是唯一的,
MODBUS组织可改变的,
公开证明的,
具有可用的一致性测试,
MB IETF RFC 中证明的,
包含已被定义的公共指配功能码和未来使用的未指配保留供功能码。
用户定义功能码
有两个用户定义功能码的定义范围,即65 至72 和十进制100 至110。
用户没有MODBUS 组织的任何批准就可以选择和实现一个功能码
不能保证被选功能码的使用是唯一的。
如果用户要重新设置功能作为一个公共功能码,那么用户必须启动RFC,以便将改变引入
公共分类中,并且指配一个新的公共功能码。
保留功能码
一些公司对传统产品通常使用的功能码,并且对公共使用是无效的功能码。
图 1.5.1 MODBUS功能码分类
5.1 公共功能码定义
访问类型 | 含义 | 功能码 | 十六进制码 | |||
码 | 子码 | |||||
数据访问 | 字节 | 物理离散量输入 | 读输入离散量 | 02 | 02 | |
内部字节或物理线圈 | 读线圈 | 01 | 01 | |||
写单个线圈 | 05 | 05 | ||||
写多个线圈 | 15 | 0F | ||||
16比特 | 输入存储器 | 读输入寄存器 | 04 | 04 | ||
内部存储器 或 物理输出存储器 | 读多个寄存器 | 03 | 03 | |||
写单个寄存器 | 06 | 06 | ||||
写多个寄存器 | 16 | 10 | ||||
读写多个寄存器 | 23 | 17 | ||||
屏蔽写寄存器 | 22 | 16 | ||||
文件记录访问 | 读文件记录 | 20 | 6 | 14 | ||
写文件记录 | 21 | 6 | 15 | |||
封装接口 | 读设备识别码 | 43 | 14 | 2B |
6. 功能码描述
6.1 01(0x01)读线圈
在一个远程设备中,使用该功能码读取线圈的1 至2000 连续状态。请求PDU 详细说明了起始地址,即指定的第一个线圈地址和线圈编号。从零开始寻址线圈。因此寻址线圈1-16 为0-15。
根据数据域的每个比特将响应报文中的线圈分成为一个线圈。指示状态为1= ON 和0= OFF。第一个数据字节的LSB(最低有效位)包括在询问中寻址的输出。其它线圈依次类推,一直到这个字节的高位端为止,并在后续字节中从低位到高位的顺序。
如果返回的输出数量不是八的倍数,将用零填充最后数据字节中的剩余比特(一直到字节的高位端)。字节数量域说明了数据的完整字节数。
请求PDU | 响应PDU | 错误 | ||||||
功能码 | 1个字节 | 0x01 | 功能码 | 1个字节 | 0x01 | 功能码 | 1个字节 | 功能码+0x80 |
起始地址 | 2个字节 | 0x0000至0xFFFF | 字节数 | 1个字节 | N* | 异常码 | 1个字节 | 01或02或03或04 |
线圈数量 | 2个字节 | 1至2000(0x7D0) | 线圈状态 | N个字节 | n=N或N+1 |
*N=输出数量/8,如果余数不等于0,那么N=N+1
这是一个请求读离散量输出20-38 的实例:
请求 | 响应 | ||
域名 | (十六进制) | 域名 | (十六进制) |
功能 | 01 | 功能 | 01 |
起始地址Hi | 00 | 字节数 | 03 |
起始地址Lo | 13 | 输出状态27-20 | CD |
输出数量Hi | 00 | 输出状态35-28 | 6B |
输出数量Lo | 13 | 输出状态28-36 | 05 |
将输出27-20 的状态表示为十六进制字节值CD,或二进制1100 1101。输出27 是这个字节的MSB,输出20 是LSB。
通常,将一个字节内的比特表示为MSB 位于左侧,LSB 位于右侧。第一字节的输出从左至右为27至20。下一个字节的输出从左到右为35至28。当串行发射比特时,从LSB向MSB传输:20 . . .27、28 . . . 35 等等。
在最后的数据字节中,将输出状态38-36表示为十六进制字节值05,或二进制0000 0101。输出38 是左侧第六个比特位置,输出36 是这个字节的LSB。用零填充五个剩余高位比特。
👉注:用零填充五个剩余比特(一直到高位端)。
图 1.6.1 读取线圈状态图
6.2 02 (0x02)读离散量输入
在一个远程设备中,使用该功能码读取离散量输入的1 至2000 连续状态。请求PDU 详细说明了起始地址,即指定的第一个输入地址和输入编号。从零开始寻址输入。因此寻址输入1-16 为0-15。
根据数据域的每个比特将响应报文中的离散量输入分成为一个输入。指示状态为1= ON 和0=OFF。第一个数据字节的LSB(最低有效位)包括在询问中寻址的输入。其它输入依次类推,一直到这个字节的高位端为止,并在后续字节中从低位到高位的顺序。
如果返回的输入数量不是八的倍数,将用零填充最后数据字节中的剩余比特(一直到字节的高位端)。字节数量域说明了数据的完整字节数。
请求PDU | 响应PDU | 错误 | ||||||
功能码 | 1个字节 | 0x02 | 功能码 | 1个字节 | 0x82 | 功能码 | 1个字节 | 功能码+0x82 |
起始地址 | 2个字节 | 0x0000至0xFFFF | 字节数 | 1个字节 | N* | 异常码 | 1个字节 | 01或02或03或04 |
线圈数量 | 2个字节 | 1至2000(0x7D0) | 输入状态 | N*x1个字节 |
*N=输出数量/8,如果余数不等于0,那么N=N+1
这是一个请求读离散量输出197-218的实例:
请求 | 响应 | ||
域名 | (十六进制) | 域名 | (十六进制) |
功能 | 02 | 功能 | 02 |
起始地址Hi | 00 | 字节数 | 03 |
起始地址Lo | C4 | 输出状态27-20 | AC |
输出数量Hi | 00 | 输出状态35-28 | DB |
输出数量Lo | 16 | 输出状态28-36 | 35 |
将离散量输入状态204-197表示为十六进制字节值AC,或二进制1010 1100。输入204是这个字节的MSB,输入197 是这个字节的LSB。
将离散量输入状态218-213表示为十六进制字节值35,或二进制0011 0101。输入218位于左侧第3 比特,输入213 是LSB。
👉注:用零填充2 个剩余比特(一直到高位端)。
6.3 03 (0x03)读保持寄存器
在一个远程设备中,使用该功能码读取保持寄存器连续块的内容。请求PDU说明了起始寄存器地址和寄存器数量。从零开始寻址寄存器。因此,寻址寄存器1-16 为0-15。
将响应报文中的寄存器数据分成每个寄存器有两字节,在每个字节中直接地调整二进制内容。
对于每个寄存器,第一个字节包括高位比特,并且第二个字节包括低位比特。
(未完待续。。。。。。)