# Modbus RTU

> 串行总线上的 Modbus，物理线路被显式建模出来，由它上面那些按单元 ID 寻址的设备共用。

Modbus RTU 驱动和串行总线上的 Modbus 设备对话，总线是 RS-485 或 RS-232，通过一个物理 COM 端口、一个 USB 适配器或者一个虚拟/重定向端口够到。因为总线在物理上是共用的，所以拓扑是**驱动 → 线路 → 设备 → 点位**：**线路**拥有一切与电气有关的东西（端口、波特率、格式、时序），而它上面的每一台**设备**只拥有自己的从站地址、轮询和点位。

## 串行线路 {#serial-line}

| 字段 | 含义 | 默认值 |
| --- | --- | --- |
| **名称** | 这条总线的友好名称。任何时候都能改。 | 必填 |
| **串行端口** | 操作系统的端口，例如 `COM3`。这个字段是自由文本，物理端口、虚拟端口和重定向端口都能用，旁边的箭头会提供 Windows 当前检测到的那些端口（并带一个**刷新**，供适配器热插拔时用）；检测出来的列表只是方便，绝不是白名单。 | `COM1` |
| **波特率** | 总线上每台设备共用的位速率。 | 9600 |
| **传输模式** | 总线上每台设备怎样组织报文：**RTU** 是以 CRC 校验的二进制帧；**ASCII** 是从冒号到换行以文本写成、以 LRC 校验的帧，老设备用它。一条总线只说一种模式，所以模式属于线路。 | RTU |
| **串行格式** | 封闭的选项：`8N1`、`8N2`、`8E1`、`8E2`、`8O1`、`8O2`，ASCII 线路上还有 `7N2`、`7E1`、`7E2`、`7O1`；数字是数据位，字母是校验（`N` 无校验，`E` 偶校验，`O` 奇校验），最后一位是停止位。RTU 帧需要 8 个数据位，所以 RTU 线路只提供 8 位的格式；从 ASCII 线路留下来的 7 位格式显示为空，在另选一个之前不会被接受。别的位宽、mark/space 校验和 1.5 位停止位都不支持。 | `8E1` |
| **响应超时（ms）** | 等一台从站应答的最长时间，100 到 60000。超时放长能迁就慢设备，但也让缺席的从站和设备发现更慢才认得出来。 | 1000 |
| **最小事务间隔（ms）** | 全线通用的兼容性节奏，0 到 1000。填零就只保留协议自动的那段静默；填正数只会抬高它，绝不会缩短它。 | 0 |

ASCII 线路不适用下面说的静默：ASCII 帧以换行结束，所以自动静默显示 0 ms，**最小事务间隔（ms）**就是帧与帧之间唯一的停顿，卡片在它下面也这样写：“ASCII 帧以换行结束，线路不另加静默：这个间隔就是帧与帧之间唯一的停顿。”传输模式是一项物理设置，线路启用期间，它和端口、波特率、格式、响应超时一起被锁定；ASCII 线路上设备的运行时地址以 `/ascii` 结尾。

协议本身要求帧与帧之间有静默，线路始终把这段静默执行到位：波特率在 19200 及以下时，自动静默是按配置的格式算出来的 3.5 个字符时间；高于 19200 时，则是规范建议的固定 1.75 毫秒。实际生效的静默取那个自动下限和配置的间隔中**较大**的那个，绝不是两者相加，面板会把两个值都显示出来。把间隔抬高就是有意让线上每一台从站都慢下来；它是一种兼容性辅助手段，不是用来修接线、终端电阻、接地或干扰问题的。

新线路一开始是**已禁用**的，好让它的物理设置先配好。每个物理端口最多属于一条线路，比较时不区分大小写，即使那些线路都禁用着也一样。线路跑着的时候，名称和事务间隔仍然可以改（改时序不必让端口重新开合就生效）；端口、波特率、格式和响应超时在线路启用期间锁住，要改就先禁用并保存。禁用一条线路会停掉它所有设备的通信，但不改动它们各自的“已启用”选择。

线路把物理传输和它的设备分开报告：**已关闭**、**空闲**（没有已启用的设备）、**正在连接**、**在线**，或者带诊断文字的**端口故障**。删掉一条空线路只删这条线路；删掉一条有设备的线路会要求你明确确认，并把那些设备和它们的点位一并删掉。

## 设备 {#device}

