一、通信框架设计
1. 协议分层架构
csharp
public class OmronFinsTcpClient
{
// 传输层
private TcpClient _tcpClient;
private NetworkStream _stream;
// 协议层
private byte[] _header = { 0x46, 0x49, 0x4E, 0x53 }; // FINS头
private byte[] _handshake = new byte[20]; // 握手包
// 状态管理
private bool _connected = false;
private byte _clientNode; // 客户端节点号
private byte _serverNode; // PLC节点号
}
二、核心实现代码
1. TCP连接与握手
csharp
public bool Connect(string ip, int port = 9600)
{
try
{
_tcpClient = new TcpClient();
_tcpClient.Connect(IPAddress.Parse(ip), port);
_stream = _tcpClient.GetStream();
// 生成握手包
BuildHandshake();
_stream.Write(_handshake, 0, _handshake.Length);
// 接收响应
byte[] buffer = new byte[24];
int bytesRead = _stream.Read(buffer, 0, buffer.Length);
if (buffer[0] == 0x46 && buffer[1] == 0x49 && buffer[2] == 0x4E && buffer[3] == 0x53)
{
_serverNode = buffer[23]; // 获取PLC节点号
_connected = true;
return true;
}
}
catch (Exception ex)
{
Debug.WriteLine($"连接失败: {ex.Message}");
}
return false;
}
private void BuildHandshake()
{
Array.Clear(_handshake, 0, _handshake.Length);
_handshake[0] = 0x46; // F
_handshake[1] = 0x49; // I
_handshake[2] = 0x4E; // N
_handshake[3] = 0x53; // S
_handshake[7] = 0x0C; // 数据长度
_handshake[16] = 0x00; // 客户端节点号低位
_handshake[17] = 0x00; // 客户端节点号高位
}
2. 数据读写操作
csharp
/// <summary>
/// 读取PLC数据寄存器(D区)
/// </summary>
public ushort[] ReadDRegisters(string address, int count)
{
ushort start = Convert.ToUInt16(address.Substring(1));
byte area = GetAreaCode(address);
// 构建命令帧
byte[] cmd = BuildCommand(
FinsCommand.MemoryAreaRead,
area,
start,
count
);
// 发送并接收数据
SendCommand(cmd);
return ReadResponse(count * 2);
}
/// <summary>
/// 写入PLC保持寄存器(HR区)
/// </summary>
public bool WriteHRRegisters(string address, ushort[] values)
{
ushort start = Convert.ToUInt16(address.Substring(1));
byte area = GetAreaCode(address);
// 构建命令帧
byte[] cmd = BuildCommand(
FinsCommand.MemoryAreaWrite,
area,
start,
values
);
// 发送并验证
SendCommand(cmd);
return CheckAck();
}
private byte[] BuildCommand(byte mrc, byte src, ushort address, ushort count)
{
byte[] cmd = new byte[26];
cmd[6] = 0x1A; // 数据长度
cmd[7] = 0x02; // 命令类型
cmd[20] = mrc; // 主请求码
cmd[21] = src; // 副请求码
cmd[22] = area; // 区域代码
// 地址和长度编码...
return cmd;
}
三、关键功能实现
1. 区域代码映射
csharp
private byte GetAreaCode(string area)
{
return area switch
{
"DM" => 0x82,
"HR" => 0xB2,
"WR" => 0x31,
"CIO" => 0x30,
_ => throw new ArgumentException("无效区域代码")
};
}
2. 报文解析
csharp
private ushort[] ReadResponse(int expectedLength)
{
byte[] buffer = new byte[expectedLength];
int bytesRead = _stream.Read(buffer, 0, buffer.Length);
if (buffer[12] != 0x00 || buffer[13] != 0x00) // 检查错误码
throw new Exception("PLC返回错误码");
// 转换为ushort数组
ushort[] result = new ushort[bytesRead / 2];
for (int i = 0; i < result.Length; i++)
{
result[i] = (ushort)(buffer[2*i] << 8 | buffer[2*i+1]);
}
return result;
}
四、异常处理与优化
1. 自动重连机制
csharp
public void Reconnect()
{
Disconnect();
int retry = 0;
while (!_connected && retry < 3)
{
try
{
Connect(IPAddress, Port);
retry++;
}
catch { Thread.Sleep(500); }
}
}
2. 性能优化
csharp
// 批量操作优化
public void BatchWrite(Dictionary<string, ushort[]> data)
{
using (var ms = new MemoryStream())
{
foreach (var item in data)
{
byte[] cmd = BuildCommand(
FinsCommand.MemoryAreaWrite,
GetAreaCode(item.Key),
Convert.ToUInt16(item.Key.Substring(1)),
item.Value
);
ms.Write(cmd, 0, cmd.Length);
}
_stream.Write(ms.ToArray(), 0, ms.Length);
}
}
五、完整使用示例
csharp
var plc = new OmronFinsTcpClient();
if(plc.Connect("192.168.1.100"))
{
try
{
// 读取D100-D105
ushort[] data = plc.ReadDRegisters("D100", 6);
plc.WriteHRRegisters("HR10", new ushort[] { 1234, 5678 });
// 实时监控
Timer timer = new Timer(1000);
timer.Elapsed += (s,e) =>
{
float temp = plc.ReadFloat("DM100");
Console.WriteLine($"温度: {temp}℃");
};
timer.Start();
}
finally
{
plc.Disconnect();
}
}
六、调试工具与验证
-
Wireshark抓包分析
过滤
tcp.port==9600查看FINS报文结构,验证握手过程和数据帧格式。 -
PLC监控工具
使用Omron FinsTool验证基础通信,对比手动操作与程序运行结果。
-
日志记录
csharp_stream.Write(Encoding.ASCII.GetBytes($"[{DateTime.Now}] 写入HR10: 1234\r\n"));
七、常见问题解决方案
| 问题现象 | 解决方案 |
|---|---|
| 连接超时 | 检查防火墙设置,确认PLC网口连接状态 |
| 数据校验错误 | 确保字节序转换正确(大端模式) |
| 批量写入失败 | 拆分操作(单次≤2000字),增加重试机制 |
| 实时性不足 | 启用异步通信,设置_tcpClient.ReceiveTimeout = 1000; |
参考代码 C#编写OMRON Fins TCP协议与OMRON PLC通讯 www.youwenfan.com/contentcsp/38791.html
八、扩展功能实现
-
数据缓存机制
csharpprivate Dictionary<string, ushort[]> _cache = new(); public ushort[] ReadWithCache(string address, int count) { if (!_cache.ContainsKey(address) || _cache[address] == null) { _cache[address] = ReadDRegisters(address, count); } return _cache[address]; } -
报警处理
csharppublic void CheckTemperatureAlarm(string address, float threshold) { float temp = ReadFloat(address); if (temp > threshold) { SendEmailAlert($"温度报警!{address}: {temp}℃"); plc.WriteBit("CIO.0.0", true); // 触发报警继电器 } }
九、工程部署建议
-
硬件配置
-
工业PC:研华工控机(支持宽温运行)
-
网络设备:欧姆龙CP1E PLC + FINS兼容交换机
-
-
安全配置
csharp// 启用加密通信 _stream = new SslStream(_tcpClient.GetStream(), false); _stream.AuthenticateAsClient("PLC001");