☰
PLC程序能运行不等于写得好:从坏味道到自检清单的实战指南
2026/10/11 12:53:10 网站建设 项目流程

“能跑”和“写得好”从来不是一回事。这些年我见过太多“能运行但不敢碰”的PLC程序:设备一开,一切正常;一旦要加个安全连锁、改个工艺流程,现场的工程师就得对着上万条指令发愁。普遍想法是:程序能运行,设备能干活,验收能通过,那就是好程序。但真实情况是,这不过把“能用”和“好用”混成了一谈。程序能运行,只代表交付完成了最低限度的任务;而程序写得好,才决定这台设备在后续三五年里,是越用越顺还是处处埋雷。

我印象最深的一次,是接手一套别人交付的设备。程序运行完全正常,可我要新增一个互锁逻辑,光是理清那几百个中间继电器的置位、复位关系就花了两天,期间还差点把一个正在使用的状态位复位掉。从那天起我看程序的标准就变了——先不问能不能跑,而问这程序能不能被快速理解、被安全修改、被准确定位故障。这篇文章就是围绕这个标准展开的,梳理我在实际项目里总结的“能用”与“好用”之间的差距、最常见的坏味道、一次真实故障的完整复盘,以及一份可以直接照做的程序自检清单。

1. 先分清“能用”和“好用”:两类程序之间的差距

1.1 同一个启停控制,三版写法的差距

先拿一个最简单的电机启停来说。设备要满足的功能只有一个:按启动,电机转;按停止,电机停。这个功能谁都能写,而且写出来的程序都能跑,但写法不同,后续命运完全不同。

第一版是最基础的启保停逻辑,梯形图大概是这个意思:

X0 启动按钮(常开) X1 停止按钮(常闭) Y0 接触器输出 LD X0 OR Y0 ANI X1 OUT Y0

这版程序能跑吗?能。但它没法把“停止按钮没按”和“急停回路断线”区分开,出故障时现场只能挨个量线,排查效率很差。而且它把“运行状态”和“物理输出”绑在一起,程序里根本看不出设备到底是想运行还是不想运行。

第二版开始引入状态变量,把运行状态和物理输出分开:

LD X0 // 启动按钮 AND X2 // 热继电器正常(常开点) OR M0 // 运行状态自保持 ANI X1 // 停止按钮 AND NOT M1 // 无报警 OUT M0 // 运行状态位 LD M0 AND X3 // 接触器吸合反馈 OUT Y0 // 输出接触器

这一版的思路是把“我想让它运行”和“它实际在运行”分成两个层面。哪怕接触器吸合反馈丢了,程序里的M0也能告诉我“系统确实请求了运行,但执行层有问题”。操作工按了启动没反应时,我先看M0在不在,再看Y0有没有输出,两步就能把问题定位在控制逻辑还是执行机构。

第三版更进一步,用功能块把整个启停逻辑封装起来:

FUNCTION_BLOCK FB_MotorControl VAR_INPUT StartCmd : BOOL; // 外部启动请求 StopCmd : BOOL; // 外部停止请求 ThermalOk : BOOL; // 热继电器正常 ContactFb : BOOL; // 接触器吸合反馈 Alarm : BOOL; // 外部故障 END_VAR VAR_OUTPUT RunState : BOOL; // 运行状态 OutputCmd : BOOL; // 输出指令 END_VAR

有了这个功能块,一台电机占一段调用,十几台电机就复制十几份调用,参数由外部传入。以后要改逻辑,只改功能块内部,不用逐台电机去翻梯形图。三版程序在“能运行”层面没有区别,但在可维护性、可复用性、可诊断性上,差距是数量级的。

1.2 五个维度对比“能跑”与“好用”

我习惯用下面这张表来判断一个程序处在什么水平,也从这五个维度向团队成员解释为什么不能停留在“能跑”阶段:

对比维度能运行的普通程序写得好的程序
可靠性正常工况下逻辑正确异常工况下有兜底和互锁
可维护性写的人自己调试期能看懂别人半年后接手也能改
可扩展性加一个功能要动大段逻辑加一个块、配几个参数即可
可诊断性出故障只能抱着笔记本现查结合状态字和报警快速定位
复用性换一个项目重写一半功能块和子程序直接迁移

我见过太多项目,整台设备从头到尾只有一个主程序块,几百个网络像意识流一样把所有设备堆在一起。这种程序也能跑,稳定性甚至不差,但它的“好”只停留在“能用”层面。如果今天让我回答标题那个问题,我的答案是:PLC程序能运行,当然算完成了任务;但离“写得好”还差得很远,这个距离才真正体现一个工程师的价值。

