推荐使用omronfinsnet(hslcommunication)实现fins/tcp通信,因其封装完整、支持自动重试与错误码映射、地址格式贴近plc编程习惯,且默认适配大小端,一行代码即可读写d/cio/hr/wr区,省去手动拼帧、fcs计算及节点号配置等复杂细节。

直接用 OmronFinsNet(来自 HslCommunication.Profinet.Omron)是最省事、最稳的选择,尤其对 FINS/TCP 场景。串口 Host Link 虽然还能用,但配置繁琐、校验易错、响应解析反直觉,新手掉坑里基本靠手册硬啃。
FINS/TCP 通信:用 HslCommunication 一行地址就能读写
绝大多数现代欧姆龙 PLC(CP/CJ/NJ/NX 系列)都支持 FINS/TCP,默认端口 9600,只要网线通、IP 配对、节点号不冲突,连上就跑得动。
-
OmronFinsNet封装了完整的帧构造、超时重试、字节序转换和错误码映射,你不用碰0x02 0x46 0x49...这类原始字节 - 地址写法完全贴近 PLC 编程习惯:
"D100"、"CIO0"、"HR10.0"、"WR200.3",不用手动补零或拆高低字节 - 读取返回的是
ushort[],写入也直接传ushort[],底层自动处理大小端——NJ/NX 默认小端,HslCommunication默认按此适配 - 连接失败时会明确告诉你错误码,比如
0x0020(目标节点未响应)、0x0030(内存区域不存在),比抓包看*00RD0000000000\r强太多
示例代码:
var plc = new OmronFinsNet("192.168.1.10", 9600);
var connectResult = plc.ConnectServer();
if (!connectResult.IsSuccess) {
Console.WriteLine($"连接失败: {connectResult.Message}");
return;
}
// 读 D100~D105 共 6 个字
var readResult = plc.ReadUInt16("D100", 6);
if (readResult.IsSuccess) {
ushort[] values = readResult.Content; // [123, 456, ...]
}
串口 Host Link:必须手算 FCS 且地址格式反直觉
老设备(如 CPM1A、CPM2A)没网口,只能走 RS-232/485,这时得自己拼 Host Link 帧。核心难点不在串口本身,而在协议细节:
- 地址必须严格四字符 ASCII:
"DM100"要写成"DM0100","CIO50"得是"CIO050",少一个零就返回*00RD0000000000\r - FCS 是对
@00RD...0001这段纯 ASCII 字符逐字节异或,不是对整个*@00RD...0001XX\r计算 - 响应数据中,
"00ABCD"表示一个 Word,但实际值是0xCDAB(低位在前),不能直接Convert.ToUInt16("ABCD", 16) -
SerialPort的DataReceived事件不可靠,建议用ReadExisting()+ 固定延时(Thread.Sleep(200))收完整帧,否则容易截断
关键片段:
string address = "DM" + addr.ToString("D4"); // DM100 → DM0100
string finsFrame = $"@00RD{address}0001";
byte fcs = 0;
foreach (char c in finsFrame) fcs ^= (byte)c;
string cmd = $"*{finsFrame}{fcs:X2}\r";
<p>sp.Write(cmd);
Thread.Sleep(200);
string resp = sp.ReadExisting().Trim();
// 解析 "00ABCD" → 先取后 4 字符,再反转字节顺序
string hexData = resp.Substring(resp.Length - 8, 4); // 示例取法,需按实际响应结构调整
byte[] bytes = new byte[2] {
Convert.ToByte(hexData.Substring(2, 2), 16),
Convert.ToByte(hexData.Substring(0, 2), 16)
};
ushort value = BitConverter.ToUInt16(bytes, 0);</p>
节点号与网络号配错:TCP 连不上最常卡在这儿
FINS/TCP 不是“IP 通就能连”,它有一套独立的寻址逻辑,和 IP 层无关。配错会导致静默失败(无异常,但 ConnectServer() 返回 false):
-
RemoteNode(PLC 节点号)默认是0,但在 NJ/NX 中可能被设为其他值(查 PLC 系统寄存器D8000或编程软件设置) -
LocalNode(上位机节点号)不能为0,推荐用100~254,避免和 PLC 冲突 - 网络号(
SA1/DA1)多数场景填0即可;若 PLC 在多网段,才需匹配其网络配置 - 有些型号(如 CP1E)需在 PLC 上启用“FINS 通信”功能(通过 DIP 开关或系统设置),否则端口监听都不开
为什么别自己从头写 FINS/TCP 帧
看似只是拼几个字节,但实际要处理的边界远超想象:
- 事务 ID(
SID)必须递增且不能重复,否则 PLC 可能丢弃后续请求 - 帧长度字段(
0x00 0x00位置)要动态计算并填充,漏填或填错导致 PLC 直接断连 - 读写命令的地址编码方式不同:DM 区用 0x02,CIO 区用 0x30,HR 区用 0x34,写错就返回
0x0040(非法地址) - 批量读写时,长度字段单位是“字”(Word),不是“字节”,
0x000A表示读 10 个ushort,不是 10 字节 - 没有内置重试机制,网络抖动一次就失败,而
HslCommunication默认带 3 次重试
真正难的从来不是“怎么发”,而是“发完之后怎么确定它真的被 PLC 执行了”。用成熟库,等于把三年踩过的坑打包买断。











