Bootstrap

Modbus TCPIP基础知识-------------ModbusTCPIP协议概述(一)

1 ModbusTcp

简单的理解一下Modbus TCP/IP协议的内容,就是去掉了modbus协议本身的CRC校验,增加了MBAP 报文头。TCP/IP上的MODBUS的请求/响应如下图所示:

TCPIPmodbus请求和响应

1.1 MBAP报文

首先来看一下,MBAP 报文头都包括了哪些信息和内容

长度描述客户机服务器
事务元标识符2个字节MODBUS请求/响应事务处理的识别码客户机启动服务器从接收的请求中重新复制
协议标识符2个字节0=MODBUS协议客户机启动服务器从接收的请求中重新复制
长度2个字节以下字节的数量客户机启动(请求)服务器(响应)启动
单元标识符1个字节串行链路或其它总线上连接的远程从站的识别码客户机启动服务器从接收的请求中重新复制

事务元标识符(2个字节):用于事务处理配对。在响应中,MODBUS服务器复制请求的事务处理标识符。这里在以太网传输中存在一个问题,就是先发后至,我们可以利用这个事务处理标识符做一个TCP序列号,来防止这种情况所造成的数据收发错乱(这里我们先不讨论这种情况,这个事务处理标识符我们统一使用0x00,0x01)
协议标识符(2个字节):modbus协议标识符为0x00,0x00
长度(2个字节):长度域是下一个域的字节数,包括单元标识符和数据域。
单元标识符(1个字节):该设备的编号。(可以使用PLC的IP地址标识)。

在MODBUS MODBUS+串行链路子网中对设备进行寻址时,这个域是标识设备地址。在这种情况下,“Unit Identifier”携带一个远端设备的MODBUS从站地址:

  • 如果MODBUS服务器连接到MODBUS+或MODBUS串行链路子网,并通过一个桥或网关配置地址这个服务器,MODBUS单元标识符对识别连接到网桥或网关后的子网的从站设备是必需的。目的IP地址识别了网桥本身的地址,而网桥则使用MODBUS单元标识符将请求转交给正确的从站设备。
  • 分配串行链路上MODBUS从站设备地址为1~247(10进制),地址0作为广播地址。
    对 TCP/IP 来说,利用IP地址寻址MODBUS服务器;因此,MODBUS单元标识符是无用的。必需使用值0xFF。
  • 当对直接连接到TCP/IP网络上的MODBUS服务器寻址时,建议不要在“单元标识符”域使用有效的MODBUS从站地址。在一个自动系统中重新分配IP地址的情况下,并且如果以前分配给MODBUS服务器的IP地址又被指配给网关,使用一个有效的从站地址可能会由于网关的路由不畅而引起麻烦。使用无效的从站地址,网关仅是简单地废弃MODBUD PDU,而不会有任何问题。建议:在采用0xFF作为“单元标识符”的无效值。

    注:0也可以用作与MODBUS/TCP设备直接通信。

1.2 MODBUS请求的生成

在收到来自用户应用的需求后,客户端必须生成一个MODBUS请求,并发送到TCP管理。下表显示MODBUS请求ADU编码:

类型描述字节大小实例
MBAP报文头事务处理标识符Hi10x15
事务处理标识符Lo10x01
协议标识符20x0000
长度20x0006
单元标识符10xFF
MODBUS请求功能码10x03
起始地址20x0005
寄存器数量20x0001

1.3 MODBUS响应的生成

一旦处理请求,MODBUS 服务器必须使用适当的MODBUS服务器事务处理生成一个响应,并且必须将响应发送到TCP管理组件。

根据处理结果,可以生成两类响应:

  • 肯定的MODBUS响应:
    • 响应功能码 = 请求功能码
  • MODBUS异常响应:
    • 目的是为客户机提供与处理过程检测到的错误相关的信息
    • 响应功能码 = 请求功能码+0x80
    • 提供异常码来表明出错的原因。
异常码MODBUS名称备注
01非法的功能码服务器不了解功能码
02非法的数据地址与请求有关
03非法的数据值与请求有关
04服务器故障在执行过程中,服务器故障
05确认服务器接受服务调用,但是需要相对长的时间完成服务。因此,服务器仅返回一个服务调用接收的确认。
06服务器繁忙服务器不能接受MODBUS请求PDU。客户应用由责任决定是否和何时重发请求。
0A网关故障网关路经是无效的。
0B网关故障目标设备没有响应。网关生成这个异常信息。