2. 三处最容易暴露“坏味道”的细节

2.1 置位复位满天飞,几百个M继电器全是无名状态位

置位和复位指令本身不是坏东西,它们是记忆功能,适合表达“给一个命令然后保持状态”的场景。问题出在很多程序把置位、复位当成了万能工具,有条件就SET一个M,到另一个网络又RST掉。整个程序下来,几百个中间继电器百分之九十九没有符号名,谁也不知道谁在干什么。

典型的“状态接力”写法长这样:

网络10:LD X10 SET M100 // 上料到位 网络11:LD M100 SET M101 // 推料开始 网络12:LD X11 RST M100 // 上料完成复位 网络13:LD M101 SET M102 // 加工启动

你在现场见过这种程序吗?大概率见过。它最大的问题有三个:第一,M100到M199被当成顺序号在用,编号既不是位号也不是动作名,改写时根本分不清;第二,同一状态的置位和复位散落在不同位置,一个扫描周期内可能既被置位又被复位,监控表里看到的波形毫无参考价值;第三,一旦中断程序或触摸屏直写变量误碰一个M位,状态链悄悄断掉,故障还无处查起。

正确倾向是给每个状态位一个有名字的符号,比如“上料完成_状态”,把状态变更集中写在同一段代码区域;能用结构化文本写状态机就别只用梯形图拼M位。如果确实只能用梯形图,至少把置位和复位配成对,写在相邻网络里,让审阅的人一眼能看出状态生命周期。

2.2 扫描顺序就是程序命脉,插一段逻辑就出怪病

PLC是循环扫描执行的,扫描周期内的执行顺序等于网络排列顺序。这是正常机制,但很多程序把这当成了靠山:网络5的结果网络10用,网络10的结果网络12又用,中间穿插大量其他逻辑。看着没毛病,直到有一天要插一段新逻辑,插进去之后前面网络的结果不再是原来那个值,程序就开始犯怪病。

举个例子,设备原来的启动顺序是A气缸伸、等光纤检测、B气缸伸。程序里用A伸出的置位M位去触发B,逻辑正常。后来要加一段吹气延时,改的人顺手往中间插了两个网络,结果吹气延时网络把扫描周期拖长,B触发的判断逮着A伸出置位还没来得及生效的周期,设备就偶发不动作。这种问题在仿真环境里很难复现,因为仿真时序和现场负载完全不同。

我自己的习惯有三条:程序开头做一个“输入快照区”,所有传感器、按钮、反馈统一读到中间变量,后续逻辑只读快照,不直接读物理点;程序末尾做一个“输出集中区”,所有输出指令只在这里做最终赋值;中间的逻辑层只算“该做什么”,不直接写物理输出。这样一来,网络顺序对人的影响降到最低,哪怕后来有人插了一段新逻辑,也不会把输入输出的时序搅乱。这个习惯可以帮大家省掉很多“程序明明没改,为什么行为变了”的折腾。

2.3 定时器预设值写成魔法数字,调参要等工程师出差

要说哪个细节最能在程序评审时暴露水平,我首推定时器预设值。PLC程序里最常见不过一句“T37 K2500”,但K2500到底是多少秒?这取决于CPU的时基——有的时基是0.1秒,K2500是250秒;有的时基是0.01秒,K2500是25秒。现场拿秒表对着调,十有八九要在这个单位上栽跟头。

更痛苦的场景在工艺调整阶段。比如“上料到检测位置后等2秒”,程序里直接写了K20。今天2秒够用,明天供应商把来料速度改了,需要1.8秒。车间的工艺员不敢改程序,只能等工程师出差;工程师到了现场,还得先翻程序看K20到底对应哪个设备的哪个动作,改完重新编译下载,心惊胆战怕影响其他环节。

解决办法很朴素,却常常被忽略:给定时器预设值起符号名,数值放到数据块里;把整台设备所有可调的工艺参数集中到一个“工艺参数数据块”;有条件的话,把延时、计数、极限值等参数绑定到触摸屏配方页面,让工艺员可以直接改界面上的“延时时间”字段,而不是碰程序。这个习惯一旦养成,能砍掉后期一大半的“现场改参数”服务。

3. 一次为期两周的现场排查:程序能跑,故障却能藏一年

3.1 设备跑了一年,突然开始偶发卡死

