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
六、调试与故障排除
常见问题解决方案
-
连接超时
- 检查物理连接和网络配置
- 确认PLC IP地址和端口正确
- 关闭防火墙或添加例外规则
-
数据不一致
- 确认字节顺序(大端/小端)
- 检查PLC数据格式(INT/DINT/REAL)
- 验证寄存器地址偏移量
-
通信不稳定
- 使用心跳包维持连接
- 实现自动重连机制
- 增加通信超时时间
调试工具推荐
- Wireshark - 网络协议分析
- Modbus Poll - Modbus通信测试
- S7-PLCSIM - 西门子PLC仿真
- RSLinx Classic - 罗克韦尔通信网关
七、安全注意事项
-
网络安全
// 使用加密通信(如TLS) var tcpClient = new TcpClient(); var sslStream = new SslStream(tcpClient.GetStream()); sslStream.AuthenticateAsClient("plc-hostname"); -
身份验证
// PLC端设置用户名/密码 _plc = new Plc(CpuType.S71200, "192.168.1.10", 0, 1) { User = "admin", Password = "securePassword" }; -
访问控制
- 限制PLC网络访问(IP白名单)
- 使用VPN进行远程访问
- 定期更新PLC固件和安全补丁
八、性能优化建议
-
批量读取
// 一次性读取多个寄存器 ushort[] batchData = _master.ReadHoldingRegisters(1, 0, 100); -
缓存机制
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]; } -
连接池管理
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品牌选择合适的通信协议和库,实现稳定高效的工业控制系统集成。
浙公网安备 33010602011771号