# Rockwell EtherNet/IP

> 通过 EtherNet/IP 连接 Allen-Bradley 控制器：ControlLogix、CompactLogix、PLC-5、SLC 500、MicroLogix 和 Micro800，它们的点位地址、数据类型和网络设备发现。

**Rockwell EtherNet/IP** 驱动通过 EtherNet/IP 和 Allen-Bradley / Rockwell 的控制器对话。ControlLogix 和 CompactLogix 控制器按 Logix 项目里声明的控制器标签名寻址，Micro800 按它的全局变量名寻址，PLC-5、SLC 500 和 MicroLogix 按数据文件地址寻址，完全不涉及寄存器映射。在 [Connector](connector) 里的 Rockwell EtherNet/IP 驱动下面添加一台设备，并选好它的**控制器系列**；面板只显示这个系列用得到的那些字段。

## 控制器系列 {#controller-families}

设备的**控制器系列**决定怎样够到控制器，以及它的点位怎样寻址：

| 控制器系列 | 控制器 | 点位寻址方式 | 槽位 |
| --- | --- | --- | --- |
| **ControlLogix / CompactLogix** | ControlLogix、CompactLogix | **控制器标签**，即标签名 | 有 |
| **PLC-5** | 带以太网口的 PLC-5（PLC-5/20E、/40E、/80E） | **数据文件地址**，例如 `N7:0` | 无 |
| **SLC 500** | SLC 5/05 | **数据文件地址** | 无 |
| **MicroLogix** | MicroLogix 1100 和 1400，其他型号经 1761-NET-ENI | **数据文件地址** | 无 |
| **Micro800** | Micro820、Micro850、Micro870 | **控制器标签**，即全局变量名 | 无 |

默认是 ControlLogix / CompactLogix，在这个字段出现之前建的设备也属于它，所以读取方式和以前完全一样。只有 ControlLogix 或 CompactLogix 控制器经背板在某个槽位上够到；其他系列都在自己的以太网口上应答，所以它们的设备没有**槽位**字段。设备发现从控制器的身份里读出系列，所以找到的控制器到达时系列已经选好。

## 连接 {#connection}

| 字段 | 含义 | 默认值 |
| --- | --- | --- |
| **主机** | 控制器以太网口的 IP 地址或主机名。 | 空 |
| **控制器系列** | 控制器所属的系列；它决定下面的字段以及点位怎样寻址。 | ControlLogix / CompactLogix |
| **槽位** | 仅限 ControlLogix / CompactLogix：控制器插在机箱的第几位，从 0 数起。 | 0 |
| **轮询间隔（ms）** | 整台设备的读取周期，供没有自己另作规定的点位使用。 | 1000 |

没有端口字段：EtherNet/IP 用它标准的 TCP 端口 44818。CIP 路由是背板加上**槽位**，所以控制器插在机箱哪一位都够得着；发现出来的控制器从槽位 0 开始，那个字段就是你改正它的地方。没有机架字段：CIP 路由里不带机架号。请求 10 秒之后超时。设备标题栏显示拼好的 endpoint，例如 `eip://192.168.0.10:0`，地址后面那个数字就是槽位。

其他系列的控制器不带槽位，标题栏改为写出系列，例如 `eip://192.168.0.20?family=slc500`。

每个点位都可以设自己的**轮询间隔（ms）**；点位填 0 就退回设备的间隔。

## 寻址 {#addressing}

Rockwell 的点位靠点位“信号源”卡片里的两个字段寻址：

| 字段 | 含义 | 接受的取值 | 默认值 |
| --- | --- | --- | --- |
| **控制器标签** | 控制器里声明的那个标签名。必填。 | 自由文本 | 空 |
| **标签内偏移量（字节，BOOL 为位）** | 这个值落在那个标签数据里的什么位置：一个字节偏移，或者对布尔点位来说是一个位偏移。 | 0–255 | 0 |

驱动按名字打开那个控制器标签，再从它的数据缓冲区里按偏移把值挑出来。对一个普通的原子标签，偏移保持 0。正是这个偏移让一个控制器标签能服务好几个通道：一个结构开头的 DINT 是偏移 0，四个字节之后的 REAL 是偏移 4，而对布尔点位来说，偏移数的是位而不是字节。

| 要读的东西 | 控制器标签 | 偏移 | 数据类型 |
| --- | --- | --- | --- |
| 控制器作用域的 DINT `Counter` | `Counter` | 0 | Int32 |
| `Main` 里程序作用域的一个 DINT | `Program:Main.Counter` | 0 | Int32 |
| REAL 数组的一个元素 `Temps[2]` | `Temps[2]` | 0 | Float |
| BOOL 数组的一个元素 `Flags[5]` | `Flags[5]` | 0 | Boolean |
| 结构 `Recipe` 里的第二个 DINT | `Recipe` | 4 | Int32 |
| `Flags` 数据里的第 3 位 | `Flags` | 3 | Boolean |