某台设备,两条气缸协同完成“夹紧—压装—退回”的动作,用的是某中端系列的PLC控制器。程序交付后正常跑了快一年,某天开始,操作工偶尔会碰上设备卡在流程中间,按复位又能继续。开始是一天三五次,后来变成三五天一次,完全没有规律。现场先去怀疑传感器:两个气缸的到位传感器全部换了新,安装位置重新调过,故障照旧。又开始怀疑机械:导杆、缓冲垫、气源压力都查了一圈,没什么明显问题。拖了两周,用户不干了,要求厂家必须给说法。

接手之后我先做了个很基础的判断:这是一个偶发、无规律、复位可恢复的故障。这类故障的“作案时间”往往只有几个扫描周期,靠人在现场盯波形基本抓不到。我当时跟用户商量,把可能相关的十几个状态位全部记录到数据日志里,设置成每次状态变化就追加一条,让设备继续跑着,等下一次故障自然出现。

3.2 三天数据日志,撬开偶发故障的第一道缝

设备跑了三天多,日志终于抓到一次完整的故障过程。把日志拉出来看,发现一个有意思的现象:每次故障发生前,两个气缸的到位状态都会在极短的时间内发生一次“先有后无”的变化。说白了,气缸A的到位传感器X10存在偶发抖动,信号会瞬时掉一下又恢复。硬件上传感器本身是好的,问题出在机械震动导致信号抖动。

故障的直接原因听起来很不起眼,但代码里的隐患让它放了整整一年。

原程序的逻辑是:气缸A伸出到位后,A的到位传感器X10直接作为气缸A缩回的启动条件;而气缸B伸出的启动条件,用的是“A伸出完成”的置位标志M10。也就是说,同一个X10传感器在程序里被用了两种方式:一个地方直接用原始信号,一个地方用锁存后的状态标志。正常运行时X10稳定,两条路径的结果一致,设备一切正常。X10闪断的瞬间,两条路径的结果恰好错开——直接读X10的路径变成了0,置位标志M10还保持在1,两个气缸的动作条件在同一时刻产生了矛盾,整个动作顺序就错位卡死。复位时所有状态清零,重新再来,又恢复正常。

3.3 根因不怪传感器,怪程序自相矛盾的判定条件

为什么程序能跑一年才出问题?因为机械磨损让震动越来越大,传感器闪断的频率逐步上升,最终进入了能被日志捕获的窗口。但真正的根子不在传感器,而在程序本身:同一个物理信号,有的地方用原始点,有的地方用锁存标志,逻辑上自相矛盾,隐含前提不统一。如果当初所有动作条件都统一使用经过滤波和锁存处理的状态量,X10的闪断根本不会传导到程序逻辑层,设备会一直稳定运行。

修复方向就很清晰了:不换硬件,把程序理顺。第一步,给X10这类关键输入加信号滤波,把持续时间过短的闪断直接过滤掉;第二步,所有气缸动作条件统一改用状态量,不再让任何网络直接引用原始输入点;第三步,给每一步动作加上“超时未完成报警”,哪一步卡住,程序直接告诉操作工是“气缸A伸出超时”还是“气缸B压装超时”,让故障可定位,而不是永远只显示“设备异常,请复位”。

这套修改做完之后,我没有马上验收。人为断开传感器、强制状态缺失、连续高速启动,把能想到的边界场景都造了一遍,确认重构后的状态机在异常场景下行为和原程序期望一致,生产条件下的正常时序也跟工艺手册完全相符。到目前为止,设备再没犯过同样的毛病。

3.4 这次排查反复印证的那句话

复盘这次事故,感触很深:那个程序在交付当天确实是能运行的,甚至运行了整整一年。但它一年后才把债爆发出来,爆发的时候谁都不知道该看哪里。如果当初写程序的人把状态位做成有名字的符号、把报警信息写清楚、把流程逻辑从M位接力改成显式状态机,这个故障可能当场就能在程序里看到,甚至根本不会发生。

这次排查也让我彻底告别了“能跑就行”的验收心态。再看到新的项目程序,我会先去翻它的关键输入是怎么处理的,报警信息给没给到位,状态是不是和输出分离——这几样过关了,才敢放心签字验收。

4. 从“能运行”到“写得好”:我的程序自检清单和改造路径

4.1 九条自检清单,一条条对着改

