这些年做西门子S7-1200/1500项目,我最大的体会是:凡是遇到“一堆条件同时进来,必须按优先级挑一个执行”的逻辑,就用SCL写For循环去扫,基本都能把程序写得又短又清楚。这次要聊的电梯楼层优先级调度,就是最典型的一个例子。16层楼,轿厢里的选层按钮、每层厅外的上召和下召,再加上消防强制返回基站这种特殊请求,信号量不算大,但互相之间的优先级关系特别绕。如果全用梯形图去堆,程序写出来跟蜘蛛网一样,改一个条件就可能牵一发动全身。用SCL的For循环,把每一层当成数组里的一个元素,对整个请求表扫描打分,最后挑出得分最高的楼层,整个调度逻辑就变成了一段能读懂的文本代码。
这篇文章,我把自己从需求拆解、调度算法设计、TIA Portal实际编程,再到触摸屏联动演示的完整过程都放进来。适合正在学SCL、想用结构化文本做复杂逻辑的同行,也适合被类似“排号叫号”逻辑困扰、想换个思路写程序的朋友。文章不是教科书式的语法罗列,而是从真实项目视角出发,讲清楚每一步为什么这么做。
1. 电梯调度问题的本质:为什么For循环是最优解
1.1 请求类型与数据结构:把物理按钮映射成数组
电梯调度在软件层面其实不复杂,难点在于它同时存在多个“请求源”。以16层住宅楼为例,电梯要处理的请求至少分成四类:
- 轿厢内选层指令:乘客在轿厢里按的楼层按钮,这种指令一旦登记,在到达前一直有效。
- 厅外上召:某层有人在等电梯并且要上行,按下行方向箭头里的“上”按钮。
- 厅外下召:某层有人要下行,按下“下”按钮。
- 特殊/紧急请求:消防返回基站、地震管制返回等,需要无条件抢占最高优先级。
这四类信号如果用梯形图表达,最常见做法是给每层每个方向建一个中间继电器,然后用一大堆常开常闭触点去互锁。楼层少还勉强能看,一旦楼层超过8、10层,触点组合就非常难看。改一个楼层需求,要翻好几页程序。
换成SCL思路以后,我第一件事永远是画数据结构,不急着写代码。16层楼,那就建三个长度为16的布尔数组:内部呼叫数组、上召数组、下召数组。数组下标就是楼层号,数组元素是TRUE就代表该楼层有一个等待中的呼叫。紧急请求特殊一点,用一整个BOOL加一个INT记录紧急楼层。
这种“楼层当数组下标、按钮状态当数组元素”的做法,是后面所有For循环逻辑的地基。数组天然支持循环遍历,写一行循环体就能覆盖16个楼层的检查,这就是结构化文本相对梯形图的本质优势——用数据组织代替触点堆叠。
1.2 梯形图也能做,但为什么我坚持用SCL
有工程师会问:梯形图用比较器也能实现同样的功能,为什么要换成SCL?我给你讲个实际场景。
梯形图做优先级比较,通常需要两两比较的逻辑链:呼叫A和呼叫B比较,胜者再和呼叫C比较,一层层嵌套下去。楼层多了以后,这个树状结构会非常庞大。更麻烦的是,当优先级规则调整时——比如原来“同方向顺路优先”,后来改成“附近楼层优先”——电气工程师就要在几百个触点里去找到底哪一条线路决定优先级,改完还要担心影响旁边其他逻辑。
SCL的For循环完全绕开这个问题。优先级规则不是靠触点串联实现的,而是靠“打分公式”实现的。规则变了,改几行赋值语句就行。而且For循环天然可读:从第1层循环到第16层,每一层的呼叫做加法、比较大小,逻辑一目了然。
1.3 SCL语言在逻辑密集型控制里的优势与局限
SCL在很多自动化工程师眼里“偏软件”,初期确实要适应。它的优势非常明显:
- 数组、循环、数学运算、字符串处理都很顺手,适合做算法类控制逻辑。
- 代码即注释,只要变量命名规范,半年后回看还能读懂。
- 便于团队协作和版本管理,改动可以用文本对比工具看差异。
但它也有短板。首先是电气专业背景的同事接受度低,现场维护时不一定人人都会看ST文本。其次是SCL调试时不能像梯形图那样在触点上一眼看出导通状态,必须靠监控变量。所以我的建议是:你可以在一个项目里混合使用LAD和SCL。设备级逻辑、安全回路互锁这类“看趋势”的逻辑继续用梯形图,而调度算法、数据处理、通讯解析这类“靠逻辑”的代码坚决用SCL。
2. SCL For循环的调度原语:从扫描到打分
2.1 For循环语法和执行模型,理解这几条就够了
TIA Portal里的SCL For循环,基本格式就是:
FOR #i := 1 TO 16 BY 1 DO // 循环体 END_FOR;几个关键点必须先搞清楚:
- 循环变量必须是整数型(INT、DINT),不能用REAL。
BY后面的步长可以省略,默认是1;反向循环写BY -1。- PLC里的For循环是在同一个扫描周期内全部执行完的,不是分多个周期慢慢跑。这个理解和单片机里的循环完全一致,所以循环次数过大时要注意扫描周期时间。
- 循环中如果出现数组越界或者除数为零,会直接触发OB的停止或诊断事件,不是跳过出错那一下那么简单。
正因为For循环在一个周期内执行完,它特别适合做“扫描类”任务:数据整理、请求登记、优先级裁决。电梯16层才循环16次,扫描周期影响微乎其微。
2.2 方向扫描:先确定电梯“顺不顺路”
写电梯调度,第一步不是直接挑一个最近楼层,而是先明确电梯此时的运行方向。电梯方向分三种:上行、下行、静止。方向会影响哪些呼叫“值得登记”:
- 电梯正在上行时,优先响应当前楼层以上、且是“上行方向”的呼叫;下行呼叫通常不顺路,我们可以取消或者放到反向扫描里。
- 电梯正在下行时同理,优先处理当前楼层以下的下行相关呼叫。
- 电梯静止时,哪个方向都可以,但要考虑启动后的新方向。
用SCL表达这个扫描逻辑,可以写一个简单的“顺路检测”片段:
// 只扫描当前方向上方的楼层 FOR #i := "Elevator_Data".CurrentFloor + 1 TO "Elevator_Data".MaxFloor BY 1 DO IF "Elevator_Data".CallUp[#i] OR "Elevator_Data".CallInternal[#i] THEN #nextFloor := #i; EXIT; // 向上扫到第一个请求就停,就是最顺路的 END_IF; END_FOR;这种“带方向扫第一个”的算法,用于单台电梯的顺路截梯场景非常实用。EXIT在这里起到了“提前跳出循环”的作用,避免继续扫后面楼层,节省不必要的计算。
2.3 优先级打分模型:把规则变成公式
不过,实际项目往往不只是“顺路第一”。优先级的判定还要把呼叫类型和距离综合起来。简单的“方向扫描加EXIT”不能满足所有场景。我会做一层“打分模型”,给每个待选楼层算一个分数,分数最高的楼层就是下一个目标。
打分模型我通常这么设计:
| 影响因子 | 权重值 | 设计理由 |
|---|---|---|
| 轿厢内呼 | +30 | 乘客已经在轿厢里,响应优先级高于厅外召唤 |
| 厅外召唤(与方向匹配) | +20 | 顺路方向上的外呼优先级稍低 |
| 与当前运行方向一致 | +100 | 电梯尽量保持一个方向走完,避免频繁换向 |
| 与当前运行方向相反 | -100 | 反向请求在正常扫描阶段不做考虑 |
| 楼层距离 | 每层 -5 | 同等条件下,越近越优先 |
这套权重不是拍脑袋定的。“方向权重±100”的作用是让“同方向”请求在分数上形成压倒性优势;“内呼+30 vs 外呼+20”保证同层同时有内呼和顺路外呼时先完成轿厢内任务;“距离-5”则是为了让同一方向上的近端楼层比远端楼层更容易被选中。
2.4 CONTINUE和EXIT:循环控制不是花架子
很多初学者看FOR循环只用“干跑”,不知道CONTINUE和EXIT在SCL里有多有用。
CONTINUE:跳过当前这个循环变量的剩余循环体,直接进入下一轮。在电梯扫描里,如果某层楼完全没有任何有效呼叫,继续计算这层的分数就是浪费。用CONTINUE快进跳过。EXIT:终止整个循环。方向扫描时,找到第一个同方向楼层就可以立刻退出,不需要再看更高楼层了。
在完备的打分模型里,因为要遍历所有楼层选出最高分,EXIT用得少;但如果你写的调度策略是“当前方向顺路优先,扫到就决定”,那EXIT就是性能利器。两种循环控制结合使用,能让程序在“全覆盖打分”和“单方向快扫”之间灵活切换。
3. TIA Portal中实现电梯调度FB块:可运行代码逐段拆解
3.1 先建数据蓝图:全局DB设计
正式写代码前,我在项目里会先建一个全局DB,专门用来存放电梯的请求和状态。这个DB既是PLC程序的数据中枢,也是后面触摸屏联动读取和写入的“共同语言”。推荐字段大致如下:
全局DB: Elevator_Data - MaxFloor : INT // 总楼层数,默认16 - CurrentFloor : INT // 当前楼层 - Direction : INT // 1上行,-1下行,0静止 - IsMoving : BOOL // 运行标志 - DoorOpen : BOOL // 门区标志 - CallInternal : ARRAY[1..16] OF BOOL // 轿内选层请求 - CallUp : ARRAY[1..16] OF BOOL // 上召请求 - CallDown : ARRAY[1..16] OF BOOL // 下召请求 - EmergencyActive : BOOL // 消防/紧急返回激活 - EmergencyFloor : INT // 紧急返回的目标楼层(如1层) - TargetFloor : INT // 调度选出的目标楼层 - BestScore : INT // 调试用,显示最高分 - bHmiEnable : BOOL // 触摸屏上的“允许自动调度”总开关这里有个细节:CallInternal/CallUp/CallDown直接用数组,触摸屏和HMI都可以按数组方式访问,不需要额外做数据搬运。如果你的HMI不支持直接读写PLC侧的优化DB数组,后面我会讲到怎么处理。
3.2 FB接口设计:把变化隔离在接口层
电梯调度我习惯用FB写,不写FC。原因很简单:FB自带背景DB,可以在背景DB里保存计时器实例和中间状态,多次调用时互不干扰。接口我设计得尽量清爽:
FUNCTION_BLOCK FB_ElevatorCtrl VAR_INPUT bEnable : BOOL; // 调度使能 bSimMode : BOOL; // 演示模式:用内部定时器模拟电梯移动 END_VAR VAR // 循环扫描临时变量 i : INT; callWeight : INT; dirWeight : INT; distance : INT; score : INT; bestScore : INT; bestFloor : INT; hasCall : BOOL; // 演示运动用定时器 simTimer : TON; doorTimer : TON; END_VAR接口只留两个输入:bEnable总使能开关,bSimMode演示模式开关。实际项目里,输入还可以增加当前楼层位置反馈、门区信号、上下限位、变频器运行反馈等。但核心调度算法的内部变量不需要暴露到接口层,这样以后换IO点,只改调用处,不碰FB内部逻辑。
3.3 核心调度代码:For循环扫描加打分
这一段是整个调度块的核心,我一行行拆开讲。
// ---------- 调度主逻辑 ---------- IF NOT #bEnable THEN RETURN; END_IF; // 1. 紧急请求绝对优先 IF "Elevator_Data".EmergencyActive THEN "Elevator_Data".TargetFloor := "Elevator_Data".EmergencyFloor; "Elevator_Data".Direction := 0; "Elevator_Data".IsMoving := FALSE; RETURN; END_IF;紧急请求强制优先,这是电梯行业里的硬规矩。消防时电梯必须停到指定层并开门,任何正常调度请求都靠边站。这段代码放在调度开头,就是为了保证不管前面扫描结果如何,紧急状态下都只有一个目标。
// 2. 电梯静止且没有目标时,执行一次全楼层扫描 IF NOT "Elevator_Data".IsMoving AND "Elevator_Data".TargetFloor = 0 THEN #bestScore := -32767; #bestFloor := 0; #hasCall := FALSE; FOR #i := 1 TO "Elevator_Data".MaxFloor BY 1 DO // 2.1 汇总该楼层呼叫权重 #callWeight := 0; IF "Elevator_Data".CallInternal[#i] THEN #callWeight := #callWeight + 30; END_IF; IF "Elevator_Data".CallUp[#i] AND "Elevator_Data".Direction >= 0 THEN #callWeight := #callWeight + 20; END_IF; IF "Elevator_Data".CallDown[#i] AND "Elevator_Data".Direction <= 0 THEN #callWeight := #callWeight + 20; END_IF; IF #callWeight = 0 THEN CONTINUE; END_IF; #hasCall := TRUE; // 2.2 方向权重 IF "Elevator_Data".Direction = 0 OR ("Elevator_Data".Direction = 1 AND #i > "Elevator_Data".CurrentFloor) OR ("Elevator_Data".Direction = -1 AND #i < "Elevator_Data".CurrentFloor) THEN #dirWeight := 100; ELSE #dirWeight := -100; END_IF; // 2.3 距离权重 #distance := ABS(#i - "Elevator_Data".CurrentFloor); #score := #callWeight + #dirWeight - #distance * 5; // 2.4 记录最高分 IF #score > #bestScore THEN #bestScore := #score; #bestFloor := #i; END_IF; END_FOR; // 3. 有请求就派梯 IF #hasCall THEN "Elevator_Data".TargetFloor := #bestFloor; "Elevator_Data".BestScore := #bestScore; IF #bestFloor > "Elevator_Data".CurrentFloor THEN "Elevator_Data".Direction := 1; ELSIF #bestFloor < "Elevator_Data".CurrentFloor THEN "Elevator_Data".Direction := -1; END_IF; "Elevator_Data".IsMoving := TRUE; END_IF; END_IF;这段代码最核心的思路,就是把“哪个楼层该去”这道选择题,变成“给每个楼层算一道分”的数学题。callWeight处理呼叫类型,dirWeight处理方向,distance处理远近,三者加权以后取最高分。For循环在这里不是简单遍历,而是充当了一个全楼层“评委”。
需要注意的是:IF NOT IsMoving AND TargetFloor = 0这个外层的条件。也就是说,只有当电梯停车到位、并且当前没有既定目标时,才重新扫描派梯。这样设计是为了避免电梯运行途中反复修改目标楼层造成“截梯抖动”。
3.4 演示运动模拟与到站清呼逻辑
真梯项目里,电梯的移动由变频器和编码器反馈控制,这一段的实现方式完全不同。但为了触摸屏联动演示,我们需要在PLC内部模拟“电梯正在移动”的效果。最简单的做法就是加一个1秒的定时器,每隔1秒把CurrentFloor向TargetFloor方向走一层。
// ---------- 演示模式运动模拟 ---------- IF #bSimMode AND "Elevator_Data".IsMoving THEN IF "Elevator_Data".CurrentFloor <> "Elevator_Data".TargetFloor THEN #simTimer(IN := TRUE, PT := T#1S); IF #simTimer.Q THEN #simTimer(IN := FALSE); "Elevator_Data".CurrentFloor := "Elevator_Data".CurrentFloor + "Elevator_Data".Direction; END_IF; ELSE // 到达目标楼层:清呼、开门、复位 #simTimer(IN := FALSE); "Elevator_Data".IsMoving := FALSE; "Elevator_Data".CallInternal["Elevator_Data".TargetFloor] := FALSE; "Elevator_Data".CallUp["Elevator_Data".TargetFloor] := FALSE; "Elevator_Data".CallDown["Elevator_Data".TargetFloor] := FALSE; "Elevator_Data".DoorOpen := TRUE; #doorTimer(IN := TRUE, PT := T#2S); IF #doorTimer.Q THEN #doorTimer(IN := FALSE); "Elevator_Data".DoorOpen := FALSE; "Elevator_Data".TargetFloor := 0; END_IF; END_IF; END_IF;这段代码在真实项目里不会这么写,但在演示和调试阶段非常实用。它让我不依赖真实变频器就能把整个调度流程“跑起来”,触摸屏上的楼层数字会自己动,肉眼就能验证调度是否正确。
这里还有一个关键细节:清呼。电梯到站后,必须把该楼层对应的内部呼叫、上召、下召复位掉,否则请求会一直存在,电梯会在同一层反复开门。复位要结合当前方向和请求类型,比如电梯上行到达5层,那么5层的上召可以清,但5层的下召必须保留,留给电梯换向下行时响应。
3.5 边界情况:首尾楼层、同层呼叫与换向
边界处理是电梯调度最容易翻车的地方。我列几个典型场景:
- 电梯在1层,所有请求都在1层。这时候
distance=0,目标就是当前层,应该直接开门清呼,不能出现“电梯试图下楼”的荒谬动作。 - 电梯在最高层16层向上运行,此时如果16层有上召,因为16层已经是顶层,上召按钮通常物理上就不会安装。但程序要做好防护:
CallUp[16]即便为TRUE也应该忽略,因为不可能向上走了。 - 电梯从6层上行去8层途中,有人在7层按了下召。7层是下行方向,按正常调度“反方向-100分”会被忽略;但当电梯运行到8层、清呼完成之后,如果7层下召仍然有效,就应该在下一轮扫描中进入下行队列。
这些边界条件不是锦上添花,而是决定程序能不能长期稳定运行的关键。我在真实项目里见过太多因为边界处理不够,导致电梯到了顶层还继续往上走,或者到站后不消号重新跑一次的情况。
4. 触摸屏联动演示:MTP/KTP系列HMI工程配置
4.1 屏与PLC的数据交换机制
电梯调度做完了,不能总靠在编程软件里监控DB看数据。触摸屏联动是项目验收时最直观的部分。西门子触摸屏和PLC在TIA Portal里属于同一个工程体系,组态思路本质上就是“建立变量连接、把PLC里的变量拖到画面上”。
KTP系列面向Basic Panel,直接在TIA Portal WinCC V16/V17里组态。MTP1000这类Unified面板需要使用WinCC Unified,整体思路一致,但工程文件和画面模型有差异。下面以KTP700为例说具体操作,如果你用的是MTP系列,把“HMI变量表”和“画面编辑器”的位置换一下就可以。
触摸屏和PLC之间走Profinet(或者PROFIBUS)通讯,HMI通过变量通道读取/写入PLC的DB、M区、I/O区。关键点在于HMI变量必须和PLC变量建立一一对应关系。最省事的方式,是在HMI变量表里直接添加PLC变量的路径,例如:
PLC变量: Elevator_Data.CurrentFloor HMI变量: Elev_CurrentFloor这样读写关系就绑定了,画面上的显示组件指向HMI变量即可。
4.2 创建电梯状态显示画面
工程里新建一个HMI画面,名称建议叫“电梯调度演示”。画面大致分四个区域:
- 顶部状态区:显示当前楼层、目标楼层、运行方向、门状态。
- 中部竖井模拟区:用16个指示灯代表1到16层,当前楼层那一个亮起。
- 左侧轿内选层区:16个按钮,模拟轿厢内按楼层。
- 右侧厅外召唤区:每层两个按钮,分别代表上召和下召(首层只有上召,顶层只有下召)。
当前楼层的显示是最容易的,直接拖一个IO域或者文本域,关联Elev_CurrentFloor。为了更直观,我还习惯在竖井模拟区放16个圆角矩形,每个都用一个“可见性”动画,透明度由“当前楼层是否等于该层号”决定,这样屏幕上就能看到一个小光点随着电梯升降移动。
这个“可见性动画”的做法,比用多层子画面切换更稳定,而且几乎不消耗HMI性能。16个图形控件,每个都关联一个公式:Elev_CurrentFloor == 1、Elev_CurrentFloor == 2……实话说,组态时工作量稍微大一点,但运行效果非常直观。
4.3 内部呼叫与外部召唤的触摸输入设计
轿内选层按钮,组态时可以批量实现。在KTP画面的按钮属性里,事件页签选择“单击”动作,功能选择“SetBit”,变量指向Elevator_Data.CallInternal[1]到CallInternal[16]。每个按钮配一个指示灯,指示灯用“位可见性”关联对应的请求位,这样按下去以后按钮会高亮或者变色,表示该楼层呼叫已被登记。
厅外召唤按钮也类似,但变量分别指向CallUp[1..16]和CallDown[1..16]。这里注意一个细节:按钮的“按下”动作和“释放”动作不要都写变量操作,否则可能产生重复置位或抖动。我只在“按下”事件里做置位,释放事件不做操作,这样最干净。
如果你嫌16个按钮一个个组态太累,可以使用TIA Portal的“变量批量创建”功能。先在HMI变量表中建一个变量数组或者按命名规则批量生成变量,再把按钮的“数组元素索引”绑定到变量数组的下标上。但这个操作稍微绕一点,新手建议还是规规矩矩复制粘贴,十分钟就能点完。
4.4 优化DB、绝对寻址与HMI常见连接问题
这里分享一个实战中容易踩的坑:S7-1200/1500默认使用优化DB访问,也就是变量没有固定偏移地址,只有符号名。传统HMI组态里,有些老版本WinCC或者第三方屏要求你填绝对地址(比如DB1.DBW0),这时候优化DB会显示离线,或者在线监控时看不见数据。
解决方法是二选一:
- 在DB属性中取消勾选“优化的块访问”,改成标准访问。但这样会失去符号寻址的便利,而且一部分高级语法特性可能受影响。
- 如果是KTP/MP系列和TIA Portal同版本,保留优化DB即可,HMI变量里直接拖拽PLC变量路径,不需要也不应该手动填绝对地址。
如果你的屏幕比较老,或者第三方屏,那就要把数据单独做一份“非优化DB”用于HMI交互,PLC内部跑调度就用优化DB。两块区域之间用MOVE指令同步,这种隔离方案是工程界最稳妥的做法。
触摸屏连不上时,优先检查三件事:IP地址是否同一网段,PLC与HMI是否都处于RUN状态,HMI变量是否指向了正确的PLC连接和DB。别小看这三件事,我调试时遇到的触摸屏问题,八成都是IP冲突或者变量路径指错了。
5. 调试验证与实战踩坑:文档里不会写的事
5.1 用PLCSIM验证For循环调度逻辑
TIA Portal的PLCSIM可以完美仿真S7-1200/1500,所以电梯调度逻辑在没接真实硬件之前就能验证。我推荐的做法是,在项目里建一个全局DB专门用来“注入测试请求”,比如手动置位几个呼叫位,然后观察TargetFloor和Direction的变化。
具体步骤:
- 把程序下载到PLCSIM,CPU切到RUN-P。
- 打开“监控与强制表”,把
Elevator_Data.CallInternal[5]强制为TRUE。 - 观察调度结果,此时
CurrentFloor=1、Direction=0、TargetFloor应该自动变成5。 - 再把
CallUp[3]也置TRUE,观察在电梯未动之前,TargetFloor是否会从5变成3。
第4步是个有趣的测试:当前电梯静止在1层,内呼去5层,同时3层有人上召。按“方向权重”模型,两个请求都是同方向,但3层距离近,得分更高,所以调度会优先选择3层,顺路接上3层乘客再去5层。这就是典型的“顺路截梯”逻辑验证。
5.2 常见错误一:数组下标越界与扫描周期
For循环最常见的故障是数组越界。比如你把MaxFloor从16改成24,但数组定义还是ARRAY[1..16],For循环运行时第17次访问就直接报错,CPU进入停机状态。TIA Portal对这类错误诊断比较明确,但预防更重要:数组维度和循环边界要来自同一个地方。
另一个容易被忽略的问题是For循环会占用扫描周期时间。电梯楼层16层,循环体就扫16次,CPU负荷完全在可接受范围内。但如果你把同样的套路用在“遍历几百个配方参数”上,每次扫描周期多出几毫秒,对高速设备来说就是事故。遇到大数据量遍历时,可以考虑分周期扫描,或者把For循环改写成“一次周期只处理几个元素”的节流逻辑。
5.3 常见错误二:FB背景DB的实例选择
使用FB必然涉及背景DB。电梯调度如果只有一栋楼一台电梯,那单实例FB就够了。但如果项目是双电梯并联,你要么新建两个FB实例,分别挂两个背景DB,要么在FB里用多重背景的方式管理。多重背景的好处是背景DB集中成一个,方便整体上传下载,缺点是变量路径会多一层,程序阅读稍难。
我给的建议是:单梯项目用单实例,双梯并联动不动就上多重背景。但不管哪种方式,背景DB的变量要命名清晰,别出现Instance_Data_1这种没有业务含义的名字。调度块就是Elev_Ctrl_Left、Elev_Ctrl_Right,一眼就分得清。
5.4 从演示到真梯:安全功能绝不放进For循环
最后必须强调一点:本文展示的调度逻辑属于功能控制层,负责“该去哪一层”。但真实电梯项目里,安全功能——比如门锁回路、超速保护、上下限位、轿厢安全钳动作——绝对不能放在调度逻辑里用软件判断了事。这些必须通过独立的安全回路、安全PLC、安全继电器去实现。
我在演示项目里可以把DoorOpen直接放进DB里随便置位,但在真实项目中,门状态信号来自门锁继电器回路,PLC逻辑只能读取,不能绕过安全硬件直接控制。写调度程序时,务必将“舒适/效率控制”和“安全保护”分开。这既是行业规范,也是职业底线。
还有一个经验:逻辑写完以后,至少要在PLCSIM上跑一整天的随机呼叫测试,让脚本随机置位各种呼叫组合,确认电梯在任何情况下都不会出现“上着上着突然换向下行”“到站不开门”“请求永远消不掉”这类问题。很多随机组合是手测很难覆盖到的,自动化压力测试才是验证调度算法可靠性的正路。这个习惯,我几乎在每个调度类项目里都会用,效果远好于人工一个个按钮去点。