1. 项目背景与需求分析
在工业自动化领域,上位机与PLC的通讯是实现设备监控和控制的基础。最近在完成一个自动化产线改造项目时,我遇到了需要通过C#程序与松下FP-XH C60ET PLC进行数据交互的需求。这个PLC同时支持串口(RS232/RS485)和以太网两种通讯方式,需要根据现场环境灵活选择。
实际项目中,通讯稳定性是首要考虑因素。生产线一旦运行,通讯中断可能导致严重损失,因此必须建立可靠的通讯机制。
2. 通讯方案设计与选型
2.1 通讯方式对比
在开始编码前,我对比了两种通讯方式的特性:
| 特性 | 串口通讯 | 以太网通讯 |
|---|---|---|
| 连接距离 | ≤15米 | 可达100米(普通网线) |
| 传输速率 | 115.2kbps(最大值) | 100Mbps |
| 接线复杂度 | 需要确认TX/RX接线 | 标准RJ45接口 |
| 抗干扰性 | 易受电磁干扰 | 抗干扰能力强 |
| 多设备连接 | 需要硬件分配器 | 通过交换机扩展 |
考虑到产线设备分布较散(超过20米),最终选择了以太网通讯方案。但为了代码的可扩展性,我仍然保留了串口通讯的实现。
2.2 协议选择
松下FP-XH系列支持两种主流协议:
- MC协议:松下专用协议,支持全功能访问
- Modbus TCP:标准协议,兼容性好
经过测试发现,MC协议在读写速度上比Modbus TCP快约30%,且支持更多PLC内部区域访问。因此决定采用MC协议作为主要通讯方式。
3. 核心代码实现
3.1 通讯基础类设计
首先创建抽象基类,定义通用接口:
csharp复制public abstract class PlcCommunicator : IDisposable
{
public abstract bool Connect();
public abstract void Disconnect();
public abstract bool ReadBit(string address);
public abstract void WriteBit(string address, bool value);
public abstract short ReadWord(string address);
public abstract void WriteWord(string address, short value);
protected virtual void Dispose(bool disposing)
{
// 释放资源实现
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
3.2 以太网通讯实现
基于MC协议的以太网通讯核心类:
csharp复制public class McProtocolCommunicator : PlcCommunicator
{
private TcpClient _client;
private NetworkStream _stream;
private readonly string _ip;
private readonly int _port;
public McProtocolCommunicator(string ip, int port = 5000)
{
_ip = ip;
_port = port;
}
public override bool Connect()
{
try
{
_client = new TcpClient();
_client.Connect(_ip, _port);
_stream = _client.GetStream();
return true;
}
catch (Exception ex)
{
LogError($"连接失败: {ex.Message}");
return false;
}
}
// 读取单个位(如 X0, Y10)
public override bool ReadBit(string address)
{
var command = BuildReadCommand(address, 1);
var response = SendCommand(command);
return ParseBitResponse(response);
}
// 构建读取命令帧
private byte[] BuildReadCommand(string address, int length)
{
// MC协议帧格式实现
// 子头部: 50 00
// 网络编号: 00
// PLC编号: FF
// 请求目标模块IO编号: FF 03
// 请求目标模块站号: 00
// 监视定时器: 00 10
// 命令: 01 04
// 子命令: 00 00
// 起始地址解析...
}
}
3.3 串口通讯实现
对于串口通讯,需要注意硬件流控制:
csharp复制public class SerialPortCommunicator : PlcCommunicator
{
private SerialPort _serialPort;
public SerialPortCommunicator(string portName, int baudRate = 9600)
{
_serialPort = new SerialPort(portName, baudRate)
{
Parity = Parity.Even,
DataBits = 7,
StopBits = StopBits.One,
Handshake = Handshake.RequestToSend
};
}
public override bool Connect()
{
if (_serialPort.IsOpen) return true;
try
{
_serialPort.Open();
Thread.Sleep(100); // 等待PLC响应
return true;
}
catch (Exception ex)
{
LogError($"串口打开失败: {ex.Message}");
return false;
}
}
}
4. 关键问题与解决方案
4.1 通讯超时处理
在工业现场,网络抖动可能导致通讯中断。我们实现了自动重连机制:
csharp复制private TResponse RetryCommand<TResponse>(Func<TResponse> action, int maxRetries = 3)
{
int retryCount = 0;
while (retryCount < maxRetries)
{
try
{
return action();
}
catch (IOException ex)
{
retryCount++;
if(retryCount >= maxRetries) throw;
Thread.Sleep(100 * retryCount);
Reconnect();
}
}
throw new InvalidOperationException("重试次数超过限制");
}
4.2 数据打包优化
测试发现频繁的小数据包传输效率低下。我们实现了批量读写:
csharp复制public Dictionary<string, bool> ReadMultipleBits(params string[] addresses)
{
// 分析地址连续性
// 合并相邻地址为单个读取请求
// 返回解析后的位状态字典
}
public void WriteMultipleBits(Dictionary<string, bool> values)
{
// 按地址分组写入
// 每个写入帧最多包含64个位
}
5. 实际应用案例
5.1 产线状态监控
csharp复制var plc = new McProtocolCommunicator("192.168.1.10");
if (plc.Connect())
{
var running = plc.ReadBit("X0"); // 启动信号
var alarm = plc.ReadBit("Y10"); // 报警信号
var speed = plc.ReadWord("D100"); // 当前转速
UpdateDashboard(running, alarm, speed);
}
5.2 配方参数下载
csharp复制public void DownloadRecipe(Recipe recipe)
{
plc.WriteWord("D200", recipe.Temperature);
plc.WriteWord("D202", recipe.Pressure);
plc.WriteWord("D204", recipe.Time);
// 触发执行
plc.WriteBit("M100", true);
Thread.Sleep(50);
plc.WriteBit("M100", false);
}
6. 性能优化技巧
- 连接池管理:保持长连接而非频繁开关
- 读写合并:将多个请求合并为单个帧
- 异步处理:使用async/await避免UI阻塞
- 心跳检测:定期发送心跳包维持连接
csharp复制public async Task StartHeartbeatAsync(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
try
{
await ReadBitAsync("M0");
await Task.Delay(5000, token);
}
catch
{
Reconnect();
}
}
}
7. 调试与故障排查
7.1 常见错误代码
| 错误码 | 含义 | 解决方案 |
|---|---|---|
| 0x0041 | 帧格式错误 | 检查协议帧格式 |
| 0x0042 | 不支持的命令 | 确认PLC型号支持的功能 |
| 0x0043 | 地址超出范围 | 检查寄存器地址映射 |
7.2 网络抓包分析
使用Wireshark分析MC协议通讯:
code复制No. Time Source Destination Protocol Info
1 0.000000 192.168.1.100 192.168.1.10 TCP 5000 → 1025 [SYN]
2 0.000123 192.168.1.10 192.168.1.100 TCP 1025 → 5000 [SYN, ACK]
3 0.000135 192.168.1.100 192.168.1.10 TCP 5000 → 1025 [ACK]
4 0.000456 192.168.1.100 192.168.1.10 MC 读取D100请求
5 0.001234 192.168.1.10 192.168.1.100 MC 响应数据(1234)
8. 安全注意事项
-
访问权限控制:
- 限制可访问PLC的IP地址
- 设置PLC密码保护
-
数据验证:
csharp复制public void WriteWord(string address, short value) { if(!IsValidAddress(address)) throw new ArgumentException("无效地址"); if(value < 0 || value > 10000) throw new ArgumentOutOfRangeException("值超出范围"); // 实际写入操作 } -
异常处理:
- 记录详细错误日志
- 实现优雅降级机制
9. 扩展功能实现
9.1 OPC UA网关
csharp复制public class OpcUaGateway
{
public void Start()
{
// 创建OPC UA服务器
// 映射PLC地址到OPC节点
// 提供标准接口供SCADA访问
}
}
9.2 数据持久化
csharp复制public class DataLogger
{
public void LogData()
{
var values = plc.ReadMultipleWords(
"D100", "D101", "D102");
database.Insert(new {
Timestamp = DateTime.Now,
Value1 = values[0],
Value2 = values[1]
});
}
}
10. 项目部署建议
-
环境配置:
- 安装.NET Core运行时
- 设置Windows服务自启动
-
监控方案:
- 实现心跳检测
- 集成Prometheus监控
-
日志管理:
csharp复制public static void LogError(string message) { File.AppendAllText("error.log", $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} [ERROR] {message}\n"); EventLog.WriteEntry("PLC通信", message, EventLogEntryType.Error); }
在完成这个项目后,我发现工业通讯编程最关键的不仅是技术实现,更需要深入理解现场设备的特性和工艺需求。比如在读取温度传感器时,需要根据PLC的采样周期设置合适的读取频率,既不能太低导致数据延迟,也不能太高造成网络负担。