通过上面的两步,一个Modbus TCP的客户端连接已经建立起来了,下面我们就来分析Modbus TCP协议的具体内容与实现方式了。

2 Modbus介绍

2.1 MODBUS数据模型

基本表格对象类型访问类型内容
离散量输入单个比特只读I/O系统提供这种类型数据
线圈单个比特读写通过应用程序改变这种类型数据
输入寄存器16比特字只读I/O系统提供这种类型数据
保持寄存器16比特字读写通过应用程序改变这种类型数据

2.2 公共功能码定义

功能码
子码十六进制
比特访问
物理离散量输入读输入离散量0202
内部比特或物理线圈读线圈0101
写单个线圈0505
写多个线圈150F
16比特访问输入寄存器读输入寄存器0404
内部存储器或物理输出存储器读多个寄存器0303
写单个寄存器0606
写多个寄存器1610
读/写多个寄存器2317
屏蔽写寄存器2216
文件记录访问读文件记录20614
写文件记录21615

3 功能码描述

我们直接用实例来说明。

3.1 01(0x01)功能码—读线圈

在一个远程设备中,使用该功能码读取线圈的1 至2000 连续状态。请求PDU 详细说明了起始地址,即指定的第一个线圈地址和线圈编号。从零开始寻址线圈。因此寻址线圈1-16 为0-15。

根据数据域的每个比特将响应报文中的线圈分成为一个线圈。指示状态为1= ON 和0= OFF。

第一个数据字节的LSB(最低有效位)包括在询问中寻址的输出。其它线圈依次类推,一直到这个字节的高位端为止,并在后续字节中从低位到高位的顺序。

如果返回的输出数量不是八的倍数,将用零填充最后数据字节中的剩余比特(一直到字节的高位端)。字节数量域说明了数据的完整字节数。

请求与响应格式

请求PDU

功能码1个字节0x01
起始地址2个字节0x0000至0xFFFF
线圈数量2个字节1至2000(0x7D0)

响应PDU

功能码1个字节0x01
字节数1个字节N*
线圈状态N个字节n=N或N+1

注:*N=输出数量/8,如果余数不等于0,那么N=N+1

错误

差错码1个字节功能码+0x80
异常码1个字节01或02或03或04

这是一个请求读离散量输出20-38 的实例:

请求响应
域名十六进制域名十六进制
功能01功能01
起始地址Hi00字节数03
起始地址Lo13输出状态27-20CD
输出数量Hi00输出状态35-286B
输出数量Lo13输出状态38-3605

将输出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。用零填充五个剩余高位比特。

注:用零填充五个剩余比特(一直到高位端)。

3.2 02(0x02)功能码—读离散量输入

在一个远程设备中,使用该功能码读取离散量输入的1 至2000 连续状态。请求PDU 详细说明了起始地址,即指定的第一个输入地址和输入编号。从零开始寻址输入。因此寻址输入1-16 为0-15。

根据数据域的每个比特将响应报文中的离散量输入分成为一个输入。指示状态为1= ON 和0=OFF。第一个数据字节的LSB(最低有效位)包括在询问中寻址的输入。其它输入依次类推,一直
到这个字节的高位端为止,并在后续字节中从低位到高位的顺序。

如果返回的输入数量不是八的倍数,将用零填充最后数据字节中的剩余比特(一直到字节的高位端)。字节数量域说明了数据的完整字节数。

请求PDU

功能码1个字节0x02
起始地址2个字节0x0000至0xFFFF
线圈数量2个字节1至2000(0x7D0)

响应PDU

功能码1个字节0x82
字节数1个字节N*
线圈状态N*x1个字节

注:*N=输出数量/8,如果余数不等于0,那么N=N+1

错误

差错码1个字节功能码+0x82
异常码1个字节01或02或03或04

这是一个请求读离散量输入197-218 的实例:

请求响应
域名十六进制域名十六进制
功能02功能02
起始地址Hi00字节数03
起始地址LoC4输出状态204-197AC
输出数量Hi00输出状态212-205DB
输出数量Lo16输出状态218-21335