下面这九条是我现在看任何PLC程序都会过一遍的检查项,同样适用于项目评审和程序交接。每一条都可以直接在现有程序上核对,发现问题就记下来,作为下轮改造清单。

  1. 每个物理输出是否只有一处最终赋值?同一个输出点如果被两个以上网络改写,重构时优先改成单一赋值点。这是消除“最后扫描结果怪罪人”的第一步。

  2. 输入信号是否统一采集到了快照区?如果程序里到处直接读物理输入,中间穿插逻辑越多,扫描时序干涉就越严重。统一采集可以把这些干涉隔离在硬件层。

  3. 关键动作互锁是否双向?气缸伸与缩、正转与反转、上升与下降,不仅要防止同方向重复,还要防止另一个方向还没到位就反向动作,双向封锁才算真的锁住。

  4. 定时器预设值是否全部是符号常量或数据块变量?程序里如果还有很多裸写的K值,全局搜索一遍,能改就改成有名字的参数,改不了也要在注释里写明实际时间和对应动作。

  5. 报警信息是否精确到动作和传感器?如果所有故障都只显示“设备异常,请复位”,那程序对故障定位基本没有帮助。每一个报警至少要能回答“哪个动作、哪只传感器、什么原因”。

  6. 是否采用状态与输出分离的写法?从监控表里能不能一眼看出设备当前处于哪个状态、正准备干什么?如果只能看到一堆输出点在跳,说明状态层是缺失的。

  7. 全局变量是否有命名规范和注释?M200是干什么的?不写注释、不用符号名,三个月后连写的人自己都未必记得。

  8. 是否存在死代码和永远不跳转的联锁?那些从头到尾没有被置位过的M位、写了半截的条件分支、启动后就不可能变成真条件的联锁,都算死代码,留着只会干扰阅读。

  9. 下载修改是否安全?程序修改后重新下载,会不会初始化数据、清掉工艺参数?热修改安全是生产线敢不敢用你的程序的前提。

4.2 最值得先动手术的三个部位

如果现有程序问题很多,不可能一天全改完。按性价比排序,我会建议先动三个部位。

第一件,给所有变量起名字并加注释。别嫌工作量,一个几百点位的程序最多半天到一天。有了符号名,你和程序之间才有了交流的基础;没有名字,再高明的逻辑分析都施展不开。

第二件,把输入采集和输出赋值拆开。这一步是性价比最高的,做完整套程序一半以上的“扫描顺序依赖”问题会被直接消除,中间层的逻辑无论怎么重排网络,都不会影响最终输出。

第三件,把裸写的定时器预设值改成数据块参数。工作量大一点,但做完之后,现场调试和市场反馈阶段要临时改时间的频次会明显下降。这三个部位做完,程序就已经从“能跑”跨到“能维护”了。

4.3 不改结构只改编码:小步重构的顺序

我不建议把整台设备的程序推倒重来,尤其设备还在连续生产的情况下。重构有一条金律:先改内部结构,不改变外部行为。我的经验是先小后大、先边角后核心。

选一个相对独立、故障记录又比较多的局部子程序先动手,把状态机写法跑通;在一个功能块内完成“命名—拆分—集中赋值”三步,测试验证稳定之后,再铺开第二个块;每完成一块都回到仿真环境或现场空跑验证,不要攒到月底一次性大改。引入一个设备,就验证一个设备,稳扎稳打比一次到位可靠得多。

4.4 仿真用例:让好程序真正被验证出来

程序写得好不够,还得验证手段跟得上。我自己会在仿真环境里给每个核心流程搭一套基本用例:正常流程跑一遍,单步报警触发一遍,超时场景人为制造一遍,传感器信号丢失也制造一遍。只要这几套用例全部通过,程序在异常情况下的行为基本就可控了。

这个习惯看上去费时间,但等你在现场被“找不到原因”折磨过两三轮之后,会发现它值回票价。那次双气缸故障如果当初有这套用例,X10闪断场景在仿真里就能暴露状态不一致的逻辑漏洞,根本不用等设备跑一年再让用户发起投诉。

说回标题里那个问题。我现在看程序,第一眼看的不是能不能跑,而是它能不能被快速理解、被安全修改、被准确诊断。能运行只说明它完成了最低限度的任务,写得好才是它能陪你走完整台设备生命周期的底气。我自己的判断标准也在变化:以前是“通电能启动,走完流程就行”,现在是“三个月后另一个工程师接手,能不能半天内看懂整个状态逻辑”。每次想偷懒不写注释、不想拆分逻辑的时候,我就想想那台双气缸设备,想想那次两周的排查,然后老老实实把状态机写清楚。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询