最近一直在折腾三菱PLC的时间锁机程序,这次做的2.0版本算是把“时间锁”这个功能彻底玩明白了。从一开始只做一个固定到期日停机,到现在的三期时间锁、按表驱动动态扩展期数,中间的思路变化和踩坑记录都整理在这篇里。如果你也在做设备分期付款、租赁到期控制、按年服务续费这类需求,这文章可以直接拿来当参考思路,不一定照抄我的程序,但里面的逻辑和坑绝对值得提前看一遍。
做设备控制这块的朋友应该都清楚,PLC锁机不是简单的“时间到了断开输出”这么一句话。真到现场,要考虑客户不配合、设备被断电、时钟被篡改、电池老化各种情况。这次做2.0版本,我给自己定的目标是:稳定、抗绕过、容易扩展,同时不能让现场维护变成灾难。
1. 时间锁机到底解决什么问题
1.1 不只是停机,更是商业条款的“技术落地”
很多非工控圈的人会问,好好的一台设备,为什么要做时间锁?这事说白了就是分期付款和租赁业务。设备厂商把几十万的全自动设备交给客户,客户先付一部分钱,剩下的按季度或年度结清。如果客户不按时付款,厂商又不能派人把设备拖回来,这时候PLC里的时间锁就成了“最后一道保障”——到期没续费,设备自动进入锁定状态,无法正常生产。
这类需求在包装机械、注塑机周边、裁切设备、激光加工设备上特别常见。客户买的是生产能力,厂商收的是分期款,锁机程序就要精确到“年月日”,还得支持多个付款节点。这次2.0版本支持的三期锁,正好对应很多商务合同里的“首付后一年、第二年、第三年”三个节点,每个节点到期都要重新授权一次。
1.2 从1.0到2.0,改的不只是“多锁几次”
我最早做的1.0版本逻辑很简单:程序里写死一个到期日期,读PLC实时时钟,当前日期大于等于到期日就锁机。这个版本应付单期付款足够,但真放到商用场景里,问题一堆。第一,三期付款意味着要改三次程序,每次还得派工程师到现场,差旅成本比设备利润还高。第二,客户如果懂一点PLC,直接把日历改回去,锁机就形同虚设。第三,日期比较逻辑如果没处理好掉电保持,客户断电重启几次,锁机状态可能就丢了。
2.0版本这三点都做了重新设计。三期时间锁做成了一张“到期日期表”,每一期独立存储、独立判断,程序结构不随着期数增加而复杂化。防绕过方面加了时钟回拨检测,只要发现PLC日历被往回调,立即触发锁定。掉电保持则全部改用锁存区寄存器,从根本上解决“断电重启失效”的问题。
1.3 这个方案适合谁
如果你是设备厂的电气工程师、售后技术支持,或者自己接非标自动化项目的外包人员,这套思路会有直接用处。三菱FX系列PLC是当前中小型设备里占有率很高的型号,也是我这次实现的目标平台。文中给的寄存器分配表和判断逻辑,可以原样移植到FX3U、FX5U,其他品牌PLC只要支持读实时时钟和锁存寄存器,同样能参考。
2. 核心逻辑拆解:三期锁与扩展表
2.1 三步看懂2.0版状态机:正常、提醒、半锁、全锁
做时间锁最先要搞清楚的,是设备在整个期限周期里应该处于什么状态。我这次把状态拆成四个阶段。
第一阶段是正常期,设备全速运行,PLC界面上显示距离到期还有多少天,方便客户提前安排打款。第二阶段是第一节点到期但未续费,处于“提醒期”,设备可以继续运行,但每次开机都弹出倒计时警告,屏幕上持续显示超期天数。第三阶段是第二节点到期未续费,进入“半锁期”,设备只能手动操作,不允许自动连续运行,相当于把产能压到接近零,但保留调试模式方便售后处理。第四阶段才是真正意义上的锁定,第三节点到期后,所有输出全部切断,设备只能进入参数查看状态,必须输入新的解锁码才能恢复。
这四个状态从商务角度讲,给客户留了缓冲,也给厂商留了谈判空间。直接一刀切锁死容易引发纠纷,前面加了警告期和半锁期,客户就会明白“不是设备坏了,是确实该付款了”。
2.2 数据表设计:把“期数”变成一行行的配置
2.0版本最重要的变化,是把“期数”这个概念从程序代码里剥离出来,变成一张纯数据的表格。每一期在数据表里占用一组寄存器,内容包括:到期日期、该期是否已经解锁、解锁后有效期延长到哪天。程序运行时按顺序扫描这张表,逐期判断当前日期是否超过到期日。
以FX3U为例,我从D100开始分配:
| 寄存器地址 | 占用长度 | 含义 |
|---|---|---|
| D100-D101 | 32位 | 第一期到期日,存格式为YYYYMMDD |
| D102-D103 | 32位 | 第二期到期日 |
| D104-D105 | 32位 | 第三期到期日 |
| D110 | 16位 | 第一期解锁标志 |
| D111 | 16位 | 第二期解锁标志 |
| D112 | 16位 | 第三期解锁标志 |
| D120 | 16位 | 当前锁定等级 |
| D130-D131 | 32位 | 上次上电日期快照 |
| D140 | 16位 | 时钟回拨次数计数 |
D100这几组到期日,出厂前由工程师用编程软件或触摸屏配方写入,客户现场不需要重新下装程序。续费时只需要给客户一个解锁码,授权下一期继续使用,整个业务闭环就完成了。相比改程序,这种方式至少省掉了80%的售后差旅成本。
2.3 无限扩展的思路:循环扫描加变址寻址
三期锁是三组比较,那十期、二十期怎么办?总不能程序里Ctrl+C复制二十遍吧。这里用到了“表驱动”的通用思路:程序里只写一份比较逻辑,通过变址寄存器Z0依次指向表中的每一期。因为每一期占用的寄存器长度固定,比如每期两个字的日期加一个字的状态位,那么用Z0做地址偏移就能实现“指针滑动”。
FX3U的FOR/NEXT循环指令可以配合Z0变址使用。循环次数直接设为总期数,循环体内部完成一次“当前日期与到期日比较”和“解锁状态检查”,每次循环结束时给Z0增加一个固定偏移量。以后期数变化,只需要修改循环次数和数据表内容,程序一行都不用改。
这个设计思路看起来简单,但是有一个坑极其隐蔽。三菱的比较或数据处理指令如果使用了变址寄存器,而循环体内部又调用了输出位,很容易出现同一组中间位被反复覆盖的问题。后面第5节我会专门讲这个现场踩雷案例。
3. 手把手实现:基于FX3U的程序拆解
3.1 先搞定日期读取和转换:BCD是个大头坑
三菱FX系列PLC读取实时时钟最常用的是TRD指令。执行TRD D0之后,从D0开始连续7个寄存器里会依次存放秒、分、时、日、月、年、星期。注意这里面的数据格式是BCD码,不是普通的十进制数,这是整个程序里最容易出错的地方。
举个真实例子:5月这个月份,BCD码和十进制看起来一样,都是5。但是10月,BCD码在寄存器里表示为十六进制的0x10,PLC内部的实际数值是16,而不是10。如果直接把寄存器和常数K1010或者K20260210比较,结果全错。我之前见过一个同行,程序逻辑怎么看都对,就是锁机时间总是不对,最后查了一下午才发现是月份10被当成16来比较了。
解决办法是在比较前先把BCD码转成BIN码。三菱的BIN指令就是干这个用的。我习惯先把年份、月份、日期分别转到三个中间寄存器,再做等值比较。核心代码如下:
// GX Works3 ST风格,FX3U/FX5U均可参考 TRD D0; // D0秒 D1分 D2时 D3日 D4月 D5年 D6星期 BIN D5 RD_Year; // 年 BCD->BIN BIN D4 RD_Month; // 月 BIN D3 RD_Day; // 日 // 拼成YYYYMMDD整数,便于直接比较 RD_YMD := (2000 + RD_Year) * 10000 + RD_Month * 100 + RD_Day;这里把年份统一成4位,是为了避免“2026年1月”和“2025年12月”这种跨年比较时出现误判。如果不转成YYYYMMDD整数,就得写年、月、日三个比较嵌套,梯形图会非常啰嗦。转成长整数之后,日期比较就退化成两个数字比大小,简单明了。
3.2 到期判断与锁定动作:输出切断要放在哪一层
有了当前日期的整数形式RD_YMD,接下来的判断就顺理成章了。我对每一期做的事是:如果当前日期大于该期到期日,并且该期未解锁,就把当前锁定等级标记为这一期对应的等级。等级越高,设备权限越小。
锁定等级和被控输出之间,我是用“使能继电器”的方式做的。程序里定义一个M500作为“主运行允许”,正常状态下M500为ON。当检测到处于第三期锁定状态,M500强制为OFF。然后在所有涉及自动运行输出的程序段里,串联M500的常开触点,包括伺服使能、气缸动作、变频器启动信号等。
关键点在于,切断点一定放在“输出使能”这个公共层,而不是只断某一个执行机构。如果只切掉伺服,客户可能手动操作气缸完成部分生产;如果只切掉气缸,伺服空跑也没产能。M500这个公共使能一旦断开,整条自动流程的所有执行机构全部失效,而手动调试回路不受影响,这样才能既锁住设备又方便售后排查。
3.3 解锁码与续期授权:怎么设计才不会轻易被破解
解锁功能是时间锁的另一个重点。2.0版本的解锁逻辑我设计成了“期序号+日期校验码”的组合方式。每期到期日对应一组解锁码,客户输入解锁码后,程序先判断当前是否处于对应期数的锁定状态,再执行码值校验。
校验算法不要用简单的常数加减,否则客户拿一个码猜来猜去,几次就能试出来。我这次用的是日期散列方式,把到期日里的年、月、日和期序号做一个多项式运算,取模后生成校验值。类似这样:
CheckSum := (RD_Year * 13 + RD_Month * 17 + RD_Day * 19 + PeriodNo * 7919) MOD 10000; UnlockCode := PeriodNo * 10000 + CheckSum;这个算法客户在设备界面上看不出来,即使截获了一期的解锁码,也只能推算当前这一期,下一期算法继续变化。实际项目里解锁码通常由触摸屏输入框接收,写入D400,程序比对通过后,置位对应的D110/D111/D112解锁标志,同时把下一期的到期日保留为有效状态。
还有一组厂家维护码是独立的,固定算法,输入后直接跳过全部锁定状态。这组码只给厂商售后工程师使用,避免客户欠费后设备彻底瘫痪无法维修。维护码的算法写在带有注释的程序段里,现场调试时方便识别。
3.4 防回拨检测:防止客户“调时间”绕过锁机
做时间锁最怕的一件事,就是客户把PLC时钟往回调。比如第三期已经锁了,客户把日历改成2025年,设备以为还在第一期内,锁机瞬间失效。
我的应对方案是在PLC里记录上次上电时的日期快照。每次PLC上电,先执行一段检查逻辑:读取当前日期,与D130中存储的上次日期做比较。如果当前日期比上次日期小了,说明时钟被回拨过,直接触发锁定,并把回拨次数加一。回拨次数连续累计超过三次,程序锁定为永久锁死状态,只能由厂家维护码恢复。
这个方案在实际设备里非常有效。正常使用情况下,PLC断电后再上电,当前日期只会大于或等于上次日期,因为时间不可逆。除非客户刻意修改,否则不可能出现“日期变小”的情况。唯一需要小心的是首次上电时D130里没有有效值,需要在初始化逻辑里做一个判断,当D130等于0时直接把当前日期写入,不做回拨判断。
4. 现场调试的常见问题与排查技巧
4.1 电池耗尽导致误锁:最隐蔽的杀手
三菱FX系列PLC靠内部电池维持实时时钟和锁存寄存器。电池寿命一般在5年左右,但很多设备现场环境温度高,电池老化速度远超预期。电池没电后常见现象是:PLC上电时间归零,日期回到出厂默认值。
这时候防回拨逻辑会判定“当前日期小于上次日期”,触发锁机。现场看起来就是设备突然锁死,但客户说我没动日期啊。排查时需要看PLC特殊寄存器M8015、M8016,它们分别代表电池电压下降和锂电池电压过低报警。我后来在程序里加了电池低压提示:一旦M8015置ON,触摸屏直接弹出“请联络厂家更换电池”的提示,避免“锁机原因其实是电池没电”这种尴尬情况。
建议你在调试时给程序加一段电池状态读取逻辑,哪怕不显示在界面上,也要把状态存到某个寄存器,方便远程诊断时第一时间判断。
4.2 锁存区设置不正确,一断电就“解封”
三菱FX3U的普通数据寄存器D0到D199属于停电清除区,断电后内容恢复为0。如果锁定标志、期数状态放在这些地址里,客户只需要断电重启,锁定状态就全部清零,时间锁形同虚设。
正确做法是把所有需要掉电保持的数据放到锁存区。FX3U默认锁存区从D512开始,上限范围可以在GX Works软件的参数里配置。但很多人忽略了一个细节:如果程序里使用了超出默认锁存范围的地址,而参数里又没有把该范围设置为锁存,你还是会遇到断电丢数据的现象。
我这次分配时把D100一直到D210全部放在锁存范围内,并在程序开头写了一段自检逻辑:上电后先判断D110-D112解锁标志的值是否在合法范围,如果发现内容异常,说明曾经发生过数据丢失,立即重新进入锁机状态。这个自检逻辑是最后一道防线,也是很多通用锁机方案没做的。
4.3 循环体里的位元件覆盖问题
这是我这次开发中真正踩过的一个坑。用FOR循环和Z0变址实现多期扫描时,我把比较结果写到了一个公共中间位M0上。第一期比较完M0有结果,第二期一开始还没比较完,M0又被复位了。最终表现是,只有最后一期的判断结果有效,前面几期到期状态全部被覆盖。
解决方式有两种。第一种最简单直接,三期就写三段重复的比较逻辑,每期使用独立的结果位,比如M501、M502、M503,这样互不干扰。第二种才是表驱动方案的标准写法:循环体内不直接使用位元件参与后续逻辑,而是将本期锁机结果累加到一个寄存器D120里,用D120的数值表示当前最高锁定等级。循环结束后再根据D120的值一次性设置M500公共输出。这样既保留了扩展性,又避开了位覆盖问题。
如果你不是特别在意“无限扩展”这个卖点,我更推荐第一种。工业现场稳定压倒一切,程序多写几十行梯形图完全无所谓,但判断逻辑出问题造成的停机损失是不可接受的。
4.4 解锁码暴力穷举防护
既然做了解锁功能,就不得不考虑客户会不会反复试码。虽然校验码是四位数字,理论上有一万种组合,但如果不设限制,客户买个PLC调试工具慢慢试,总能试出来。
程序里我加了一个输入次数计数器,放到锁存寄存器D141。连续输入错误码5次,锁定输入功能30分钟,期间任何新的输入都被忽略。这个计数器在正确解锁后自动清零,不会影响正常续费操作。为了防止现场售后被锁在外部干着急,厂家维护码不参与这个错误计数,维护码永远可用。
4.5 给售后留好“应急旁路”
软件锁做得再完善,也要想到一种极端情况:客户到期当天正好遇到生产高峰,款已经打了但解锁码还没下发,设备锁死导致生产线停线,这个责任最终会落到厂商头上。
我在设计里增加了一个隐藏的应急旁路输入端子。售后工程师到达现场后,可以通过短接特定输入点,让PLC进入为期72小时的临时生产模式。这个旁路信号同时记录在锁存寄存器里,每次使用都会留下时间戳。它不能永久解除锁定,只作为“款到账但码未发”的临时过渡方案,到期后设备会再次进入锁定状态。
这个设计在商务上非常讨巧,既体现了厂商的服务意识,又保留了锁定约束力。实际调试的时候,记得把旁路端子选在控制柜内不容易被客户误碰的位置,并用中文标签提示“仅限厂家售后使用”。
5. 一些实验后的心得与建议
5.1 纯软件锁和硬件锁的边界在哪里
现在很多设备开始用带加密芯片的硬件锁模块,通过IO通信与PLC交互,破解难度确实比纯软件方案高不少。但硬件锁成本高、接线多、模块本身也有故障率。以我这次做的设备类型来说,单价在三十万以下、分期周期三年内的设备,纯PLC软件锁完全够用;如果是上百万的设备,建议还是加硬件锁,因为一旦被破解,损失远超那几十块的硬件成本。
判断标准很简单:客户有没有动力花大力气去破解。低价值设备靠的是“合同约束+软件提醒”,高价值设备才需要“物理对抗+加密验证”。别一上来就追求最高安全等级,设备厂商最怕的是软件锁误锁导致服务口碑崩盘。
5.2 寄存器分区的扩展技巧
如果你打算把这个方案做成产品,寄存器规划一定要比实际期数多留50%的空间。我这次虽然只写了三期,但数据表从D100起预留到了D199,中期如果要扩展到十期、二十期,完全不需要调整程序结构。
日期表、状态表、临时计算区、系统参数区之间要留出足够的隔离空间。我这次把临时计算区放在D20-D50,日期表从D100开始,状态表从D110开始,系统参数从D200开始。每个区域之间至少空出来5个寄存器,防止以后加功能时地址重叠,也方便现场调试时用软件监控数据。
这个习惯让我后期维护省了很多事。每次修改程序,只要查表就知道哪些寄存器已经被占用,不会因为地址冲突出现莫名其妙的故障。
5.3 程序版本号和管理台账
最后说一个跟技术不太相关但非常重要的建议:给你的锁机程序加上版本号寄存器。我在D200里放了程序版本号,比如V2.00,每次修改解锁算法或判定逻辑,版本号同步更新。设备出厂时,把版本号、解锁码算法、到期日期信息登记成台账。这样哪台设备是哪一版程序、用的什么算法,一查就知道。
遇到过最头疼的售后场景是:客户打电话说设备锁了,但售后到了现场发现程序版本和出厂记录对不上,根本不知道该用哪组解锁码。有了版本号,这个问题三十秒就能定位。
我这套2.0版本已经在某设备厂的分切机上跑了一个多月,目前没有出现过一次误锁。最让我满意的不是三期锁本身,而是“表驱动+回拨检测+售后旁路”这几个设计组合起来之后,整个方案变得非常省心。下一步我打算把运行小时数锁机也加进去——有些设备不是按日期续费,而是按累计运行时间计费,核心思路和日期锁完全一致,只是把“当前日期”换成“累计运行小时数”而已。这部分的实现等我在现场验证没问题之后,再整理出来分享。