将离散量输入状态204-197表示为十六进制字节值AC,或二进制1010 1100。输入204是这个字节的MSB,输入197 是这个字节的LSB。

将离散量输入状态218-213表示为十六进制字节值35,或二进制0011 0101。输入218位于左侧第3 比特,输入213 是LSB。

注:用零填充2 个剩余比特(一直到高位端)。

3.3 03(0x03)功能码—读保持寄存器

在一个远程设备中,使用该功能码读取保持寄存器连续块的内容。请求PDU说明了起始寄存器地址和寄存器数量。从零开始寻址寄存器。因此,寻址寄存器1-16 为0-15
将响应报文中的寄存器数据分成每个寄存器有两字节,在每个字节中直接地调整二进制内容。
对于每个寄存器,第一个字节包括高位比特,并且第二个字节包括低位比特。

请求

功能码1个字节0x03
起始地址2个字节0x0000至0xFFFF
寄存器数量2个字节1至125(0x7D)

响应

功能码1个字节0x03
字节数1个字节2xN*
寄存器值N*x2个字节

注:*N=输出数量/8,如果余数不等于0,那么N=N+1

错误

差错码1个字节0x83
异常码1个字节01或02或03或04

这是一个请求读保持寄存器108-110 的实例:

请求响应
域名十六进制域名十六进制
功能03功能03
高起始地址00字节数06
低起始地址6B寄存器值Hi(108)02
高寄存器编号00寄存器值Lo(108)2B
低寄存器编号03寄存器值Hi(109)00
寄存器值Lo(109)00
寄存器值Hi(110)00
寄存器值Lo(110)64

将寄存器108的内容表示为两个十六进制字节值02 2B,或十进制555。将寄存器109-110的内容分别表示为十六进制00 00 和00 64,或十进制0 和100。

3.4 04(0x04)功能码—读输入寄存器

在一个远程设备中,使用该功能码读取1 至大约125 的连续输入寄存器。请求PDU 说明了起始地址和寄存器数量。从零开始寻址寄存器。因此,寻址输入寄存器1-16 为0-15
将响应报文中的寄存器数据分成每个寄存器为两字节,在每个字节中直接地调整二进制内容。
对于每个寄存器,第一个字节包括高位比特,并且第二个字节包括低位比特。

请求

功能码1个字节0x04
起始地址2个字节0x0000至0xFFFF
寄存器数量2个字节0x0000至0x007D

响应

功能码1个字节0x04
字节数1个字节2xN*
寄存器值N*x2个字节

注:*N=输入寄存器的数量

错误

差错码1个字节0x84
异常码1个字节01或02或03或04

这是一个请求读输入寄存器9的实例:

请求响应
域名十六进制域名十六进制
功能04功能04
起始地址Hi00字节数02
起始地址Lo08输入寄存器值Hi(9)00
输入寄存器数量Hi00输入寄存器值Lo(9)0A
输入寄存器Lo01

将输入寄存器9 的内容表示为两个十六进制字节值00 0A,或十进制10。

3.5 05(0x05)功能码—写单个线圈

在一个远程设备上,使用该功能码写单个输出为ON 或OFF。
请求数据域中的常量说明请求的ON/OFF状态。十六进制值FF 00请求输出为ON。十六进制值00 00 请求输出为OFF。其它所有值均是非法的,并且对输出不起作用。
请求PDU说明了强制的线圈地址。从零开始寻址线圈。因此,寻址线圈1 为0。线圈值域的常量说明请求的ON/OFF 状态。十六进制值0XFF00请求线圈为ON。十六进制值0X0000请求线圈为
OFF。其它所有值均为非法的,并且对线圈不起作用。
正常响应是请求的应答,在写入线圈状态之后返回这个正常响应。

请求

功能码1个字节0x05
输出地址2个字节0x0000至0xFFFF
输出值2个字节0x0000至0xFF00?

响应

功能码1个字节0x05
输出地址2个字节0x0000至0xFFFF
输出值2个字节0x0000至0xFF00

错误

差错码1个字节0x85
异常码1个字节01或02或03或04

这是一个请求写线圈173为ON的实例:

请求响应
域名十六进制域名十六进制
功能05功能05
输出地址Hi00输出地址Hi00
输出地址LoAC输出地址LoAC
输出值Lo00输出值Lo00

3.6 06(0x06)功能码—写单个寄存器

在一个远程设备中,使用该功能码写单个保持寄存器。
请求PDU 说明了被写入寄存器的地址。从零开始寻址寄存器。因此,寻址寄存器1 为0。
正常响应是请求的应答,在写入寄存器内容之后返回这个正常响应。

请求

功能码1个字节0x06
寄存器地址2个字节0x0000至0xFFFF
寄存器值2个字节0x0000至0xFFFF

响应

功能码1个字节0x06
寄存器地址2个字节0x0000至0xFFFF
寄存器值N*x2个字节0x0000至0xFFFF

错误

差错码1个字节0x86
异常码1个字节01或02或03或04

这是一个请求将十六进制 00 03 写入寄存器2的实例:

请求响应
域名十六进制域名十六进制
功能06功能06
寄存器地址Hi00输出地址Hi00
寄存器地址Lo01输出地址Lo01
寄存器值Hi00输出值Hi00
寄存器值Lo03输出值Hi03

3.7 15(0x0F)功能码—写多个线圈

在一个远程设备中,使用该功能码强制线圈序列中的每个线圈为ON 或OFF。请求PDU说明了强制的线圈参考。从零开始寻址线圈。因此,寻址线圈1 为0。
请求数据域的内容说明了被请求的ON/OFF 状态。域比特位置中的逻辑“1”请求相应输出为ON。域比特位置中的逻辑“0”请求相应输出为OFF。
正常响应返回功能码、起始地址和强制的线圈数量。

请求PDU

功能码1个字节0x0F
起始地址2个字节0x0000至0xFFFF
输出数量2个字节0x0001至0x07B0
字节数1个字节N*
输出值N*X1个字节

注:*N=输出数量/8,如果余数不等于0,那么N=N+1

响应PDU

功能码1个字节0x0F
起始地址2个字节0x0000至0xFFFF
输出数量2个字节0x0001至0x07B0

错误

差错码1个字节0x8F
异常码1个字节01或02或03或04

这是一个请求从线圈20开始写入10个线圈的实例:

请求的数据内容为两个字节:十六进制CD 01 (二进制1100 1101 0000 0001)。使用下列方法,二进制比特对应输出。

比特1100110100000001
输出27262524232221202928

传输的第一字节(十六进制CD)寻址为输出27-20,在这种设置中,最低有效比特寻址为最低输出(20)。
传输的下一字节(十六进制01)寻址为输出29-28,在这种设置中,最低有效比特寻址为最低输出(28)。
应该用零填充最后数据字节中的未使用比特。

请求响应
域名十六进制域名十六进制
功能0F功能0F
起始地址Hi00起始地址Hi00
起始地址Lo13起始地址Lo13
输出数量Hi00输出数量Hi00
输出数量Lo0A输出数量Lo0A
字节数02
输出值HiCD
输出数量Lo01

3.8 16(0x10)功能码—写多个寄存器

在一个远程设备中,使用该功能码写连续寄存器块(1 至约120 个寄存器)。
在请求数据域中说明了请求写入的值。每个寄存器将数据分成两字节。
正常响应返回功能码、起始地址和被写入寄存器的数量。

请求

功能码1个字节0x10
起始地址2个字节0x0000至0xFFFF
寄存器数量2个字节0x0001至0x0078
字节数1个字节2xN*
寄存器值N*x2个字节

注:*N=寄存器的数量

响应

功能码1个字节0x04
起始地址2个字节0x0000至0xFFFF
寄存器数量2个字节1至123(0x7B)

错误

差错码1个字节0x90
异常码1个字节01或02或03或04

这是一个请求将十六进制00 0A 和01 02 写入以2 开始的两个寄存器的实例:

请求响应
域名十六进制域名十六进制
功能10功能10
起始地址Hi00起始地址Hi00
起始地址Lo01起始地址Lo01
寄存器数量Hi00寄存器数量Hi00
寄存器数量Lo02寄存器数量Lo02
字节数04
寄存器值Hi00
寄存器值Lo0A
寄存器值Hi01
寄存器值Lo02

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;