C#通过TCP/IP与PLC通信

本文将全面介绍如何使用C#通过TCP/IP协议与各种PLC进行通信,包括西门子、罗克韦尔、三菱等主流品牌PLC的连接方法。

一、PLC通信基础

PLC通信协议概览

协议类型 适用品牌 特点
Modbus TCP 通用协议 简单易用,广泛支持
Siemens S7 西门子PLC 高效专用协议
EtherNet/IP 罗克韦尔PLC CIP协议实现
MC Protocol 三菱PLC 三菱专用协议
FINS/TCP 欧姆龙PLC 欧姆龙专用协议

通信架构

C#应用程序 (TCP/IP客户端)
       ↓
以太网交换机
       ↓
PLC (TCP/IP服务器)

二、Modbus TCP通信实现

使用NModbus库

// 安装NuGet包:Install-Package NModbus
using Modbus.Device;
using System.Net.Sockets;

public class ModbusClient
{
    private TcpClient _tcpClient;
    private ModbusIpMaster _master;

    public void Connect(string ipAddress, int port = 502)
    {
        _tcpClient = new TcpClient(ipAddress, port);
        _master = ModbusIpMaster.CreateIp(_tcpClient);
    }

    // 读取保持寄存器
    public ushort[] ReadHoldingRegisters(byte slaveId, ushort startAddress, ushort numRegisters)
    {
        return _master.ReadHoldingRegisters(slaveId, startAddress, numRegisters);
    }

    // 写入单个寄存器
    public void WriteSingleRegister(byte slaveId, ushort registerAddress, ushort value)
    {
        _master.WriteSingleRegister(slaveId, registerAddress, value);
    }

    // 批量写入寄存器
    public void WriteMultipleRegisters(byte slaveId, ushort startAddress, ushort[] values)
    {
        _master.WriteMultipleRegisters(slaveId, startAddress, values);
    }

    public void Disconnect()
    {
        _tcpClient?.Close();
    }
}

使用示例

var client = new ModbusClient();
try
{
    client.Connect("192.168.1.10");
    
    // 读取10个保持寄存器(起始地址0)
    ushort[] values = client.ReadHoldingRegisters(1, 0, 10);
    
    // 写入单个寄存器(地址5,值1234)
    client.WriteSingleRegister(1, 5, 1234);
    
    // 批量写入(地址10开始的3个寄存器)
    client.WriteMultipleRegisters(1, 10, new ushort[] { 100, 200, 300 });
}
finally
{
    client.Disconnect();
}

三、西门子S7通信实现

使用S7NetPlus库

// 安装NuGet包:Install-Package S7NetPlus
using S7.Net;

public class S7Client
{
    private Plc _plc;

    public void Connect(CpuType cpuType, string ipAddress, short rack = 0, short slot = 1)
    {
        _plc = new Plc(cpuType, ipAddress, rack, slot);
        _plc.Open();
    }

    // 读取DB块数据
    public object ReadDataBlock(int dbNumber, int startByte, VarType varType, int count = 1)
    {
        return _plc.Read(DataType.DataBlock, dbNumber, startByte, varType, count);
    }

    // 写入DB块数据
    public void WriteDataBlock(int dbNumber, int startByte, object value)
    {
        _plc.Write(DataType.DataBlock, dbNumber, startByte, value);
    }

    // 读取输入/输出点
    public bool ReadDigital(DataType dataType, int dbNumber, int byteOffset, int bitOffset)
    {
        return (bool)_plc.Read(dataType, dbNumber, byteOffset, VarType.Bit, 1, bitOffset);
    }

    // 写入输入/输出点
    public void WriteDigital(DataType dataType, int dbNumber, int byteOffset, int bitOffset, bool value)
    {
        _plc.WriteBit(dataType, dbNumber, byteOffset, bitOffset, value);
    }

    public void Disconnect()
    {
        _plc?.Close();
    }
}

使用示例

var s7Client = new S7Client();
try
{
    s7Client.Connect(CpuType.S71500, "192.168.1.20");
    
    // 读取DB10.DBD0 (REAL类型)
    float temperature = (float)s7Client.ReadDataBlock(10, 0, VarType.Real);
    
    // 读取DB10.DBX1.0 (BOOL类型)
    bool status = s7Client.ReadDigital(DataType.DataBlock, 10, 1, 0);
    
    // 写入DB10.DBW10 (INT类型)
    s7Client.WriteDataBlock(10, 10, (short)100);
    
    // 设置输出点Q0.0
    s7Client.WriteDigital(DataType.Output, 0, 0, 0, true);
}
finally
{
    s7Client.Disconnect();
}

四、罗克韦尔EtherNet/IP通信

使用libplctag库

// 安装NuGet包:Install-Package libplctag
using libplctag;
using libplctag.DataTypes;

public class AllenBradleyClient
{
    private readonly Tag<Tag> _plcTag = new Tag<Tag>()
    {
        Gateway = "192.168.1.30",
        Path = "1,0",
        PlcType = PlcType.ControlLogix,
        Protocol = Protocol.ab_eip,
        Name = "MyTag"
    };

    public void Connect()
    {
        _plcTag.Initialize();
    }

    public T ReadTag<T>(string tagName)
    {
        _plcTag.Name = tagName;
        _plcTag.Read();
        return (T)Convert.ChangeType(_plcTag.Value, typeof(T));
    }

    public void WriteTag<T>(string tagName, T value)
    {
        _plcTag.Name = tagName;
        _plcTag.Value = value;
        _plcTag.Write();
    }

    public void Disconnect()
    {
        _plcTag?.Dispose();
    }
}