| 字段 | 含义 | 默认值 |
| --- | --- | --- |
| **串行线路** | 固定：设备是在它的线路下面创建出来的，并继承那组物理参数，在面板里显示为只读。请把设备加在你想要的那条线路下面。 | 设备所加入的那条线路 |
| **单元 ID** | 这台设备在总线上的 Modbus 地址，1 到 247，在线路内部唯一（已禁用的设备也算在内）。 | 1 |
| **轮询间隔（ms）** | 没有自己另作规定的点位所用的默认轮询速率。 | 1000 |

运行时内部的地址是 `modbus+rtu://<line-id>/<port>/<baud>/<format>/<timeout>/<unitId>/<interval>`：它带的是线路稳定的身份和完整的物理设置，绝不是它的友好名称。

## 共用线路的行为 {#shared-line-behavior}

驱动**每条线路开一个串行会话**，并把来自每台设备的每一个请求都通过一个调度器串起来，所以总线上的读和写绝不重叠。等着写的请求会以有上限的优先级拿到下一个空闲的轮次：写入被及时服务，但持续不断的写入也饿不死轮询。每台从站的超时是彼此隔离的，并带渐进退避，所以一台沉默的设备只拖慢它自己。一个 CRC 校验通过的 Modbus 异常，是那个点位上一次请求的错误，线路仍然健康。只有物理 I/O 故障（端口消失、适配器被拔掉）才会让共用会话失效，并为整条线路发起一次协调好的重开。最后一台设备释放时，端口关掉。

## 点位寻址 {#tag-addressing}

点位寻址和 [Modbus TCP](driver-modbus-tcp) 完全一样：一个**寄存器类别**（线圈（0xxxx）、离散输入（1xxxx）、输入寄存器（3xxxx）、保持寄存器（4xxxx），默认由点位的类型和读写推出来）、一个从 0 起的**寄存器号**、一个自动的**数量（寄存器，留空为自动）**（布尔、Int16 和 UInt16 1 个寄存器，Int32 和 Float 2 个，文本 4 个；这个框只在文本点位上画出来，因为其余每一种宽度都是定死的），以及两个顺序框：管字节序的**高字节在前（MSB first，大端序）**和管字序的**高字在前（high word first）**（只在占两个寄存器的 Int32 和 Float 上画出来），都默认勾选。“信号源”卡片末尾的**手册中的地址**是点位按设备手册印法写出的地址（输入寄存器 0 是 30001）。支持的数值类型是同样那六种：Boolean、Int16、UInt16、Int32、Float 和 String；Int16 和 UInt16 按有符号或无符号读取单个 16 位寄存器，只有字节序对它们起作用，字序框也不画。读写用的功能码和总线排布在 [Modbus TCP](driver-modbus-tcp) 页面上讲，这里原样适用。

## 设备发现 {#discovery}

设备发现在一条已有线路上探测一段单元 ID 范围，它住在那条线路的面板上：

| 字段 | 含义 | 默认值 |
| --- | --- | --- |
| **起始单元 ID** | 第一个要探测的从站 id，1 到 247。 | 1 |
| **结束单元 ID** | 最后一个从站 id，从起点往上递增。 | 32 |

在 ASCII 线路上，探测用 ASCII 帧发送同一个请求，由 LRC 代替 CRC：LRC 校验通过的异常证明有这台从站，LRC 不通过的帧什么也证明不了。

扫描会重新载入线路已保存的物理设置，把端口打开一次，然后用“读取基本设备识别”（功能码 43/14）**按顺序**探测这段范围，每一次探测都按线路实际生效的帧间静默来定节奏。它从不去试各种设置组合，也从不读寄存器。一个 CRC 校验通过的 Modbus 异常足以证明有这台从站，只是说不出它的身份；一次超时或者一个无效帧则证明不了。

一条正在使用的线路绝不会被自动打断或自动开关：请在扫描前先禁用它，扫描完之后再由你自己把它启用回来。取消会关掉串行会话，并把已经显示出来的结果留作临时候选项，直到你明确把它们加进来为止。已经配置过的从站 id 会被标出来，并打开已有的那台设备，而不是造一个重复的；结果不带点位，寄存器是在**添加**之后再配置的。

设备发现和线路配置对每个物理端口共用一份快速失败的占用登记，所以一次扫描和一次改设置不会为端口打起来：输的那一方会被告知是哪一项操作占着它。

## 值得知道的限制 {#limits-worth-knowing}

- 那六种数值类型就是总线编解码的全部词汇；不提供其他宽度的整数和 Double。
- 吞吐是总线的属性：线上每一台设备共用同一条请求流，节奏由实际生效的帧间静默决定，所以设备一多，或者间隔一抬高，每一轮轮询周期都会变长。
