Modbus TCP
TCP/IP 上的 Modbus 寄存器和线圈,带定型寻址、字节序控制,以及一次有边界的网络扫描。
Modbus TCP 驱动通过 TCP/IP 读写 Modbus 寄存器和线圈,对象是 PLC、仪表、网关,以及任何说 Modbus/TCP 的服务器。寻址是完全定型的:你挑一个寄存器类别和一个从 0 起的寄存器号,应用把总线请求拼出来;没有自由文本地址可以打错。
设备
| 字段 | 含义 | 默认值 |
|---|---|---|
| 主机 | Modbus 服务器或网关的 DNS 名称、IPv4 或 IPv6 地址。 | 空:填写之前设备不会被连接 |
| 端口 | TCP 端口,1 到 65535。框留空时 502 以灰色显示,设备就在这个端口上被拨号连接;以 0 保存的设备也是这样显示的。 | 502 |
| 单元 ID | 每一次请求都带着的 Modbus 从站 id,网关靠它把报文路由到它后面那台设备。0 到 255。 | 1 |
| 帧格式 | 每个请求在连接上以什么帧传送:MBAP 在每个请求前带 Modbus TCP 报头;RTU 把带 CRC 的 RTU 帧原样发送,用于工作在透传模式的串口转以太网转换器(见 TCP 上的 RTU)。主机、端口、单元 ID 和点位两种情况都一样。 | MBAP |
| 最小事务间隔(ms) | 兼容性节奏:一次事务完成之后、下一次开始之前的最小静默,0 到 1000。填零不加任何人为延时。 | 0 |
| 轮询间隔(ms) | 没有自己另作规定的点位所用的默认轮询速率。 | 1000 |
面板标题栏里显示的基础 URI 是 modbus+tcp://host:port/unitID,设了间隔时再加上 ?minimumTransactionIntervalMs=n。
共用同一主机和端口的设备也共用一道事务闸门:发往同一个 endpoint 的请求绝不重叠,而这个 endpoint 上那些已启用设备中配置得最大的那个最小事务间隔,替所有设备定节奏。别的设备把实际生效的间隔抬到高于这里配置的值时,面板会说出来。这个间隔只管本应用发出去的流量:它是给慢服务器或慢网关的兼容性辅助手段,不是用来修网络故障的。
连接最多等 5 秒;一次应答最多等 10 秒;协议层没有重试。重新连接由后台的重试循环负责,所以一台关着的设备除了一次质量不可用的读数之外不花什么代价。
TCP 上的 RTU
工作在透传模式的串口转以太网转换器自己不说 Modbus TCP:套接字上收到什么,就原样发到它的串行总线上,总线回答什么,就送回来。它后面的设备,就是一台把帧格式设为 RTU 的 Modbus TCP 设备:工作站发送不带 MBAP 报头的 RTU 帧(单元 ID、请求和 CRC),只有 CRC 校验通过的应答才被采信。这时基础 URI 的查询里带上 framing=rtu,例如 modbus+tcp://192.168.0.20:502/1?framing=rtu;设为 MBAP 的设备保留它一直以来的地址。本页其余内容一律照旧:寄存器类别、功能码、写入队列、超时和事务间隔都一样。
- 单元 ID 是设备在转换器后面那条串行总线上的地址,1 到 247。
- 同一台转换器后面的设备共用它的主机和端口,也就共用它的事务关口:一次只有一个请求,这也正是一条串行总线所能承载的全部。转换器如果需要帧与帧之间停顿一下,就调大最小事务间隔(ms)。
- 转换器自身的串口设置(波特率、格式)在转换器上配置;工作站看不到它们。
- RTU 帧不带事务编号,所以每次请求之前,工作站先丢掉转换器已经发来的内容:请求超时之后才到的应答,绝不会被当成下一个请求的应答。
点位寻址
| 字段 | 含义 | 默认值 |
|---|---|---|
| 寄存器类别 | 这个点位寻的是哪一种 Modbus 实体:线圈(0xxxx)、离散输入(1xxxx)、输入寄存器(3xxxx)或保持寄存器(4xxxx)。每一类都带着手册地址给它的首位数字,所以只印了 30001 的手册指的是输入寄存器。 | 由类型和读写推出来:布尔点位取离散输入(只读)或线圈(可写);其余每一种类型取输入寄存器(只读)或保持寄存器(可写)。 |
| 寄存器编号(从 0 开始) | 协议里那个原始地址。它是总线上从 0 起的地址,不是手册的记法:保持寄存器 40001 就是寄存器号 0。在这里输入 30001 不会选定类别,因为确实有设备的寄存器超过 30000:类别在上面选。寄存器号在总线上以一个 16 位数传输,所以这个字段接受 0 到 65535,更宽的值在打字的地方就被拒绝。 | 0 |
| 数量(寄存器,留空为自动) | 这个值占多少个 16 位寄存器。这个框只在文本点位上画出来:其余每一种类型的宽度都是定死的,没有什么可覆盖。 | 按类型:布尔、Int16 和 UInt16 1,Int32 和 Float 2,文本 4 |
| 高字节在前(MSB first,大端序) | 每个 16 位寄存器里哪一个字节先上总线;括号里是手册对这一项的写法。数值点位有,放在保持寄存器或输入寄存器里的布尔点位也有;线圈或离散输入里的布尔值只是一个位,没有字节可排,所以那里不画这个框。 | 开(Modbus 规范要求的那样) |
| 高字在前(high word first) | 两个寄存器组成的值里,哪一个寄存器带高半部分。只对 Int32 和 Float 点位有:只占一个寄存器的值(Boolean、Int16、UInt16)没有第二个寄存器,所以那里不画这个框。两个框下面的数值的字节顺序用手册的字母说出两个框合起来的排布(字节序与字序)。 | 开 |
“信号源”卡片的末尾是手册中的地址,即点位在设备手册所印记法里的位置:类别的首位数字后接从 1 起数的寄存器。输入寄存器 0 读作 30001,保持寄存器 100 上的 Float 读作 40101 到 40102,超过 9998 的寄存器用六位(保持寄存器 9999 读作 410000),所以面板可以和手册逐行对照。
数值类型与数量
提供六种数值类型,每一种在协议上的宽度都是定死的:
| 数据类型 | 寄存器数 | 总线形式 |
|---|---|---|
| Boolean | 1 | 在寄存器类别上:任何非零寄存器读作 true,写入发 1 或 0。在 Coil/DiscreteInput 上:就是那个位本身。 |
| Int16 | 1 | 放在单个寄存器里的 16 位有符号整数,-32768 到 32767。 |
| UInt16 | 1 | 放在单个寄存器里的 16 位无符号整数,0 到 65535。同一个寄存器,按 UInt16 读是 65535,按 Int16 读是 -1:符号由所选的数据类型决定,而不是由寄存器决定。 |
| Int32 | 2 | 跨一对寄存器的 32 位有符号整数。 |
| Float | 2 | 跨一对寄存器的 IEEE 754 单精度。 |
| String | 1 或更多(默认 4) | UTF-8 字节,每个寄存器装两个。读取在第一个 NUL 字节处停下;写入必须装得进声明的数量(每寄存器 2 字节),不足处补零。 |
文本点位按设备存放字节的顺序读和写,而写入总是发 UTF-8。不是合法 UTF-8 的字节,也就是设备从未初始化过的一段区域、或者用别的编码写进去的文本看起来的样子,读作 Unicode 替换字符而不是报错:这个点位显出明显不对的东西,而那台设备上其余每个点位照读不误。
数量(寄存器,留空为自动)只对文本点位询问。布尔、Int16、UInt16、Int32 和 Float 的宽度是定死的,所以那个框在这些点上不画出来,点位面板里和点位表格里都不画;这个覆盖项的存在,就是为了给文本点位定尺寸。
字节序与字序
字节序框画在数值点位和放在寄存器里的布尔点位上,字序框只画在占两个寄存器的点位(Int32、Float)上。它们是两个互不相干的问题:每个寄存器内部哪个字节在前,以及一对寄存器之间哪个寄存器在前。Modbus 对文本两者都没有定义,所以文本点位不提供它们:它的字节按设备存放的顺序传输。以值 0x01020304 为例,把最高位字节记作 A,四种排布是:
| 高字节在前 | 高字在前 | 总线上的字节 |
|---|---|---|
| 开 | 开 | AB CD(规范默认) |
| 开 | 关 | CD AB(经典的字交换) |
| 关 | 开 | BA DC |
| 关 | 关 | DC BA |
读和写按同样的顺序应用这同样两个答案,所以一次写入放下的字节,恰好就是一次读取会取回来的那些。框下面的数值的字节顺序像手册一样用一个词说出这个排布(ABCD、CDAB、BADC 或 DCBA)。
Int16 或 UInt16 的值只是一个寄存器,所以只有字节序对它起作用:对 0x1234,高字节在前发出 12 34,关掉则发出 34 12。没有第二个寄存器可排,所以不画字序框。
功能码
| 寄存器类别 | 读 | 写 |
|---|---|---|
| 保持寄存器(4xxxx) | 3(读保持寄存器) | 6(写单个寄存器)或 16(写多个寄存器) |
| 输入寄存器(3xxxx) | 4(读输入寄存器) | 只读;写入被拒绝 |
| 线圈(0xxxx) | 1(读线圈) | 5(强制单线圈) |
| 离散输入(1xxxx) | 2(读离散输入) | 只读;写入被拒绝 |
只有当服务器的协议应答确认接受时,一次写入才算成功;缓存的值也是到那时才往前走。
设备发现
这个扫描是明确的、有边界的:应用从不推断、也从不横扫你的本地子网。范围表单接收:
| 字段 | 含义 | 默认值 |
|---|---|---|
| IPv4 主机或 CIDR | 一个地址(192.168.1.10),或者一段不宽于 /24 的子网(192.168.1.0/24)。网络地址和广播地址会被跳过。 |
必填 |
| 端口 | 要探测的端口。 | 502 |
| 单元 ID | 单个值、逗号分隔的列表或者递增的区间:1、1,5,9、1, 3, 8-16(0 到 255)。 |
1 |
| 帧格式 | 每次探测所用的帧格式。透传模式的转换器只回应 RTU 帧,所以要用 RTU 扫描。 | MBAP |
| 最小事务间隔(ms) | 同一台主机上两次完成的探测之间的节奏,0 到 1000。 | 0 |
扫描用所选的帧格式发送每一次探测。选 RTU 时,每次探测都是裸的 RTU 帧,只有单元 ID 对得上、CRC 校验通过的应答才算数,所以透传模式转换器后面的从站也能像其他从站一样被找到。找到的从站在添加时沿用这次扫描的帧格式。
一次扫描最多发 256 次探测(主机数 × 从站 id 数);计划更大的扫描在任何套接字打开之前就被拒绝。唯一发出去的请求是读取基本设备识别(功能码 43/14):设备发现从不读寄存器,也从不去猜寄存器映射。一个完全合法的 Modbus 异常应答足以证明有这台从站,只是说不出它的身份;TCP 可达、超时和畸形帧都证明不了。同时最多并行扫四台主机,每台主机内部按顺序来,连接和探测的超时都是 750 毫秒。
结果带着完整的 endpoint,以及在设备报告了厂商和产品代码时由它们拼出来的一个建议名称。候选项的点位数为零:寄存器是在添加之后再配置的。已经配置过的从站会被标出来,并提供打开,而不是造一个重复的。
值得知道的限制
- 不提供其他宽度的整数类型(SByte、Byte、UInt32、Int64、UInt64)和 Double:总线编解码只能原样往返上面那六种类型。
- 每个 endpoint 同一时刻只有一个请求在途;一个网关后面的许多设备按设计共用这一层串行化。
- 写入一次只寻一个线圈或一段连续寄存器;没有多线圈写入。