使用示例

var abClient = new AllenBradleyClient();
try
{
    abClient.Connect();
    
    // 读取BOOL标签
    bool runStatus = abClient.ReadTag<bool>("Program:MainProgram.RunStatus");
    
    // 读取REAL标签
    float pressure = abClient.ReadTag<float>("PressureSensor");
    
    // 写入DINT标签
    abClient.WriteTag("ProductionCount", 1500);
    
    // 写入BOOL标签
    abClient.WriteTag("MachineStart", true);
}
finally
{
    abClient.Disconnect();
}

五、高级通信技巧

1. 异步通信实现

public async Task<ushort[]> ReadRegistersAsync(byte slaveId, ushort startAddress, ushort numRegisters)
{
    return await Task.Run(() => 
        _master.ReadHoldingRegisters(slaveId, startAddress, numRegisters)
    );
}

2. 数据转换工具类

public static class PLCDataConverter
{
    // 字节数组转浮点数
    public static float ToFloat(byte[] bytes, bool bigEndian = true)
    {
        if (bigEndian) Array.Reverse(bytes);
        return BitConverter.ToSingle(bytes, 0);
    }

    // 浮点数转字节数组
    public static byte[] FromFloat(float value, bool bigEndian = true)
    {
        byte[] bytes = BitConverter.GetBytes(value);
        if (bigEndian) Array.Reverse(bytes);
        return bytes;
    }

    // 字节数组转整数
    public static int ToInt32(byte[] bytes, bool bigEndian = true)
    {
        if (bigEndian) Array.Reverse(bytes);
        return BitConverter.ToInt32(bytes, 0);
    }
}

3. 连接管理与重连机制

public class PLCConnectionManager
{
    private Timer _reconnectTimer;
    private bool _isConnected;
    
    public event EventHandler ConnectionRestored;
    public event EventHandler ConnectionLost;

    public PLCConnectionManager()
    {
        _reconnectTimer = new Timer(5000);
        _reconnectTimer.Elapsed += ReconnectTimer_Elapsed;
    }

    public void StartMonitoring()
    {
        _reconnectTimer.Start();
    }

    private void ReconnectTimer_Elapsed(object sender, ElapsedEventArgs e)
    {
        try
        {
            // 尝试Ping PLC
            if (!CheckConnection())
            {
                _isConnected = false;
                ConnectionLost?.Invoke(this, EventArgs.Empty);
                AttemptReconnect();
            }
            else if (!_isConnected)
            {
                _isConnected = true;
                ConnectionRestored?.Invoke(this, EventArgs.Empty);
            }
        }
        catch { /* 处理异常 */ }
    }

    private void AttemptReconnect()
    {
        // 实现重连逻辑
        // 1. 尝试重新连接
        // 2. 指数退避策略
        // 3. 最大重试次数限制
    }
}

参考项目 C#通过TCP/IP和PLC通讯 www.youwenfan.com/contentcnk/93115.html

六、调试与故障排除

常见问题解决方案

  1. 连接超时

    • 检查物理连接和网络配置
    • 确认PLC IP地址和端口正确
    • 关闭防火墙或添加例外规则
  2. 数据不一致

    • 确认字节顺序(大端/小端)
    • 检查PLC数据格式(INT/DINT/REAL)
    • 验证寄存器地址偏移量
  3. 通信不稳定

    • 使用心跳包维持连接
    • 实现自动重连机制
    • 增加通信超时时间

调试工具推荐

  1. Wireshark - 网络协议分析
  2. Modbus Poll - Modbus通信测试
  3. S7-PLCSIM - 西门子PLC仿真
  4. RSLinx Classic - 罗克韦尔通信网关

七、安全注意事项

  1. 网络安全

    // 使用加密通信(如TLS)
    var tcpClient = new TcpClient();
    var sslStream = new SslStream(tcpClient.GetStream());
    sslStream.AuthenticateAsClient("plc-hostname");
    
  2. 身份验证

    // PLC端设置用户名/密码
    _plc = new Plc(CpuType.S71200, "192.168.1.10", 0, 1)
    {
        User = "admin",
        Password = "securePassword"
    };
    
  3. 访问控制

    • 限制PLC网络访问(IP白名单)
    • 使用VPN进行远程访问
    • 定期更新PLC固件和安全补丁

八、性能优化建议

  1. 批量读取

    // 一次性读取多个寄存器
    ushort[] batchData = _master.ReadHoldingRegisters(1, 0, 100);
    
  2. 缓存机制

    private Dictionary<string, object> _tagCache = new();
    
    public T GetCachedValue<T>(string tagName)
    {
        if (!_tagCache.ContainsKey(tagName) || 
            _cacheAge[tagName] < DateTime.Now.AddSeconds(-5))
        {
            _tagCache[tagName] = ReadTag<T>(tagName);
            _cacheAge[tagName] = DateTime.Now;
        }
        return (T)_tagCache[tagName];
    }
    
  3. 连接池管理

    public class PLCConnectionPool
    {
        private ConcurrentBag<TcpClient> _connections = new();
        
        public TcpClient GetConnection()
        {
            if (_connections.TryTake(out var connection))
                return connection;
            
            return CreateNewConnection();
        }
        
        public void ReturnConnection(TcpClient connection)
        {
            _connections.Add(connection);
        }
    }
    

通过本文介绍的方法,您可以根据不同PLC品牌选择合适的通信协议和库,实现稳定高效的工业控制系统集成。

posted @ 2025-11-07 15:56  alloutlove  阅读(6)  评论(0)    收藏  举报