C#实现OMRON FINS-TCP协议与PLC通信

一、通信框架设计

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();
    }
}

六、调试工具与验证

  1. Wireshark抓包分析

    过滤tcp.port==9600查看FINS报文结构,验证握手过程和数据帧格式。

  2. PLC监控工具

    使用Omron FinsTool验证基础通信,对比手动操作与程序运行结果。

  3. 日志记录

    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

八、扩展功能实现

  1. 数据缓存机制

    csharp 复制代码
    private 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];
    }
  2. 报警处理

    csharp 复制代码
    public void CheckTemperatureAlarm(string address, float threshold)
    {
        float temp = ReadFloat(address);
        if (temp > threshold)
        {
            SendEmailAlert($"温度报警!{address}: {temp}℃");
            plc.WriteBit("CIO.0.0", true); // 触发报警继电器
        }
    }

九、工程部署建议

  1. 硬件配置

    • 工业PC:研华工控机(支持宽温运行)

    • 网络设备:欧姆龙CP1E PLC + FINS兼容交换机

  2. 安全配置

    csharp 复制代码
    // 启用加密通信
    _stream = new SslStream(_tcpClient.GetStream(), false);
    _stream.AuthenticateAsClient("PLC001");
相关推荐
hoiii1872 小时前
C# 俄罗斯方块游戏
开发语言·游戏·c#
不当菜虚困2 小时前
centos7虚拟机配置网络
运维·服务器·网络
网安小白的进阶之路3 小时前
B模块 安全通信网络 第二门课 核心网路由技术-2-BGP通告原则-IBGP水平分割-路由反射器
网络·安全
橘颂TA3 小时前
【Linux 网络】应用层自定义协议和序列化
linux·运维·服务器·网络·tcp·结构与算法·序列化和反序列
龙亘川3 小时前
GB/Z167-2025《系统控制图》全维度解析
运维·网络·智慧城市·内涝监测·技术标准
ytttr8733 小时前
基于C# WinForms实现多窗口通信
开发语言·microsoft·c#
开开心心就好3 小时前
内存清理工具显示内存,优化释放自动清理
java·linux·开发语言·网络·数据结构·算法·电脑
做萤石二次开发的哈哈3 小时前
萤石开放平台 设备运维 | B端设备添加工具 产品介绍
linux·服务器·网络·萤石云·萤石
fengfuyao9853 小时前
基于C# WinForm实现的串口调试助手源码
开发语言·c#