去年为某食品加工厂开发的一套温度监控系统,让我对西门子PLC数据采集有了更深的实战理解。这套基于C#的上位机程序,核心任务是实时采集西门子S7-200 Smart PLC的温度数据,并通过动态曲线展示温度变化趋势。当温度超过预设的安全阈值时,系统会触发声光报警并记录异常事件,这套方案最终帮助客户实现了烘烤车间温度的全天候自动化监控。
这个项目看似简单,但实际开发中会遇到PLC通信协议解析、实时数据渲染、多线程同步等典型工业控制场景下的技术挑战。下面我就从硬件连接、通信协议、曲线绘制、阈值报警四个核心模块,拆解具体实现过程。
项目采用的西门子S7-200 Smart PLC(型号CPU SR20)通过RS485接口与工控机通信。这里有个关键细节:必须使用西门子官方PPI电缆(6ES7901-3DB30-0XA0)或可靠的USB/PPI转换器,第三方廉价转换器常会导致通信不稳定。
接线时注意:
经验提示:曾因终端电阻误开启导致信号反射,通信成功率直降到60%以下,这个坑我踩过两次。
在STEP 7-Micro/WIN SMART中设置:
plaintext复制波特率:19200(默认值)
站地址:2(需避开0和1)
协议:PPI模式
对应的C#程序需同步这些参数:
csharp复制SerialPort port = new SerialPort("COM3", 19200, Parity.Even, 8, StopBits.One);
port.Handshake = Handshake.RequestToSend;
西门子PPI协议采用主从问答模式,读取温度值的请求报文示例:
csharp复制byte[] BuildReadRequest(byte station, int startAddr, int length)
{
byte[] frame = new byte[] {
0x68, // 开始标志
0x1B, // 长度
0x00, // 固定
0x6C, // 协议标识
station, // PLC站地址
0x00, // 保留
0x76, // 功能码(读)
0x00, // 保留
0x00, // 引用号高字节
0x00, // 引用号低字节
0x00, 0x00, // 数据长度
0x00, 0x00, // 数据单元
(byte)(startAddr >> 24), // 地址字节3
(byte)(startAddr >> 16), // 地址字节2
(byte)(startAddr >> 8), // 地址字节1
(byte)startAddr, // 地址字节0
0x00, 0x00, 0x00, 0x00, // 保留
(byte)(length >> 8), // 长度高字节
(byte)length // 长度低字节
};
// 计算校验和
frame[frame.Length - 1] = CalculateChecksum(frame);
return frame;
}
采用生产者-消费者模式处理实时数据:
csharp复制ConcurrentQueue<float> dataQueue = new ConcurrentQueue<float>();
void AcquisitionThread()
{
while (!token.IsCancellationRequested)
{
byte[] request = BuildReadRequest(0x02, 0x4000, 2);
port.Write(request, 0, request.Length);
byte[] buffer = new byte[256];
int read = port.Read(buffer, 0, buffer.Length);
var temp = ParseTemperature(buffer);
dataQueue.Enqueue(temp);
Thread.Sleep(200); // 采集间隔
}
}
使用ScottPlot库实现高性能渲染:
csharp复制FormsPlot plot = new FormsPlot();
double[] x = new double[1000];
double[] y = new double[1000];
int nextIndex = 0;
void UpdatePlot()
{
while (dataQueue.TryDequeue(out float temp))
{
x[nextIndex] = DateTime.Now.ToOADate();
y[nextIndex] = temp;
nextIndex = (nextIndex + 1) % x.Length;
}
plot.Plot.Clear();
var sp = plot.Plot.AddScatter(x, y);
sp.Color = Color.Red;
sp.LineWidth = 2;
plot.Render();
}
当数据点超过5000个时,建议启用双缓冲:
csharp复制plot.Plot.Style(figureBackground: Color.White);
plot.Plot.Benchmark(true);
plot.Plot.AxisAuto(0.1, 0.1);
csharp复制enum AlarmLevel { Normal, Warning, Critical }
AlarmLevel CheckTemperature(float temp)
{
if (temp > upperLimit * 1.2)
return AlarmLevel.Critical;
else if (temp > upperLimit)
return AlarmLevel.Warning;
else if (temp < lowerLimit * 0.8)
return AlarmLevel.Critical;
else if (temp < lowerLimit)
return AlarmLevel.Warning;
else
return AlarmLevel.Normal;
}
void AlarmControl(AlarmLevel level)
{
switch(level)
{
case AlarmLevel.Warning:
buzzer.Beep(1000, 200);
warningLight.Blink(500);
break;
case AlarmLevel.Critical:
buzzer.Beep(2000, 1000);
alarmLight.Strobe();
SendSMSAlert();
break;
}
}
采用SQLite存储报警历史:
csharp复制void LogAlarmEvent(float temp, AlarmLevel level)
{
using (var conn = new SQLiteConnection("Data Source=alarms.db"))
{
conn.Open();
var cmd = new SQLiteCommand(
"INSERT INTO alarms(time, temperature, level) VALUES(@t, @temp, @lvl)", conn);
cmd.Parameters.AddWithValue("@t", DateTime.Now);
cmd.Parameters.AddWithValue("@temp", temp);
cmd.Parameters.AddWithValue("@lvl", (int)level);
cmd.ExecuteNonQuery();
}
}
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 通信超时 | 波特率不匹配 | 检查PLC与程序的波特率设置 |
| 数据乱码 | 校验位错误 | 确认使用Even Parity |
| 间歇性断连 | 终端电阻冲突 | 关闭PLC端终端电阻 |
| 读取失败 | 地址偏移错误 | V区地址需加0x4000偏移 |
在连续运行测试中发现:
这套基础框架还可扩展:
实际部署时发现,车间的电磁干扰会导致RS485通信误码率升高。后来我们改用带屏蔽的双绞线,并在程序端添加了CRC校验重传机制,通信稳定性从92%提升到99.8%。这个细节再次证明,工业现场环境的复杂性往往超出预期。