
1. 赛题分析与整体方案设计1.1 六部十层电梯题到底在考什么2021年西门子杯初赛的电梯题目核心是六部电梯、十层站台的群控调度。别被“电梯”两个字带偏比赛真正要的并不是你写出一个能用就行的梯形图而是要在有限时间内拿出一套稳定、高效、可复现的调度策略。跑分系统会按一组预设的客流场景反复给电梯发内呼、外呼指令然后统计平均候梯时间、最长候梯时间、轿厢停靠准确率、运行效率这些指标。说白了这不是“能不能动”的问题而是“动得好不好、稳不稳”的问题。我当时拿到题目的第一反应是六部电梯虽然看起来多但如果你把每部电梯都当成完全独立的个体去写程序逻辑会爆炸。六部电梯之间需要协同而协同的核心就一个字派。谁来接这个外呼接了之后走什么路线路线上顺路带谁这一连串决策才是群控的难点。单部电梯的上下行、开关门反而是最基础的部分。这个题目适合两类人参考。第一类是刚接触西门子PLC、想用竞赛练手的在校生你能从里面看到一套完整项目的拆解思路。第二类是已经在用博途做工程、但没做过群控调度的人电梯模型虽然是简化的但调度思想其实可以直接迁移到物流分拣、多设备协同这些场景里。文章里我尽量把每一步“为什么这么干”也讲清楚而不是只贴一段让你抄的程序。1.2 总体架构与控制方案选型六部十层电梯放在真实工程里属于典型的“多对象协同控制”所以我第一件事不是写程序而是搭架构。整体分三层现场层、控制层、调度层。现场层就是每部电梯的物理信号位置编码、平层信号、上下行限位、开关门到位、超载、内呼按钮、每层的外呼上下按钮。控制层是每部电梯自己的状态机和顺序控制负责“让电梯稳稳地走、准确地停、安全地开门”。调度层则单独拎出来负责收集所有外呼决定派哪一部电梯去响应。这个拆分很重要。如果你把调度逻辑塞进每一部电梯的程序里一旦电梯数量增加或者运行中要修改派梯策略你会改到怀疑人生。我见过有选手把派梯判断写在六个地方结果改一个策略六个程序块要同步改漏一个就出诡异故障。正确做法是每部电梯用一个FB六部电梯就是同一FB的六个实例调度逻辑单独放一个FC或者全局DB统一处理请求分配。平台选型上当时我用的TIA Portal V16配合S7-1200系列。1200对初赛来说完全够用而且和比赛评分系统的变量对接更省事。编程语言我建议混合使用逻辑控制、状态机用SCL写读起来清晰涉及到安全互锁、硬接线信号处理这类逻辑用LAD更直观。群控调度里的算法部分SCL优势非常明显写循环、算距离、排序都比梯形图舒服得多。提示如果你用S7-1500大部分代码可以无缝迁移但要注意FB的优化访问属性和DB的存储设置这会影响数据读取方式。1200上默认设置一般够用。2. 信号规划与程序框架搭建2.1 I/O映射先把“手和脚”界定清楚博途里编程最忌讳的事情就是直接在梯形图里用硬地址I0.0、Q0.1这种。一旦评分系统或者现场模型的信号定义跟你最初假设不一致你会改到崩溃。我的习惯是先建一张完整的变量表把每个物理信号映射成带注释的符号名然后程序里只跟符号名打交道。六部十层电梯的I/O数量其实不多但命名必须有规律。我按“模块区域_对象_信号方向_编号”这套规则来编比如L1_POS_ENCODER表示1号电梯的位置编码L1_DOOR_OPEN表示1号电梯开门到位。这个工程量虽然不起眼但在调试查错的时候能节约大把时间。具体I/O分布我整理成了一张表信号类别变量名示例含义数量外呼上行CALL_UP[1] ~ CALL_UP[10]1~10层上行呼叫按钮10外呼下行CALL_DOWN[1] ~ CALL_DOWN[10]1~10层下行呼叫按钮10顶层没有按10算也行内呼登记Lx_CAR_CALL[1] ~ Lx_CAR_CALL[10]x号电梯各层内呼按钮606部×10层平层信号Lx_FLOOR_SENSOR[1] ~ Lx_FLOOR_SENSOR[10]x号电梯各层平层开关60上下行接触器Lx_UP_CONTACTOR, Lx_DOWN_CONTACTORx号电梯上行/下行输出12开关门输出Lx_DOOR_OPEN_OUT, Lx_DOOR_CLOSE_OUTx号电梯开门/关门输出12这个表看起来简单但有几个细节容易被疏忽。第一外呼信号只跟楼层绑定不跟电梯绑定它属于“公共请求”等待群控分配。第二内呼信号必须做到“按电梯隔离”否则会出现1号电梯的内呼在2号电梯里被响应的情况。第三位置反馈建议用编码器计数值或者用平层开关组合推算别只靠一个“当前楼层”的变量来判断因为真实运动会经过中间层信号会有短暂的多点触发。我当时在初版程序里犯过一个很典型的错误只用了平层信号来更新当前楼层结果电梯高速通过中间层时个别层站的感应信号被漏扫导致楼层位置错位。后面改成“编码器计数值连续计算平层信号校正”的双通道方案才真正稳定下来。2.2 功能块抽象单梯FB与群控逻辑如何拆分我建议把单梯逻辑做成一个FB名字叫FB_ElevatorSingle它的输入是内呼登记、外呼分配指令、位置信号输出是运行方向、目标楼层、开关门指令、运行状态。这样六部电梯各自生成一个背景DB互不干扰任何一个实例的状态变化都不会影响到别的电梯。这个FB内部我再拆成几个小模块状态机核心、方向决策、目标楼层计算、开关门时序、安全互锁。方向决策和开关门时序是重中之重后面第三节我会详细说。群控逻辑我单独写了一个FC或者放在全局OB1里循环调用名字就叫FC_GroupDispatch。它只负责回答一个问题某一层有外呼来了派哪部电梯去它的输入是六部电梯的当前状态、方向、位置、已分配任务输出是电梯编号和分配给它的目标楼层。这样拆完以后程序的调试思路就非常清晰群控出了问题查看分配给几号梯单梯出了问题查看对应背景DB里的状态位。你不需要在几千行程序里大海捞针。3. 核心调度算法与程序实现3.1 单梯状态机让电梯“知道自己在干什么”任何多步骤控制设备最好都用状态机来设计。电梯的状态我分成六种IDLE空闲停靠当前层门保持关闭RUNNING正在运行有明确方向DOOR_OPENING正在开门DOOR_OPEN门已开等待乘客进出DOOR_CLOSING正在关门FAULT故障或超时需要特殊处理在SCL里状态机我习惯用CASE语句实现。每一步做的事情非常明确根据当前状态判定允许的迁移条件然后执行该状态下的动作。举个例子当前状态是IDLE如果分配到了目标楼层且目标楼层不等于当前楼层就切换到RUNNING并置位对应方向接触器。状态迁移最需要小心的点是“关门过程中是否允许重新开门”。跑分系统会在关门过程中不断发送新的呼叫请求如果程序不能及时响应就会造成乘客已经到了电梯口但门关上了评分直接扣分。我的处理办法是在DOOR_CLOSING状态下持续检查同层外呼或内呼是否仍然有效如果有效并且门还没完全关到位就重置关门计时器重新回到DOOR_OPEN状态。3.2 请求登记与消除逻辑上不能出现“幽灵请求”请求登记的逻辑看起来简单但藏着一个隐藏雷区同一层的外呼请求究竟是持续保持到电梯到达该层为止还是在电梯启动响应后就自动消失如果处理不对要么电梯反复跑来跑去要么请求彻底消失导致没人响应。我的实现是外呼请求采用“置位保持、到达消除”的机制。某层上行外呼按钮按下对应位CALL_UP[3]被置1直到某部电梯到达3层且运行方向为上或者电梯空闲到达3层时才将其复位。这样既能防止请求丢失又不会出现同一请求被多部电梯重复响应的冲突。消除时机需要精确控制。如果电梯还在5层往3层赶3层的外呼就先消除了那乘客会发现电梯经过3层却不停直接投诉。正确做法是在电梯进入目标楼层的减速区段或收到该层平层信号且电梯处于开门状态时才执行消除动作。比赛跑分系统看的就是这些细节。内呼的登记就简单一些按钮按下登记到达对应楼层后清除。唯一要注意的是内呼和外呼在目标楼层计算时要合并去重。我通常用一个整型数组TargetFloorList[1..10]来存放某一部电梯当前需要停靠的所有楼层内呼、被分配的外呼、顺路捎带的请求都写进这个列表取值为0或1。3.3 顺向截梯与反向响应派梯算法的“灵魂”单梯的算法核心在于方向和目标楼层的选择。常见做法是“顺向优先无顺向则反向”。具体逻辑描述起来很简单电梯当前方向为上如果上行方向上有任何登记的目标楼层继续上行取其中最近的层停靠。如果上行方向没有任何目标楼层先看看下行方向有没有目标如果有切换为下行取下行方向最远的层停靠因为要一路往下把所有下行目标都经过。如果两个方向都没有目标电梯进入空闲停在当前层。这段逻辑如果用梯形图写会很绕但SCL里十几行就能解决。我贴一段核心方向判断的伪代码// 方向决策伪代码具体变量以你的DB为准 IF currentDirection UP THEN IF EXISTS targetFloor currentFloor THEN nextTarget : 上行方向最近目标; ELSIF EXISTS targetFloor currentFloor THEN currentDirection : DOWN; nextTarget : 下行方向最远目标; ELSE currentDirection : IDLE_DIR; END_IF; END_IF;这段代码背后藏着一个竞赛给分点“顺向截梯”能力。比如6号电梯正从1层往上赶往5层此时4层有人按了上行如果程序足够聪明应该把4层加入目标列表顺路带上而不是让另一部空闲电梯专门跑过来。想实现这个能力只需要在电梯经过某层时检查该层的外呼方向是否与当前运行方向一致如果一致就向目标楼层列表写入该层。反向响应的策略要保守一些。如果电梯正在上行途中3层来了个下行外呼这时候是否立即响应我的判断标准是如果电梯上行方向已经没有任何目标而且3层离当前位置不远就切换方向去响应如果上方还有任务就等任务清空后再转向。这个策略本质上是“先完成在手任务再响应反向邀请”能有效减少电梯来回空跑。3.4 群控派梯如何让六部电梯“有默契地干活”群控逻辑是我花时间最多的地方。六部电梯协同本质上是一个动态任务分配问题。比赛场景并不会复杂到需要上神经网络但需要一个足够稳定的启发式规则。我采用的策略是“最小预测到达时间”法。每当出现一个新的外呼请求程序遍历六部电梯估算每部电梯从当前状态赶到该楼层所需要的时间选择时间最短的那一部去响应。看起来很简单但估算时间不能只按“楼层差×单层耗时”来算必须考虑这部电梯当时的方向、顺路情况、当前任务量。我当时的估算函数大致是电梯IDLE且在同层到达时间0直接派梯电梯IDLE但不在同层时间距离×单层运行时间开关门预估电梯RUNNING方向和外呼方向一致且目标楼层处于前进方向时间经过楼层数×单层运行时间途中可能经过目标楼层可以顺路停靠电梯RUNNING方向相反时间先运行到最远端任务完成反向赶到目标的时间已经在目标楼层且停靠中时间当前开关门剩余时间这段估算逻辑里我加了一个“任务惩罚系数”。如果某部电梯已经被分配了3个以上目标楼层即使它理论到达时间很短我也会在评估分数上乘以1.2。这是为了防止任务扎堆——六部电梯里三辆闲死、三辆忙死是跑分的大忌。平均候梯时间会因此变得非常差。群控调度的另一个关键点是“外呼转移”。当2号电梯正在赶去响应3层外呼的路上结果5层出现了一个更紧急的请求而2号电梯还没到达3层这时候2号电梯能不能“甩掉”3层外呼转交给其他电梯我的做法是允许转移但加了一个条件只有当1号电梯尚未开始减速停车时才允许转移。如果电梯已经进入减速区段或者已经平层就强制完成当前任务不允许中途改派。这个条件是为了防止“反复易主”的情况——外呼请求在两个电梯之间来回甩结果一个都没停评分直接崩。3.5 开关门时序与安全互锁跑分好不好一半看这里开关门时序是最容易被忽视、但也是最影响跑分体验的模块。评分系统很看重开关门效率开门太慢乘客进出时间就长整体效率低关门太快可能夹人同样扣分。我设计的时序参数如下开门输出后开门到位信号反馈时间通常模拟为1秒开门到位后进入DOOR_OPEN状态DOOR_OPEN保持时间设为3秒模拟乘客进出。如果存在同层外呼请求再延长1秒防止乘客刚跑到门口门就关了关门输出后关门到位信号反馈模拟为1秒收到到位信号后电梯才算“完全准备好下一次运行”安全互锁的逻辑必须做到“宁可错杀不可放过”运行中严禁开门只要电梯不是停在平层区门输出必须被强制为0。开门中严禁启动运行门未完全关闭到位时上下行接触器不允许吸合。超载信号触发时禁止关门门保持全开状态。上下行接触器互锁上行输出和下行输出不能同时为1。这个我会在程序里加硬互锁判断即便程序逻辑已经写了方向切换也必须在输出端再判断一次。这里我遇到过一个真实问题在调试时因为方向切换瞬间上行接触器还没完全断开、下行接触器就被置位导致模型里的方向信号“砰”地跳变。后来我加了一个“方向切换等待”条件——如果上一次运行方向与本次不同必须先等待0.5秒等接触器彻底断开后再置位新的方向。别小看这0.5秒它彻底消除了方向切换的毛刺问题。4. 跑分调试与踩坑实录4.1 跑分系统里最常见的失分点我整理了当时调试中遇到的高频失分情况供各位直接对照排查症状可能原因解决方法电梯在某层一直反复开关门外呼请求没有正确消除门开后请求仍保持置位检查请求消除条件务必在轿厢开门后执行复位多部电梯同时去响应同一外呼群控派梯后没有锁定电梯编号其他电梯仍把该请求当作可响应增加“请求已分配”标志未被分配的外呼请求必须置位等待电梯赶到了目标层但不停目标楼层列表更新滞后或减速判断条件与位置信号不匹配确认目标楼层写入发生在位置信号触发前并留出减速距离电梯开门后人还没上下完就关门DOOR_OPEN保持时间太短适当拉长保持时间并在同层仍有请求时延长开门电梯方向切换时频繁急停方向决策抖动目标列表里有过期楼层清理目标列表中的已消除请求方向切换添加延时那个“多部电梯同时响应同一外呼”的问题比较隐蔽。我最初把外呼请求直接挂在全局DB里所有电梯都能看到然后每部电梯自己判断“我顺路我去响应”结果经常出现两三部电梯同时接近同一楼层。解决办法是引入“请求所有权”概念这个和前面群控派梯说的是一件事。一个外呼一旦被分配给某个电梯其他电梯即使经过该层也只能停靠却不能消除这个请求直到分配电梯真正到达。4.2 信号抖动与边界处理真实模型和仿真不一样用TIA Portal的仿真器调试时信号是理想的按钮按下就置位松手就复位。但到了比赛模型或者真实物理平台上信号会有抖动尤其是按钮信号、平层光电信号。我在程序里统一做了“去抖处理”连续扫描到该信号有效20ms以上才认为信号有效。位置判断也要增加迟滞。不要用单一楼层值做“等于”判断因为电梯在平层定位时可能会有几个mm级别的来回晃动导致当前楼层变量在两层之间反复跳。我采用的方法是以“平层传感器信号触发”为准当电梯到达某层平层传感器输出有效时才更新当前楼层传感器无效时保持上一楼层值不变。有一次模型跑分时电梯明明在3层稳稳停着但程序认为它还在2层就是因为我的楼层更新依赖的是编码器数值等于设定值这个条件而编码器数值因为机械回差差了几十个脉冲。后来改成“编码器数值进入目标楼层范围 平层信号有效”双重确认问题立刻消失。4.3 调试技巧如何用数据监控定位调度问题群控程序最难的其实是“看不见摸不着”。电梯动起来以后你根本不知道它脑子里在想什么。我的做法是建一个专门的调试DB把关键状态全部开放出来监控当前方向、当前楼层、目标楼层数组、分配状态、各电梯的响应评分估算值。然后用博途的监控表和跟踪功能把跑分过程全程录制下来。当跑分结果不理想时不要去猜哪里有问题直接把时间轴拉出来看某层外呼什么时候产生的群控什么时候响应的电梯什么时候到达的门开了多久请求什么时候消除的这一套下来问题通常一眼就能看出来。比如有一次我发现某部电梯总是被派到很远的楼层响应新请求而另一部空闲电梯就在旁边后来排查发现是估算函数里“空闲电梯但不在同层”的到达时间计算错了缺少一行“如果电梯空闲但不在同层需要先计算启动延迟”。一行代码的差异导致整个群控策略非常笨拙。另外强烈建议在程序里加上“运行计数器”和“响应时间统计”这类变量。跑分结束后直接读取这些统计值可以量化你的程序表现而不是只靠一个总分猜来猜去。我当时就给自己加了一个“平均响应时间”统计每次跑分后对比优化一小步看得见一小步的提升。最后分享一个小技巧调群控算法时不要一次性把六部电梯全挂上先让2~3部电梯跑通稳定策略验证没有策略性错误后再全量投入。否则六部电梯同时乱跑你根本分不清问题是算法设计问题还是程序实现问题。这个习惯帮我省下的时间比任何写代码技巧都值钱。