在 BOOL 数组里，索引指的是那个 BOOL，而不是控制器的 32 位字：`Flags[5]` 就是编号为 5 的 BOOL；按位的偏移从所指的 BOOL 往后数，所以 `Flags[5]` 加偏移 2 就是编号为 7 的 BOOL。

名字是整个送到控制器那里的，中间的点也在内，而同一个标签的读和写落在同一个地方。正因如此，`Program:Main.Counter` 这种程序作用域的名字才管用。带点的成员路径（`MyUdt.Member`）在控制器按名字发布那个成员时由控制器解析；控制器不发布时，就点名基础的控制器作用域标签，再用字节偏移伸进去，这条路永远走得通。“信号源”卡片里那行只读的**源地址（总线）**会随着你编辑显示拼好的地址。

Micro800 也这样寻址，用 `Level` 这样的全局变量名：它没有程序范围的标签可写。

### 数据文件地址 {#data-file-addresses}

PLC-5、SLC 500 或 MicroLogix 的点位只靠一个字段寻址，即**数据文件地址**：依次写文件字母和编号、冒号和元素，然后在点号后写成员，或在斜杠后写位。没有偏移量字段，因为地址自己写明了元素、成员和位；驱动读不了的地址，这个字段在你输入时就会拒绝，并说明原因。

| 文件 | 示例 | 读取 | 数据类型 |
| --- | --- | --- | --- |
| 整数（N） | `N7:0`、`N7:0/15` | 一个 16 位字，或它的某一位 | Int16 或 UInt16；位为 Boolean |
| 位（B） | `B3:0`、`B3:0/2` | 一个 16 位字，或它的某一位 | Int16 或 UInt16；位为 Boolean |
| 浮点（F） | `F8:1` | 一个 32 位浮点值 | Float |
| 长整数（L） | `L9:0` | 一个 32 位整数 | Int32 或 UInt32 |
| 定时器（T） | `T4:0.ACC`、`T4:0.PRE`、`T4:0.DN` | ACC 或 PRE，或者 EN、TT、DN 位 | Int16 或 UInt16；位为 Boolean |
| 计数器（C） | `C5:0.ACC`、`C5:0.PRE`、`C5:0.DN` | ACC 或 PRE，或者 CU、CD、DN、OV、UN 位 | Int16 或 UInt16；位为 Boolean |
| 控制（R） | `R6:0.LEN`、`R6:0.POS` | LEN 或 POS | Int16 或 UInt16 |
| 状态（S） | `S:1`、`S:1/15` | 状态文件的一个字，或它的某一位 | Int16 或 UInt16；位为 Boolean |
| 输入（I）、输出（O） | `I:0`、`I:0/1`、`O:0/1` | 输入或输出映像的一个字，或它的某一位 | Int16 或 UInt16；位为 Boolean |

可选的**数据类型**跟随文件；输入一个地址，如果它的文件不存放点位当前的类型，类型就会改成该文件的类型。状态、输入和输出文件可以像编程软件那样省略编号来写；驱动会把它们作为 `S2:1`、`I1:0/1` 和 `O0:0/1` 发送，**源地址（总线）**那一行会显示出来。在输入或输出地址里，元素是该字在映像中的位置：MicroLogix 的内置输入从 `I:0/0` 开始。定时器、计数器或控制元素通过它的某个成员读取，从不整体读取；字符串、ASCII、BCD、消息和 PID 文件不读取。PLC-5 用八进制为输入和输出编号，驱动不读取这种编号，所以它的 I 和 O 文件会被拒绝：请让程序把这样的值复制到一个数据文件里，再通过该数据文件读取。文件是否存在要读了才知道：控制器没有的文件（没有 N9 时的 `N9:0`）从该点位第一次读取起就读作错误，设备的其他点位照常读取。

## 数据类型 {#data-types}

每一种 CIP 原子类型都能作为点位的**数据类型**：

| 数据类型 | CIP 类型 | 大小 |
| --- | --- | --- |
| Boolean | BOOL | 位偏移处的 1 个位 |
| SByte | SINT | 1 字节 |
| Byte | USINT | 1 字节 |
| Int16 | INT | 2 字节 |
| UInt16 | UINT | 2 字节 |
| Int32 | DINT | 4 字节 |
| UInt32 | UDINT | 4 字节 |
| Int64 | LINT | 8 字节 |
| UInt64 | ULINT | 8 字节 |
| Float | REAL | 4 字节 |
| Double | LREAL | 8 字节 |

不支持结构和 UDT：只有上面那些原子类型能读能写，而字节偏移的存在，正是为了伸进它们里面去够成员。选择器里刻意没有 String：Logix 的 STRING 不是原子类型，而是一个由长度和一个 82 字节数组组成的结构，驱动既读不了也写不了。总线上的字节序就是 CIP 自己的小端序，由驱动自动处理；没有字节序选项。

在 PLC-5、SLC 500 或 MicroLogix 上，类型改为跟随数据文件，见上面的数据文件地址表。

