欧姆龙作为日系PLC的代表品牌,在国内自动化领域有广泛应用。其上位机通讯主要使用两种协议:经典的Host Link协议(串口)和现代的FINS协议(以太网)。在工厂升级改造中,往往需要同时对接老旧CP1H设备和新一代NJ/NX设备,理解两种协议的差异与对接实现至关重要。本文结合赢式科技在上海食品厂、苏州包装机械、无锡物流分拣等60+欧姆龙PLC项目的实战经验,深度解析Host Link与FINS协议的对比与C#上位机对接实战。
一、欧姆龙PLC产品线与协议演进
欧姆龙PLC产品线丰富,不同系列支持的通讯协议差异显著,先理清产品线是协议选型的基础。
| 系列 | 典型型号 | 定位 | 支持协议 |
|---|---|---|---|
| CP系列 | CP1H/CP1L/CP1E | 小型PLC | Host Link(串口)、FINS/TCP(需以太网模块) |
| CJ系列 | CJ2M/CJ1M | 中型PLC | Host Link、FINS/TCP/UDP、Controller Link |
| CS系列 | CS1G/CS1H | 大型PLC | Host Link、FINS、Controller Link |
| NJ系列 | NJ501/NJ301 | 机器自动化控制器 | FINS/TCP/UDP、OPC UA、EtherCAT |
| NX系列 | NX1P/NX7 | 新一代小型PLC | FINS、OPC UA、EtherCAT |
二、Host Link协议深度解析
2.1 协议原理
Host Link协议是欧姆龙早期上位机通讯协议,基于主从架构,上位机为主站,PLC为从站。物理层通常为RS232C或RS422/RS485,链路层基于HDLC,报文使用ASCII字符表示。
Host Link报文结构:
@ | 单元号 | 头代码 | 文本 | FCS | * | CR
| 01 | RR | ... | 5A | * | \r
- @: 起始符
- 单元号: 00-31,PLC站号
- 头代码: 命令类型(RR=读CIO区, WR=写CIO区等)
- 文本: 命令参数与数据
- FCS: 帧校验(2字节ASCII)
- *: 结束符
- CR: 回车符(0x0D)
2.2 常用命令代码
| 头代码 | 命令 | 说明 | 示例 |
|---|---|---|---|
| RR | 读CIO区 | 读取CIO区域继电器 | @00RR00010001 → 读CIO 0001 |
| WR | 写CIO区 | 写入CIO区域继电器 | @00WR000100014567 → 写4567 |
| RL | 读LR区 | 读取链接继电器 | @00RL0100... |
| RD | 读DM区 | 读取数据存储器 | @00RD010001 → 读DM0100 |
| WD | 写DM区 | 写入数据存储器 | @00WD0100000A → 写10 |
| TS | 读状态 | 读取PLC运行状态 | @00TS |
| SC | 写状态 | 切换PLC运行/停止 | @00SC03 → 启动运行 |
2.3 C#实现Host Link通讯
using System.IO.Ports;
public class OmronHostLinkClient
{
private SerialPort _serialPort;
public void Connect(string portName, int baudRate = 9600,
Parity parity = Parity.Even, int dataBits = 7, StopBits stopBits = StopBits.Two)
{
_serialPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits)
{
ReadTimeout = 2000,
WriteTimeout = 2000,
NewLine = "\r"
};
_serialPort.Open();
}
/// <summary>读取DM区寄存器</summary>
public short ReadDm(ushort dmAddress)
{
// 构造命令:@00RD010001{FCS}*\r
string cmd = $"@00RD{dmAddress:D4}0001";
string fcs = CalculateFcs(cmd);
string frame = $"{cmd}{fcs}*\r";
_serialPort.WriteLine(frame);
string response = _serialPort.ReadLine();
// 校验响应FCS
if (!ValidateFcs(response))
throw new Exception("FCS校验失败");
// 响应格式:@00RD00{data}{FCS}*\r
string dataHex = response.Substring(6, 4);
return Convert.ToInt16(dataHex, 16);
}
/// <summary>计算FCS校验</summary>
private string CalculateFcs(string data)
{
byte fcs = 0;
foreach (char c in data)
{
fcs ^= (byte)c;
}
return fcs.ToString("X2");
}
private bool ValidateFcs(string response)
{
if (!response.StartsWith("@") || !response.EndsWith("*\r"))
return false;
string data = response.Substring(0, response.Length - 3);
string fcsInFrame = data.Substring(data.Length - 2);
string calculatedFcs = CalculateFcs(data.Substring(0, data.Length - 2));
return fcsInFrame.Equals(calculatedFcs);
}
}
三、FINS协议深度解析
3.1 协议原理
FINS(Factory Interface Network Service)是欧姆龙专为工业以太网设计的应用层协议,支持TCP与UDP两种传输模式,TCP模式默认端口9600。FINS协议使用二进制报文,效率高于Host Link的ASCII格式。
FINS报文结构(TCP模式):
FINS帧结构:
┌──────────┬──────────┬──────────┐
│ FINS头 │ 命令数据 │ 数据 │
│ (12字节) │ (4字节) │ (变长) │
└──────────┴──────────┴──────────┘
TCP封装(在FINS帧前加4字节长度头):
┌────────────┬───────────────────────────┐
│ 长度(4字节)│ FINS帧 │
│ 大端序 │ │
└────────────┴───────────────────────────┘
FINS头字段:
- 信息头(2字节): 0x8000(请求)/0xC000(响应)
- 源网络号(1字节)
- 源节点号(1字节)
- 源单元号(1字节)
- 目标网络号(1字节)
- 目标节点号(1字节)
- 目标单元号(1字节)
- 服务ID(1字节)
3.2 常用FINS命令
| 命令代码 | 子命令 | 功能 | 说明 |
|---|---|---|---|
| 0101 | 0000 | 读CIO区 | 读取CIO区域数据 |
| 0102 | 0000 | 写CIO区 | 写入CIO区域数据 |
| 0101 | 0001 | 读DM区 | 读取DM区域数据 |
| 0102 | 0001 | 写DM区 | 写入DM区域数据 |
| 0101 | 0009 | 读定时器PV | 读取定时器当前值 |
| 0601 | - | 读PLC状态 | 读取运行/停止状态 |
| 0401 | 0000/0001 | 切换状态 | 切换运行/停止 |
3.3 C#实现FINS TCP通讯
using System.Net.Sockets;
using System.Text;
public class OmronFinsTcpClient
{
private TcpClient _tcpClient;
private NetworkStream _stream;
private byte _sourceNode = 0;
private byte _destNode = 0;
private byte _serviceId = 0;
public async Task ConnectAsync(string ip, int port = 9600)
{
_tcpClient = new TcpClient();
await _tcpClient.ConnectAsync(ip, port);
_stream = _tcpClient.GetStream();
// FINS/TCP连接握手
await SendConnectionHandshakeAsync();
}
private async Task SendConnectionHandshakeAsync()
{
// 客户端节点号信息(固定格式)
byte[] handshake = new byte[20];
handshake[0] = 0x46; // 'F'
handshake[1] = 0x49; // 'I'
handshake[2] = 0x4E; // 'N'
handshake[3] = 0x53; // 'S'
handshake[4] = 0x00; handshake[5] = 0x00; handshake[6] = 0x00; handshake[7] = 0x0C;
handshake[8] = 0x00; handshake[9] = 0x00; handshake[10] = 0x00; handshake[11] = 0x00;
handshake[12] = 0x00; handshake[13] = 0x00; handshake[14] = 0x00; handshake[15] = 0x01;
// 客户端节点号
handshake[16] = 0x00; handshake[17] = 0x00; handshake[18] = 0x00; handshake[19] = _sourceNode;
await _stream.WriteAsync(handshake);
byte[] response = new byte[24];
await _stream.ReadAsync(response, 0, 24);
_destNode = response[23]; // 服务端返回的PLC节点号
}
/// <summary>读取DM区寄存器</summary>
public async Task<short> ReadDmAsync(ushort dmAddress, ushort count = 1)
{
byte[] finsHeader = BuildFinsHeader();
byte[] command = BuildReadCommand(0x02, dmAddress, count); // 0x02 = DM区代码
byte[] request = new byte[finsHeader.Length + command.Length];
Buffer.BlockCopy(finsHeader, 0, request, 0, finsHeader.Length);
Buffer.BlockCopy(command, 0, request, finsHeader.Length, command.Length);
// TCP封装:4字节长度头 + FINS帧
byte[] lengthPrefix = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(request.Length));
await _stream.WriteAsync(lengthPrefix);
await _stream.WriteAsync(request);
// 读取响应
byte[] respLenBytes = new byte[4];
await _stream.ReadAsync(respLenBytes, 0, 4);
int respLen = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(respLenBytes, 0));
byte[] respData = new byte[respLen];
await _stream.ReadAsync(respData, 0, respLen);
// 检查响应状态(偏移12-13为结束代码,00 00表示成功)
if (respData[12] != 0x00 || respData[13] != 0x00)
throw new Exception($"FINS读取失败 错误码: 0x{respData[12]:X2}{respData[13]:X2}");
// 数据从偏移14开始,大端序
return (short)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(respData, 14));
}
private byte[] BuildFinsHeader()
{
return new byte[]
{
0x80, 0x00, // 信息头(请求)
0x00, // 源网络号(本地网络)
_sourceNode, // 源节点号
0x00, // 源单元号
0x00, // 目标网络号
_destNode, // 目标节点号
0x00, // 目标单元号(PLC CPU)
++_serviceId // 服务ID(每次递增)
};
}
private byte[] BuildReadCommand(byte areaCode, ushort address, ushort count)
{
return new byte[]
{
0x01, 0x01, // 命令: 读区域
0x00, 0x00, // 子命令: 默认
(byte)((address >> 8) & 0xFF), // 起始地址(高字节)
(byte)(address & 0xFF), // 起始地址(低字节)
0x00, // 位地址
areaCode, // 区域代码(0x02=DM)
(byte)((count >> 8) & 0xFF), // 数量(高字节)
(byte)(count & 0xFF) // 数量(低字节)
};
}
}
四、CP1H与NJ系列对接差异
在工厂改造中,常见CP1H与NJ系列并存的情况,二者的对接方式存在显著差异:
| 对比维度 | CP1H(经典系列) | NJ(新一代) |
|---|---|---|
| 原生通讯接口 | RS232串口 | 以太网(双口) |
| 主推协议 | Host Link(串口) | FINS/TCP + OPC UA |
| 以太网扩展 | 需CP1W-CIF41模块 | 原生支持 |
| 地址空间 | CIO/DM/LR/HR等 | 变量(符号化) |
| 符号访问 | 仅绝对地址 | 支持符号名访问 |
| OPC UA支持 | 不支持 | 内置服务器 |
| 典型通讯速率 | 9600bps(串口) | 100Mbps(以太网) |
| 开发复杂度 | 中等(需封装Host Link) | 低(OPC UA原生) |
4.1 CP1H以太网扩展方案
CP1H本身仅有串口,扩展以太网有两种方案:
- 方案A:CP1W-CIF41以太网模块:直接插在CP1H扩展槽,支持FINS/TCP,配置简单,约1500元
- 方案B:串口服务器+Host Link over TCP:将CP1H串口接入串口服务器(Moxa NPort等),上位机通过TCP封装Host Link报文,成本低约500元
五、协议选型建议
基于60+项目的实战经验,针对不同场景给出协议选型建议:
| 场景 | 推荐协议 | 选型理由 |
|---|---|---|
| CP1H老设备无网口 | Host Link(串口) | 无需硬件改造,成本最低 |
| CP1H+ CIF41模块 | FINS/TCP | 性能优于串口,支持远程 |
| CJ2M中型PLC | FINS/TCP | 原生以太网,性能稳定 |
| NJ/NX新设备 | OPC UA | 内置服务器,符号化访问 |
| 多PLC混采 | FINS统一 | 协议栈复用,便于维护 |
| 跨厂商整合 | OPC UA | 标准化,跨品牌通用 |
六、实际项目案例
2026年5月,赢式科技承接上海某食品厂的产线数据采集改造项目,现场设备情况:
- 8台CP1H(2008年采购,仅串口) → 用Host Link over TCP方案
- 3台CJ2M(2015年采购) → 用FINS/TCP
- 2台NJ501(2022年采购) → 用OPC UA
- 总计采集点位1800点
项目实施关键数据:
| 设备类型 | 通讯协议 | 采集周期 | 稳定性 |
|---|---|---|---|
| CP1H(串口) | Host Link | 2秒 | 99.7% |
| CJ2M(以太网) | FINS/TCP | 500ms | 99.95% |
| NJ501(以太网) | OPC UA | 200ms | 99.98% |
七、总结
欧姆龙PLC的协议演进清晰反映了工业通讯从串口到以太网、从专有到开放的演进趋势。Host Link作为经典协议,在老旧CP1H设备改造中仍不可或缺;FINS作为过渡的以太网协议,在CJ/CS系列中广泛应用;OPC UA则是NJ/NX新一代控制器的首选。赢式科技基于60+欧姆龙PLC项目积累,形成了Host Link/FINS/OPC UA的统一开发框架,支持CP/CJ/CS/NJ/NX全系列欧姆龙PLC的快速接入,项目平均交付周期3-5周,系统可用率99.95%以上。
需要欧姆龙PLC上位机对接服务?
赢式科技提供免费需求调研、30分钟出方案、2万元起接单,服务覆盖上海、苏州、无锡、昆山等长三角城市,24小时上门交付。咨询电话:15001875806