欧姆龙 FINS 协议通讯
Darra PLC 通过 FINSProtocolAdapter 支持欧姆龙 FINS (Factory Interface Network Service) 协议, 可与 CP/CJ/NX 系列 PLC 通讯。
支持系列
| 系列 | 型号 | 传输层 | 说明 |
|---|---|---|---|
| CP 系列 | CP1E/CP1H/CP1L | TCP/UDP | 内置以太网或模块 |
| CJ 系列 | CJ1M/CJ2M/CJ2H | TCP/UDP | 以太网模块 CJ1W-ETN21 |
| CS 系列 | CS1G/CS1H/CS1D | TCP/UDP | 以太网模块 |
| NX 系列 | NX1P2/NX102/NX502 | TCP/UDP | 内置以太网 |
| NJ 系列 | NJ101/NJ301/NJ501 | TCP/UDP | 内置以太网 |
传输层选择
| 传输层 | 端口 | 特点 |
|---|---|---|
| UDP | 9600 | 低延迟, 无连接, 适合局域网 |
| TCP | 9600 | 可靠传输, 支持跨子网, 推荐 |
推荐
生产环境建议使用 FINS/TCP 模式, 稳定性更好。
存储区访问
| 存储区 | 标识 | 地址范围 | 数据类型 |
|---|---|---|---|
| CIO 通道 IO | CIO | CIO0 ~ CIO6143 | WORD/BIT |
| WR 工作区 | WR | WR0 ~ WR511 | WORD/BIT |
| HR 保持区 | HR | HR0 ~ HR511 | WORD/BIT |
| DM 数据区 | DM | DM0 ~ DM32767 | WORD |
| AR 辅助区 | AR | AR0 ~ AR959 | WORD/BIT (只读) |
| EM 扩展数据 | EM | EM0 ~ EM32767 | WORD |
| TIM 定时器 | TIM | TIM0 ~ TIM4095 | PV/完成标志 |
| CNT 计数器 | CNT | CNT0 ~ CNT4095 | PV/完成标志 |
连接配置
{
"protocol": "OmronFINS",
"host": "192.168.1.30",
"port": 9600,
"transport": "TCP",
"sourceNode": 0,
"destinationNode": 0,
"sourceUnit": 0,
"destinationUnit": 0,
"timeout": 3000
}
节点地址
FINS 协议使用节点地址 (SA1/DA1) 路由。若 PLC 与 Darra 在同一子网, 节点地址通常为 IP 地址的最后一个字节。例如 PLC IP 为 192.168.1.30, 则 DA1=30。
PLC 变量映射
欧姆龙 FINS 地址 Darra PLC 变量
DM0 ←→ MW3000 (INT)
DM100 ←→ MD3200 (DINT)
CIO0.00 ←→ IX20.0 (BOOL, 只读)
CIO100.00 ←→ QX20.0 (BOOL)
WR0 ←→ MW3500 (INT)
代码示例
// 创建 FINS 连接
var adapter = PLCDriverFactory.Create(ProtocolId.OmronFINS);
await adapter.ConnectAsync(new ConnectionConfig
{
Host = "192.168.1.30",
Port = 9600,
Transport = FINSTransport.TCP,
SourceNode = 1,
DestinationNode = 30
});
// 读取 DM0~DM9 (10 个 WORD)
var data = await adapter.ReadAsync("DM0", 10);
short value = BitConverter.ToInt16(data, 0);
// 写入 CIO100.00 (置位)
await adapter.WriteAsync("CIO100.00", new byte[] { 0x01 });
错误处理
FINS 协议返回标准错误码:
| 主码 | 子码 | 含义 |
|---|---|---|
| 0x00 | 0x00 | 正常完成 |
| 0x01 | 0x01 | 本地节点不在网络中 |
| 0x02 | 0x01 | 目的节点不在网络中 |
| 0x04 | 0x01 | 数据长度超限 |
| 0x11 | 0x01 | 存储区访问错误 |
| 0x15 | 0x01 | 地址范围超限 |
FINS 帧结构
FINS/UDP 帧
| ICF | RSV | GCT | DNA | DA1 | DA2 | SNA | SA1 | SA2 | SID | MRC | SRC | TEXT |
- ICF (Info Control Field): 0x80 = 请求, 0xC0 = 响应
- GCT (Gateway Count): 跨网关跳数, 通常 0x02
- DNA (Destination Network Address): 目的网络号, 同网段填 0
- DA1 (Destination Node Address): 目的节点地址
- DA2 (Destination Unit Address): 目的单元地址, CPU = 0x00
- SNA/SA1/SA2: 源地址三元组
- SID (Service ID): 客户端分配, 用于匹配响应
- MRC/SRC: 主命令码 / 子命令码
- TEXT: 命令数据
FINS/TCP 帧
TCP 模式在 FINS 头之前加一个 20 字节的 FINS/TCP 头, 含帧长度和帧类型 (握手/数据/错误)。握手阶段客户端和服务器交换节点地址。
握手流程 (TCP)
客户端 → 服务器: FINS/TCP Node Address Data Send
(客户端自己期望的节点号, 一般 0 = 让服务器分配)
客户端 ← 服务器: FINS/TCP Node Address Data Send Reply
(服务器分配的客户端节点号 + 自己的节点号)
(后续所有 FINS 命令都包含双方节点号)
Darra 侧在 ConnectAsync 时自动完成握手, 并缓存节点号供后续请求使用。
FINS 命令码
| MRC | SRC | 功能 |
|---|---|---|
| 0x01 | 0x01 | 存储区读取 |
| 0x01 | 0x02 | 存储区写入 |
| 0x01 | 0x03 | 存储区填充 |
| 0x01 | 0x04 | 多地址读取 |
| 0x01 | 0x05 | 多地址写入 |
| 0x01 | 0x06 | 存储区传送 (块间复制) |
| 0x04 | 0x01 | CPU 运行 |
| 0x04 | 0x02 | CPU 停止 |
| 0x05 | 0x01 | CPU 单元读取 |
| 0x06 | 0x01 | CPU 状态读取 |
| 0x07 | 0x01 | 时钟读取 |
| 0x07 | 0x02 | 时钟写入 |
| 0x21 | 0x02 | 错误清除 |
| 0x22 | 0x02 | 错误日志读取 |
存储区代码
FINS 用单字节代码识别存储区:
| 存储区 | 字读取码 | 位读取码 |
|---|---|---|
| CIO | 0xB0 | 0x30 |
| WR | 0xB1 | 0x31 |
| HR | 0xB2 | 0x32 |
| AR | 0xB3 | 0x33 |
| DM | 0x82 | 0x02 |
| EM (bank 0) | 0xA0 | 0x20 |
| EM (bank 1) | 0xA1 | 0x21 |
| EM (当前 bank) | 0x98 | 0x18 |
适配器根据地址文本 (DM100, CIO50.3) 自动选择代码。
节点地址规划
FINS 使用节点地址做路由, 同一网络内节点号唯一:
PLC1 (NodeId=1) PLC2 (NodeId=2) Darra PLC (NodeId=100)
│ │ │
└──────────────┴──────────────────┘
同一 FINS 网络 (网络号=1)
配置建议:
- PLC IP 最后一字节 == 节点号 (容易记忆)
- 或独立分配, 写到电子表格备案
- 同网内避免冲突
批量读写
多地址读取 (MRC=0x01 SRC=0x04)
一次请求多个不连续地址, 但每个地址只能读 1 个字:
var items = new[] { "DM0", "DM100", "CIO5", "WR20" };
var values = await adapter.ReadMultipleAsync(items);
适合读少量状态变量。
连续读取
ReadAsync("DM0", 100) 一次读 100 个字, 比循环读 100 次快几十倍。
CPU 状态与控制
// 读 CPU 状态
var status = await adapter.ReadCpuStatusAsync();
// status.Mode: Program / Monitor / Run
// status.ErrorCode
// 远程控制
await adapter.StartCpuAsync(); // 进入 Run 模式
await adapter.StopCpuAsync(); // 进入 Program 模式
CPU 上的拨码开关优先级高于远程命令。设成 Program 拨码时, 远程启动无效。
时钟同步
var plcTime = await adapter.ReadClockAsync();
await adapter.WriteClockAsync(DateTime.Now);
CP 系列的时钟精度约 ±5 秒/天, 远程同步频率建议 1 次/小时。
EM 存储区分页
EM (扩展数据存储) 分为多个 bank, 每 bank 32K 字:
| bank | 大小 | 访问方式 |
|---|---|---|
| bank 0 | 32K W | EM0:0 (推荐显式带 bank 号) |
| bank 1 | 32K W | EM1:0 |
| ... | ||
| bank F | 32K W | EMF:0 |
| current | EM:0 (按当前激活 bank) |
多 bank 配置下必须指定 bank, 否则可能读错区。
错误处理
完整错误码表见 Omron W227/W421 手册附录。常见:
| 主码:子码 | 含义 | 处理 |
|---|---|---|
| 00:00 | 正常 | - |
| 01:01 | 本地节点不存在 | 节点号在 FINS 网络里没配 |
| 02:01 | 目的节点不存在 | 核对目的节点号 |
| 03:02 | 多地址读越界 | 减少请求项数 |
| 11:01 | 存储区越界 | 检查地址范围 |
| 11:03 | 起始地址越界 | 同上 |
| 21:01 | 写保护 | CPU 在 Run 模式禁止写某些区 |
| 22:02 | CPU 故障 | 清错 (MRC=0x21 SRC=0x02) |
| 23:03 | FINS 包非法 | 包长/头格式错 |
排错
| 症状 | 处理 |
|---|---|
| 连接建立但命令超时 | 节点号配错, 握手成功不代表节点号对 |
| 间歇性 02:01 | 跨网关时 GCT 字段不够 |
| DM 能读 CIO 不能读 | 检查 CP/NJ CPU 的存储区访问权限 |
| EM 访问返回 11:01 | EM 不存在 (CP1E 等小型无 EM) |
| UDP 模式丢包 | 改用 TCP |
| 中文字符串乱码 | 欧姆龙默认 Shift-JIS, 非 UTF-8 |