1. 传送带失控事故现场还原
那天早上7点,产线刚完成交接班,我正端着咖啡在HMI前检查夜班生产数据。突然听到包装工段传来一阵巨响——三号传送带上的玻璃瓶像保龄球一样飞了出去,碎渣溅到五米开外。操作工老张脸色煞白地冲进控制室:"这破带子抽什么风!突然加速到飞起!"
我立刻拍下急停按钮,调出TIA Portal里的在线监控。画面上Q0.3输出点疯狂闪烁,本应受光电开关控制的传送带电机竟以120%的设定速度运转。更诡异的是,根据程序逻辑,这个速度档位根本不存在于当前工艺配方中。
2. PLC程序员的噩梦排查链
2.1 第一步:硬件还是软件的锅?
用万用表测量电机驱动器输入电压,确实达到了异常值。但重点在于:这个指令是谁发出的?
-
排查路径1:检查物理接线
- 确认Q0.3到驱动器的线缆无短路
- 测量24V电源稳定性(示波器显示纹波<5%)
- 结果:硬件正常
-
排查路径2:在线监测程序流
- 发现FB12功能块内的Speed_Actual值异常
- 交叉引用显示该值被DB45.DBW22覆盖
2.2 内存越界的幽灵
在交叉引用视图中追踪DB45.DBW22的写入点,发现一段危险的指针操作:
STL复制L P#DB45.DBX22.0
LAR1
L MW200
T W [AR1,P#0.0]
这段代码本意是将MW200的值传给DB45.DBW22,但MW200在某个边缘条件下会溢出到32768(0x8000),导致速度设定值被篡改。更致命的是,原始程序员没有做数值范围校验。
3. Factory IO仿真暴露的设计缺陷
3.1 仿真环境搭建技巧
用Factory IO重建产线模型时,关键要还原真实场景的I/O延迟:
- 在传感器配置中添加5-10ms随机抖动
- 电机响应曲线按实际型号参数设置
- 特别模拟网络通信丢包(每500ms随机丢1个报文)
3.2 仿真中的灵异现象
当我在TIA Portal V17中加载备份程序进行联调时,发现一个细思极恐的细节:在连续运行8小时后,DB45.DBW22的值会突然跳变。进一步分析发现是OB35循环中断里的一个累加计算导致了内存溢出。
关键教训:永远要对来自HMI和SCADA的设定值做钳位处理,像这样:
SCL复制#Speed_Set := LIMIT(0, #HMI_Input, 100);
4. 从血泪中总结的PLC编程军规
4.1 内存操作五重防护
-
边界检查:所有数组访问前检查索引
SCL复制IF #Index >= LOWER_BOUND(ARRAY) AND #Index <= UPPER_BOUND(ARRAY) THEN -
指针消毒:在LAR1后立即验证地址范围
STL复制L P#DB45.DBX22.0 LAR1 L P##MySafeRange >D JC ERROR -
看门狗计时:关键功能块添加运行超时监测
-
默认值保护:所有DB变量声明时赋安全初始值
-
变更追溯:用SCL的
//%CHANGE标签标注敏感修改
4.2 速度控制的防呆设计
针对传送带这类惯性设备,必须实现:
- 加速度斜率限制(S曲线调速)
- 相邻速度档位最小间隔时间
- 紧急减速时的能量泄放回路检测
我在现有程序中增加了速度变化率监控:
SCL复制// 速度突变检测
IF ABS(#Speed_Now - #Speed_Prev) > #Max_Change_Rate THEN
#Alarm_102 := TRUE;
#Speed_Out := #Speed_Prev; // 保持前值
END_IF;
5. 联机调试的保命秘籍
5.1 在线修改的禁忌
那次事故后,我养成了一套安全操作流程:
- 先在OB1里插入临时调试块
- 使用
WRITE_DBL指令而非直接写变量 - 修改前备份所有关联DB的当前值
- 强制信号时在注释栏注明责任人
5.2 信号追踪的骚操作
遇到诡异现象时,我会用这个技巧快速定位:
- 在变量表里添加可疑地址
- 右键选择"触发器记录"
- 设置"值变化时触发"
- 导出CSV用Excel分析时间戳
有次就用这招抓到了HMI每隔2小时偷偷发送的错误配方数据——原来是触摸屏上的定时宏在作妖。
6. 那些年我们踩过的数据类型坑
6.1 整型溢出的七十二变
- 案例1:INT类型做累加超过32767会变负数
- 案例2:WORD类型比较时若有一方是负值,比较结果完全错乱
- 案例3:REAL类型在STEP7里默认没有NaN检测
我的应对方案:
SCL复制// 安全加法函数
FUNCTION "Safe_Add" : INT
VAR_INPUT
a : INT;
b : INT;
END_VAR
VAR_TEMP
lResult : DINT;
END_VAR
lResult := DINT_TO_INT(a) + DINT_TO_INT(b);
IF lResult > 32767 OR lResult < -32768 THEN
//%CHANGE 2023-07-15 添加溢出处理
RETURN 0;
ELSE
RETURN INT_TO_DINT(lResult);
END_IF;
6.2 时间变量的花式翻车
最经典的莫过于用TON定时器做小时累计:
STL复制 L "Timer_DB".TON_1.ET
T MW100
L MW100
L 3600000 // 1小时=3,600,000ms
>=I
JC NEXT
当ET超过INT最大值时...画面太美不敢看。现在我一律用DT类型配合系统时钟做时间运算。
