1. 工业自动化通讯的核心挑战
在工业自动化领域,上位机与PLC的通讯一直是系统集成的关键环节。我经历过多个汽车生产线改造项目,最头疼的就是不同品牌PLC的协议兼容性问题。三菱的MELSEC协议、西门子的S7协议就像两个说着不同方言的技术员,要让它们在同一套系统里和谐共处,需要解决三大难题:
- 协议差异:三菱采用MC协议(MELSEC Communication Protocol),西门子使用S7协议,两者的帧结构、寻址方式完全不同
- 实时性要求:汽车焊接工位要求毫秒级响应,通讯延迟必须控制在50ms以内
- 并发处理:需要同时维持两条通讯链路,还要避免资源竞争
去年为某新能源电池厂做的监控系统,就要求同时采集三菱FX5U和西门子S7-1200的数据。下面分享的这套源码框架,已经在实际产线稳定运行超过8000小时。
2. 通讯架构设计
2.1 双协议并行处理方案
核心思路是采用协议隔离层设计,就像给不同国家的代表配备专属翻译:
csharp复制public abstract class PLCProtocolBase
{
public abstract bool Connect();
public abstract byte[] ReadData(string address);
public abstract bool WriteData(string address, byte[] value);
}
// 三菱协议实现
public class MitsubishiProtocol : PLCProtocolBase
{
// 实现MC协议特有的3E帧构造
private byte[] BuildReadCommand(string address) {
// 三菱地址需转换为软元件编号 如D100 -> 0x44 0x64
}
}
// 西门子协议实现
public class SiemensProtocol : PLCProtocolBase
{
// 实现S7协议的TSAP连接机制
protected override bool Connect() {
// 需要处理COPT字段和PDU长度限制
}
}
2.2 线程安全控制
用生产者-消费者模式处理并发请求,关键代码如下:
csharp复制private ConcurrentQueue<PLCCommand> _commandQueue = new ConcurrentQueue<PLCCommand>();
private readonly object _lockObj = new object();
void CommunicationThread()
{
while(!_stopRequested)
{
if(_commandQueue.TryDequeue(out var cmd))
{
lock(_lockObj)
{
var protocol = cmd.PLCType == PLCType.Mitsubishi ?
_mitsubishiProtocol : _siemensProtocol;
switch(cmd.Operation)
{
case OperationType.Read:
cmd.Result = protocol.ReadData(cmd.Address);
break;
case OperationType.Write:
cmd.Success = protocol.WriteData(cmd.Address, cmd.Data);
break;
}
}
}
Thread.Sleep(1); // 防止CPU占用过高
}
}
3. 协议实现细节
3.1 三菱MC协议解析
三菱的3E帧协议有个"潜规则":每次读写操作最好间隔20ms以上,否则容易触发PLC的通信错误(ERR代码0x80)。实测中发现的最佳实践是:
-
读命令帧示例(ASCII模式):
code复制02 45 30 30 31 30 34 44 30 30 30 32 30 33 03 37 44 └─┬┘ └──┬──┘ └─┬─┘ └──┬──┘ └─┬─┘ └─┬┘ └─┬┘ 头 站号 指令 地址 长度 和校验 -
写操作必须注意:
- 位元件(如M0)需要转换为0x9C前缀
- 字元件(如D100)使用0x44前缀
- 32位浮点数要拆分为两个16位寄存器
3.2 西门子S7协议要点
西门子的S7-1200系列有个坑:默认的TSAP(Transport Service Access Point)是03.02,但部分固件版本会随机变化。解决方案是在连接前先发送诊断请求:
csharp复制byte[] diagRequest = {
0x03, 0x00, 0x00, 0x16, 0x11, 0xE0,
0x00, 0x00, 0x00, 0x01, 0x00, 0xC1, 0x02, 0x10, 0x00,
0xC2, 0x02, 0x03, 0x00, 0xC0, 0x01, 0x0A
};
读取数据时要注意PDU大小限制(S7-1200默认是240字节),超出需要分多次读取。建议采用分段读取策略:
- 先获取PLC的PDU大小
- 计算最大可读字节数 = PDU大小 - 18字节头
- 大数据块自动拆分为多个请求
4. 性能优化技巧
4.1 通讯超时设置
不同场景需要差异化配置:
- 实时控制:超时200ms,重试2次
- 数据采集:超时1s,不重试
- 参数配置:超时3s,重试3次
csharp复制public class PLCTimingConfig
{
public int ReadTimeout { get; set; } = 1000;
public int WriteTimeout { get; set; } = 2000;
public int RetryCount { get; set; } = 1;
// 根据PLC类型自动适配
public static PLCTimingConfig GetDefault(PLCType type)
{
return type == PLCType.Mitsubishi ?
new PLCTimingConfig { ReadTimeout=800, WriteTimeout=1500 } :
new PLCTimingConfig { ReadTimeout=1200, WriteTimeout=2000 };
}
}
4.2 数据缓存机制
采用环形缓冲区减少实时通讯压力:
csharp复制public class PLCCircularBuffer
{
private readonly Dictionary<string, object>[] _buffers;
private int _currentIndex;
public PLCCircularBuffer(int size)
{
_buffers = new Dictionary<string, object>[size];
for(int i=0; i<size; i++)
_buffers[i] = new Dictionary<string, object>();
}
public void UpdateData(string address, object value)
{
lock(_buffers)
{
foreach(var buffer in _buffers)
buffer[address] = value;
}
}
public object GetLatestData(string address)
{
return _buffers[_currentIndex].TryGetValue(address, out var value) ?
value : null;
}
}
5. 异常处理实战经验
5.1 三菱PLC常见故障码
| 错误码 | 含义 | 解决方案 |
|---|---|---|
| 0x80 | 通信错误 | 检查站号设置,降低通讯频率 |
| 0xA0 | 地址错误 | 确认软元件类型(D/M/X/Y) |
| 0xC0 | 数据错误 | 检查写入值是否超出范围 |
5.2 西门子PLC连接问题排查
-
连接超时:
- 检查PLC的IP地址是否与PC在同一网段
- 确认PLC已启用PUT/GET通信(博途软件中设置)
- 关闭Windows防火墙测试
-
数据读取异常:
- 使用Wireshark抓包分析PDU
- 检查DB块优化访问设置
- 确认变量地址的存储格式(大端/小端)
6. 源码结构建议
推荐的分层架构:
code复制PLCSystem/
├── Core/
│ ├── Protocols/ # 协议实现
│ ├── Models/ # 数据模型
│ └── Utilities/ # 工具类
├── Services/
│ ├── Communication.cs # 通讯服务
│ └── DataProcessor.cs
└── Demo/
├── MitsubishiTest/ # 三菱测试程序
└── SiemensTest/ # 西门子测试程序
关键接口设计:
csharp复制public interface IPLCService
{
event EventHandler<DataReceivedEventArgs> DataReceived;
bool IsConnected { get; }
PLCType PLCType { get; }
Task<bool> ConnectAsync();
Task<byte[]> ReadBytesAsync(string address, int length);
Task<bool> WriteBitsAsync(string address, bool[] values);
}
在实际项目中,这套架构成功实现了:
- 三菱FX5U的0.5ms级IO控制
- 西门子S7-1200的200ms周期数据采集
- 双PLC协同误差<2ms