## 写入 {#writes}

**访问**为读写或只写的点位可以被写。一次写入只送出要写的那个值，旁边的东西一概不带，所以同一瞬间在控制器里变了的某个位或某个成员，绝不会被改回片刻之前的样子：

| 点位寻址的是 | 写入送出的是 |
| --- | --- |
| 一个完整的原子标签（偏移 0，大小就是标签本身的大小），或一个 BOOL 标签 | 这个值，写到那个标签 |
| 整数标签里的一个位（`Flags`，偏移 3，Boolean） | 只有这个位，写成 `Flags.3`：控制器就地改掉它，其他位原样不动 |
| BOOL 数组的一个元素（`Flags[5]`，Boolean） | 只有这个 BOOL，作为数组第一个字的第 5 位（`Flags[0].5`）：控制器就地改掉它，这个字里其他的 BOOL 原样不动 |
| 结构里的一个值（`Recipe`，偏移 4） | 只有这个成员，按名字写（`Recipe.Speed`），名字是从控制器发布的结构定义里找到的 |

只能把标签其余部分一起写回才能完成的写入会被拒绝，拒绝理由会点出这个地址：原子标签的一部分（DINT 偏移 2 处的一个 Int16）、REAL 的一个位，或者结构里没有哪个成员以这个值的大小从该偏移开始。这时请按值自己的名字寻址，例如 `Recipe.Speed`，偏移 0。驱动在一个点位第一次写入时弄清它的写入落在哪里，并在设备保持连接期间记住这个结果。新点位一开始是只读。

数据文件地址按它所写的样子写入：字、浮点值或成员整体写入，位（`B3:0/2`、`T4:0.DN`）只写这一位，控制器在原处修改它，字里的其他位保持不变。

## 设备发现 {#discovery}

Connector 的“发现设备”按钮会在**本机所在的每一个网络上**广播一个 CIP List Identity 请求，并一直听下去，直到有 10 秒没有新的回答为止。扫描期间它会把那些网络点出来，所以结果为零时你知道这个问题究竟发到哪里去了：一台装着给虚拟机、WSL 或 VPN 用的虚拟网卡的工作站，网络往往比操作员以为的多，候选项也可能是从其中某一个上来的。

承载不了这个请求的接口会被跳过：任何没有起来的接口、回环和隧道适配器，以及接口在没人应答 DHCP 时自己给自己取的那种地址（169.254.x.x）。只有确实是控制器自身身份回答的应答才成为候选设备，名字是 `eip://` 加上它的地址；在 EtherNet/IP 端口上应答的其他任何东西都被忽略，并在诊断日志里说明。Omron 控制器也会应答同一个请求，同样被排除，并在诊断日志里单独记一行：NJ、NX 或 NY 控制器改由 [Omron](driver-omron#discovery) 驱动的扫描提供。随后，扫描为每一个候选项读取控制器作用域的标签列表，把它的那些原子标签作为候选通道提供出来，每一个的偏移都是 0。原子类型的数组以它的第一个元素、按那个类型提供（`Temps[0]`，两维时是 `Grid[0,0]`），BOOL 数组则以它第一个 BOOL 上的一个 Boolean 提供（`Flags[0]`）。

设备发现会把驱动轮询不了的东西丢掉：结构和 UDT 标签（含 Logix 的 STRING）被丢掉，`Program:`、`Task:` 和 `Map:` 这几类条目被跳过。一台不在本机所在网络上的控制器不会应答广播；请手工添加它。其他任何类型的标签，比如系统类型，也不会提供出来，并在诊断日志里说明。

控制器应答的身份里带有它的目录号，目录号写明了它的系列：PLC-5、SLC 500、MicroLogix 或 Micro800 到达时系列已经选好，地址里也写着它（`eip://192.168.0.20?family=micrologix`）。PLC-5、SLC 500 和 MicroLogix 不公布点位清单，所以到达时没有候选通道，它们的点位按数据文件地址填写；Micro800 的点位清单和 Logix 的一样读取。

已经有的 Logix 或 Micro800 控制器可以在自己的页面上重新读它的标签列表：“点位”卡片标题栏里的**发现点位**只列出它还没有点位的控制器标签，按名称比较，不区分大小写。数据文件型控制器没有可读的列表，也没有“发现点位”（见[发现一台设备的点位](connector-discovery#discovering-a-devices-tags)）。

## 需要知道的事 {#good-to-know}

- 发往控制器的每一个请求都在 10 秒之后超时，而设备发现的监听窗口在沉默 10 秒之后关闭。
- 驱动把每一个控制器标签只打开一次，并在设备保持连接期间一直开着，所以带着几十个标签的控制器是在它已经有的会话上读的，而不是每读一次就开一次、关一次。读失败的那一次会放掉自己的标签，下一次读再按名字把它打开。
- 在 Logix 项目里给标签重命名会悄悄把这个通道弄断；在配置的名字重新对上之前，这个点位读作质量不可用。
