Modbus TCP
TCP/IP 上的 Modbus 寄存器和线圈,带定型寻址、字节序控制,以及一次有边界的网络扫描。
Modbus TCP 驱动通过 TCP/IP 读写 Modbus 寄存器和线圈,对象是 PLC、仪表、网关,以及任何说 Modbus/TCP 的服务器。寻址是完全定型的:你挑一个寄存器类别和一个从 0 起的寄存器号,应用把总线请求拼出来;没有自由文本地址可以打错。
设备
| 字段 | 含义 | 默认值 |
|---|---|---|
| 主机 | Modbus 服务器或网关的 DNS 名称、IPv4 或 IPv6 地址。 | 留空时为 localhost |
| 端口 | TCP 端口。 | 502(配置成 0 时退回 502) |
| 从站 ID | 每一次请求都带着的 Modbus 从站 id,网关靠它把报文路由到它后面那台设备。0 到 255。 | 1 |
| 最小事务间隔(毫秒) | 兼容性节奏:一次事务完成之后、下一次开始之前的最小静默,0 到 1000。填零不加任何人为延时。 | 0 |
| 轮询间隔(毫秒) | 没有自己另作规定的点位所用的默认轮询速率。 | 1000 |
面板标题栏里显示的基础 URI 是 modbus+tcp://host:port/unitID,设了间隔时再加上 ?minimumTransactionIntervalMs=n。
共用同一主机和端口的设备也共用一道事务闸门:发往同一个端点的请求绝不重叠,而这个端点上那些已启用设备中配置得最大的那个最小事务间隔,替所有设备定节奏。别的设备把实际生效的间隔抬到高于这里配置的值时,面板会说出来。这个间隔只管本应用发出去的流量:它是给慢服务器或慢网关的兼容性辅助手段,不是用来修网络故障的。
连接最多等 5 秒;一次应答最多等 10 秒;协议层没有重试。重新连接由后台的重试循环负责,所以一台关着的设备除了一次质量不可用的读数之外不花什么代价。
点位寻址
| 字段 | 含义 | 默认值 |
|---|---|---|
| 寄存器类别 | 这个点位寻的是哪一种 Modbus 实体:HoldingRegister、InputRegister、Coil 或 DiscreteInput。 |
由类型和读写推出来:布尔点位取 DiscreteInput(只读)或 Coil(可写);其余每一种类型取 InputRegister(只读)或 HoldingRegister(可写)。 |
| 寄存器号(从 0 起) | 协议里那个原始地址。它是总线上从 0 起的地址,不是经典的 4xxxx 记法:保持寄存器“40001”就是寄存器号 0。寄存器号在总线上以一个 16 位数传输,所以这个字段接受 0 到 65535,更宽的值在打字的地方就被拒绝。 | 0 |
| 数量(寄存器数,留空 = 自动) | 这个值占多少个 16 位寄存器。这个框只在文本点位上画出来:其余每一种类型的宽度都是定死的,没有什么可覆盖。 | 按类型:布尔 1,Int32 和 Float 2,文本 4 |
| 高位字节在前 | 每个 16 位寄存器里哪一个字节先上总线。只对数值点位有。 | 开(Modbus 规范要求的那样) |
| 高位字在前 | 两个寄存器组成的值里,哪一个寄存器带高半部分。只对数值点位有。 | 开 |
“来源”卡片把拼好的总线地址(例如 100?quantity=2)作为只读预览显示出来,所以面板显示的东西,正是请求里带的东西。
数值类型与数量
提供四种数值类型,每一种在协议上的宽度都是定死的:
| 数据类型 | 寄存器数 | 总线形式 |
|---|---|---|
| Boolean | 1 | 在寄存器类别上:任何非零寄存器读作 true,写入发 1 或 0。在 Coil/DiscreteInput 上:就是那个位本身。 |
| Int32 | 2 | 跨一对寄存器的 32 位有符号整数。 |
| Float | 2 | 跨一对寄存器的 IEEE 754 单精度。 |
| String | 1 或更多(默认 4) | UTF-8 字节,每个寄存器装两个。读取在第一个 NUL 字节处停下;写入必须装得进声明的数量(每寄存器 2 字节),不足处补零。 |
文本点位按设备存放字节的顺序读和写,而写入总是发 UTF-8。不是合法 UTF-8 的字节,也就是设备从未初始化过的一段区域、或者用别的编码写进去的文本看起来的样子,读作 Unicode 替换字符而不是报错:这个点位显出明显不对的东西,而那台设备上其余每个点位照读不误。
数量只对文本点位询问。布尔、Int32 和 Float 的宽度是定死的,所以那个框在这些点上不画出来,点位面板里和点位表格里都不画;这个覆盖项的存在,就是为了给文本点位定尺寸。
字节序与字序
那两个顺序框画在数值点位上。它们是两个互不相干的问题:每个寄存器内部哪个字节在前,以及一对寄存器之间哪个寄存器在前。Modbus 对文本两者都没有定义,所以文本点位不提供它们:它的字节按设备存放的顺序传输。以值 0x01020304 为例,把最高位字节记作 A,四种排布是:
| 高位字节在前 | 高位字在前 | 总线上的字节 |
|---|---|---|
| 开 | 开 | AB CD(规范默认) |
| 开 | 关 | CD AB(经典的字交换) |
| 关 | 开 | BA DC |
| 关 | 关 | DC BA |
读和写按同样的顺序应用这同样两个答案,所以一次写入放下的字节,恰好就是一次读取会取回来的那些。
功能码
| 寄存器类别 | 读 | 写 |
|---|---|---|
| HoldingRegister | 3(读保持寄存器) | 6(写单个寄存器)或 16(写多个寄存器) |
| InputRegister | 4(读输入寄存器) | 只读;写入被拒绝 |
| Coil | 1(读线圈) | 5(强制单线圈) |
| DiscreteInput | 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 |
| 最小事务间隔(毫秒) | 同一台主机上两次完成的探测之间的节奏,0 到 1000。 | 0 |
一次扫描最多发 256 次探测(主机数 × 从站 id 数);计划更大的扫描在任何套接字打开之前就被拒绝。唯一发出去的请求是读取基本设备识别(功能码 43/14):设备发现从不读寄存器,也从不去猜寄存器映射。一个完全合法的 Modbus 异常应答足以证明有这台从站,只是说不出它的身份;TCP 可达、超时和畸形帧都证明不了。同时最多并行扫四台主机,每台主机内部按顺序来,连接和探测的超时都是 750 毫秒。
结果带着完整的端点,以及在设备报告了厂商和产品代码时由它们拼出来的一个建议名称。候选项的点位数为零:寄存器是在添加之后再配置的。已经配置过的从站会被标出来,并提供打开,而不是造一个重复的。
值得知道的限制
- 不提供更宽的整数类型(Int64、无符号宽类型)和 Double:总线编解码只能原样往返上面那四种类型。
- 每个端点同一时刻只有一个请求在途;一个网关后面的许多设备按设计共用这一层串行化。
- 写入一次只寻一个线圈或一段连续寄存器;没有多线圈写入。