干工控这些年,电梯一直是PLC圈子里公认的经典练手项目,也是教学和招聘面试里出现频率最高的应用场景之一。它麻雀虽小五脏俱全:呼梯登记、方向调度、平层停车、开关门控制、安全联锁一整套逻辑全都覆盖到,难度既不会劝退新手,又能让有经验的人玩出细节。这篇文章就围绕西门子博图V16(TIA Portal V16),完整拆解一个单部八层电梯的PLC程序开发与PLCSIM仿真过程,从需求分析到I/O规划、从SCL代码到调试排错,尽量把每一步的“为什么这么做”也讲清楚。
不管你是刚开始接触PLC的学生,还是在做课程设计、毕业设计,又或者是想转行做电梯维保与自动化改造的工程师,这篇内容都能给你一个可以直接参考复现的完整方案。我会按实际做项目的顺序来讲,先把需求吃透,再谈选型,然后写代码、跑仿真,最后把仿真和真实工程之间的差距也点一下。
1. 八层电梯的需求拆解与总体方案设计
1.1 电梯控制系统的核心功能模块
一台单部八层电梯,从乘客和运维的角度看,功能无非就是:乘客在侯梯厅按“上/下”呼梯按钮,进入轿厢后按要去楼层的内呼按钮,电梯判断方向运行,到达目标楼层平层停梯,开门上下客,再关门继续运行。但把这些动作抽象成PLC程序逻辑,至少要拆成下面几个模块:
- 呼梯登记模块:内呼和外呼信号的采集、记忆保持、去抖处理,以及到达楼层后的自动清除。
- 方向决策模块:根据当前楼层、当前运行方向、各楼层呼梯情况,决定继续上行、继续下行、换向还是停止。
- 停层判断模块:电梯运行到某楼层时,判断是否需要减速、平层、停车。
- 开关门控制模块:包括自动开门、延时关门、光幕/安全触板重开、超载不关门等。
- 安全保护模块:急停、门锁断开、检修模式、上下终端限位等紧急处理逻辑。
- 信号显示模块:当前楼层显示、上行/下行方向指示、内外呼按钮指示灯复位等。
八层电梯一共有8个内呼按钮、14个外呼按钮(1楼只有上行、8楼只有下行,2到7楼各有上下两个),加上门锁、平层开关、超载、急停、检修等信号,整个系统的输入输出点数量并不少。如果不用合理的规划,I/O分配和程序结构很快就会乱。这也是我强调整体方案设计的原因。
1.2 硬件平台与PLC选型方案
博图V16支持的PLC有好几个系列,做八层电梯的话,S7-1200系列足够用,性价比也高。我这次用的是CPU 1214C DC/DC/DC,理由是:它自带14个数字量输入和10个数字量输出,本体点数相对充裕;再扩展一个16DI的SM 1221和一个16DQ的SM 1222,I/O总数可以覆盖电梯控制需求。
具体点位数可以简单算一下:
| 信号类型 | 数量 | 说明 |
|---|---|---|
| 内呼按钮 | 8 | 轿厢内1~8层按钮 |
| 外呼上行按钮 | 7 | 1层、2~7层上行 |
| 外呼下行按钮 | 7 | 2~8层下行 |
| 门锁反馈 | 1 | 轿门锁+厅门锁串联信号 |
| 平层/门区开关 | 2 | 上平层、下平层开关 |
| 超载信号 | 1 | 称重装置常开触点 |
| 急停信号 | 1 | 轿厢急停按钮 |
| 检修开关 | 1 | 检修/自动切换 |
| 光幕/安全触板 | 1 | 门区防夹信号 |
这样算下来,数字输入量大约需要30个左右。数字输出方面,上行接触器、下行接触器、开门继电器、关门继电器、楼层显示(BCD码或二进制)、方向指示灯、各呼梯按钮灯,加起来也有20多个。因此用一个CPU本体再加一个DI模块和一个DQ模块,是最省事、也最符合教学演示的做法。
1.3 为什么用PLC而不是单片机或继电器
这个问题我经常被学生问。做电梯控制,用单片机似乎成本更低,用继电器系统好像更直观,为什么主流还是PLC?核心原因有三个。第一是稳定性,PLC的硬件和系统软件是为工业环境设计的,电磁干扰、电源波动、高温高湿环境下的可靠性远高于单片机裸板。电梯一旦出问题就是安全事故,稳定压倒一切。第二是编程和维护的便利性,PLC的梯形图和SCL语言更接近电气工程师的思维方式,现场排查问题时可以一边在线监控变量一边对照电路图找原因,单片机代码在这个场景下相对难维护。第三是安全冗余,成熟PLC平台的看门狗、急停回路、安全继电器等机制都有成熟的工程范式,二次开发成本低。
当然,现在的智能电梯也在引入各种嵌入式方案,但传统电梯控制核心还是PLC为主,至少在教学和中小型改造项目里,PLC依然是最合理的选择。
2. 博图V16项目搭建与基础组态
2.1 新建项目、添加设备与组态参数
打开博图V16之后,第一步是新建项目。这个过程不复杂,但有几个细节我觉得值得说一下。
项目名称和路径建议用英文或拼音,不要用中文和特殊符号,否则某些版本的软件偶尔会出现组态文件路径异常的问题。这个虽然不是硬性报错,但调试阶段一旦出问题会很浪费时间。
新建项目后,在“设备与网络”视图中添加一个S7-1200 CPU。具体型号选择CPU 1214C DC/DC/DC后,软件会自动弹出组态界面。这里要确认几项参数:
- IP地址:S7-1200默认IP是192.168.0.1,建议规划和电脑在同一网段,比如电脑设192.168.0.100,PLC保持默认,方便后面下载和仿真。
- 时钟存储器:在CPU属性里启用“时钟存储器”,我习惯用字节地址100(即M100.0~M100.7),它会输出0.5Hz、1Hz、2Hz等常用脉冲信号,程序里做闪烁指示或者定时辅助非常方便。
- 上电启动模式:建议选“暖启动-运行”,让CPU上电后直接进入运行状态,这个在仿真和现场调试时都省事。
组态完成后,不要急着写程序,先把“设备组态”保存编译一次,确认硬件配置无误,再进入下一步。如果你要挂SM 1221和SM 1222扩展模块,需要在组态界面右侧硬件目录里把它们拖到CPU左侧或右侧的插槽上,然后编译分配I/O地址。扩展模块的地址取决于插槽位置,一般会自动分配,你在后面的变量表里直接用符号名就行。
2.2 程序块的规划思路
博图V16里的程序结构比老式Step 7要灵活很多,我通常按功能把程序拆成几个块,而不是把所有逻辑全塞进OB1。项目一开始就在“程序块”下规划好:
- OB100:启动组织块,做变量初始化。比如把运行方向、当前楼层、呼梯记忆变量全部置为初始值。
- OB1:主程序扫描循环,按顺序调用下面几个功能块。
- FC_Elevator_Main:主控制逻辑,把呼梯登记、方向决策、停层判断、开关门控制集成在一起。
- FC_Display:楼层显示和方向指示灯的编码转换。
- FB_DoorControl(可选):如果想把开关门逻辑封装成带背景数据的块,也可以用FB来做,方便复用。
这种做法的好处是程序结构和电梯的物理功能一一对应,在线监控时能快速定位“现在是哪一块在控制这个输出”,排查问题的时候思路非常清晰。不要图省事把所有逻辑写在一个FC里,越是功能多的项目,拆块越重要。
2.3 变量表与数据结构设计
变量表是我每次做项目都很重视的一环,它决定了整个程序的清晰度。八层电梯的变量很多,如果全部用“I0.0”“M3.5”这种绝对地址去写,代码会完全没法看。在博图里可以建立全局变量表(PLC变量),给每个I/O点定义符号名,例如:
| 符号名 | 数据类型 | 地址 | 注释 |
|---|---|---|---|
| DI_InnerCall_1F | Bool | %I0.0 | 内呼按钮1层 |
| DI_InnerCall_2F | Bool | %I0.1 | 内呼按钮2层 |
| DI_UpCall_1F | Bool | %I1.0 | 外呼上行1层 |
| DI_UpCall_2F | Bool | %I1.1 | 外呼上行2层 |
| DI_DownCall_8F | Bool | %I2.6 | 外呼下行8层 |
| DI_DoorLock | Bool | %I3.0 | 门锁反馈信号 |
| DI_UpLevelSwitch | Bool | %I3.1 | 上平层开关 |
| DI_DownLevelSwitch | Bool | %I3.2 | 下平层开关 |
| DI_Overload | Bool | %I3.3 | 超载信号 |
| DI_EmergencyStop | Bool | %I3.4 | 急停信号 |
| DI_Maintenance | Bool | %I3.5 | 检修模式切换 |
| DI_SafetyEdge | Bool | %I3.6 | 光幕/安全触板 |
| DO_UpContactor | Bool | %Q0.0 | 上行接触器 |
| DO_DownContactor | Bool | %Q0.1 | 下行接触器 |
| DO_OpenDoor | Bool | %Q0.2 | 开门继电器 |
| DO_CloseDoor | Bool | %Q0.3 | 关门继电器 |
除了I/O变量,运行数据建议放在DB数据块里。我建一个DB_Elevator,里面定义数组型变量:
TYPE "Struct_Elevator" VERSION : 0.1 STRUCT CurrentFloor : INT; // 当前楼层 Direction : INT; // 运行方向:0停止,1上行,-1下行 InnerCall : Array[1..8] OF Bool; // 内呼记忆 UpCall : Array[1..8] Of Bool; // 上外呼记忆 DownCall : Array[1..8] Of Bool; // 下外呼记忆 TargetFloor : INT; // 当前目标楼层 DoorStatus : Int; // 门状态:0关闭,1打开,2正在开,3正在关 END_STRUCT END_TYPE这样设计的好处是:数组下标正好对应楼层号,代码里用循环遍历呼梯信号会非常顺手,不需要写8个重复的IF语句。
3. 电梯核心调度逻辑的设计原理
3.1 呼梯登记与消除逻辑
呼梯登记是电梯控制最基础的部分。核心思路是:按钮按下时,把对应楼层的呼梯记忆位置为TRUE;电梯到达该楼层提供服务后,再把它复位为FALSE。这里要特别注意的是信号边沿问题。
机械按钮在按下瞬间会产生多次抖动,如果只写“IF 按钮 THEN 记忆 := TRUE”,理论上没问题,但实际调试时会发现偶发误登记。我的处理方式是用一个临时变量存储上一扫描周期的按钮状态,用“上升沿”来触发置位:
// 内呼登记 FOR #i := 1 TO 8 DO #btnEdge[#i] := #innerBtn[#i] AND NOT #lastInnerBtn[#i]; IF #btnEdge[#i] THEN #innerCall[#i] := TRUE; END_IF; #lastInnerBtn[#i] := #innerBtn[#i]; END_FOR; // 外呼登记 FOR #i := 1 TO 8 DO #upEdge[#i] := #upBtn[#i] AND NOT #lastUpBtn[#i]; IF #upEdge[#i] THEN #upCall[#i] := TRUE; END_IF; #lastUpBtn[#i] := #upBtn[#i]; #downEdge[#i] := #downBtn[#i] AND NOT #lastDownBtn[#i]; IF #downEdge[#i] THEN #downCall[#i] := TRUE; END_IF; #lastDownBtn[#i] := #downBtn[#i]; END_FOR;在博图SCL里,标准的上升沿指令是R_TRIG,但使用R_TRIG需要在循环外实例化很多个触发器对象,写起来比较啰嗦。用“当前值 AND NOT 上一周期值”这种方式做等价边沿检测,在循环里非常方便,实测效果完全一样。
呼梯的消除逻辑:当电梯到达某楼层并完成平层停车后,无论电梯是上行还是下行,只要停下来了,就说明这层的乘客已经得到服务,那么该层的内呼、上呼、下呼记忆全部清零。不能用“只清除内呼”或“只清除同方向呼叫”这种不完整的做法,否则会出现电梯明明已经停过某层,但外呼灯一直还亮着的尴尬情况。
3.2 方向决策:顺向优先和换向逻辑
方向决策是电梯控制程序里最有含金量的部分,也是很多初学者抄代码也没抄明白的地方。我把方向决策拆成两个阶段来理解。
第一阶段是“判断当前方向是否能继续维持”。假设电梯当前正在上行,方向决策要扫描当前楼层以上的所有楼层是否存在任何呼梯记忆——包括内呼、上外呼、下外呼。只要上方有一层存在呼梯,就说明还有上行需求,电梯继续保持上行。如果上方没有呼梯了,但下方有呼梯,则方向改为下行。如果上下方都没有呼梯,方向变为0,电梯停止。
第二阶段发生在电梯静止状态。静止时,系统不存在“保持方向”的问题,只需要决定下一步往哪走。我的策略是优先响应上方的呼梯,即如果上方有呼梯就上行,否则再判断下方有没有呼梯,有就下行。这个策略能有效减少电梯频繁换向,乘客等待体验也会好一些。
这段SCL代码是方向决策的核心,我把它完整写出来:
// 扫描上方是否有呼梯 #upExist := FALSE; FOR #i := #currentFloor + 1 TO 8 DO IF #innerCall[#i] OR #upCall[#i] OR #downCall[#i] THEN #upExist := TRUE; END_IF; END_FOR; // 扫描下方是否有呼梯 #downExist := FALSE; FOR #i := 1 TO #currentFloor - 1 DO IF #innerCall[#i] OR #upCall[#i] OR #downCall[#i] THEN #downExist := TRUE; END_IF; END_FOR; // 方向决策主体 IF #direction = 1 THEN // 当前上行:上方有呼梯继续上行,否则看下方 IF #upExist THEN // 保持上行 ELSIF #downExist THEN #direction := -1; ELSE #direction := 0; END_IF; ELSIF #direction = -1 THEN // 当前下行:对称处理 IF #downExist THEN // 保持下行 ELSIF #upExist THEN #direction := 1; ELSE #direction := 0; END_IF; ELSE // 静止状态:优先响应上方 IF #upExist THEN #direction := 1; ELSIF #downExist THEN #direction := -1; ELSE #direction := 0; END_IF; END_IF;把方向决策比作顺风车逻辑就好理解了:司机在路上只接方向顺路的乘客,如果当前方向再也没有顺路的乘客,才会考虑掉头去接反方向的乘客。
3.3 停层判断与平层停车条件
方向决策解决的是“电梯往哪走”,停层判断解决的是“电梯到哪停”。这两件事必须分开,否则程序会很乱。
当电梯正在上行时,每到达一个楼层,程序都要检查这一层是否满足停车条件。停车条件有三个:该层有内呼、该层有上外呼、该层有下外呼。只要满足任意一个,且电梯还没有进入该楼层平层区,就触发减速停车流程。
这里有个工程细节:电梯不能到了楼层才突然急停,真实系统是提前在目标楼层前约30厘米处检测到“换速感应开关”,PLC给出爬行速度,电梯以低速继续运行;再检测到“平层开关”时,PLC下达停车指令并输出抱闸,才能准确停靠。所以在I/O分配里,我特别预留了上平层、下平层两组开关信号。
对于八层电梯,更完整的做法是每层都设置独立的换速开关和平层开关,但输入点数会翻倍。在教学和仿真项目里,我做了适当简化:用“当前楼层”和“平层信号”模拟完整的换速平层过程。当电梯进入某层并触发平层信号时,先把方向置为0,清除该层所有呼梯记忆,再置位开门请求。这样逻辑上等价,但点数需求大幅下降。
停层判断的核心代码可以写成:
// 上行时判断当前层是否要停 IF #direction = 1 AND #currentFloor >= 2 THEN IF #innerCall[#currentFloor] OR #upCall[#currentFloor] OR #downCall[#currentFloor] THEN #needStop := TRUE; END_IF; END_IF; // 下行时判断当前层是否要停 IF #direction = -1 AND #currentFloor <= 7 THEN IF #innerCall[#currentFloor] OR #upCall[#currentFloor] OR #downCall[#currentFloor] THEN #needStop := TRUE; END_IF; END_IF;这里不用把上行和下行写得一模一样,但要特别注意边界:停层判断要限定在当前层不是顶层或底层的范围内,否则电梯在8楼还检测到“继续上行”的方向,逻辑就会变得很奇怪。
3.4 开关门控制与安全联锁
开关门逻辑看起来简单,实际上坑最多。我见过不少新手程序,开门信号和关门信号是两个独立输出,结果两个输出同时为TRUE,继电器打架。我的做法是用一个门状态变量(DoorStatus)来管理:0表示关闭,1表示打开,2表示正在开,3表示正在关。所有输出信号都由这个状态变量推导出来,不存在两个输出同时有效的问题。
开门流程:电梯停稳后置位开门请求,门限位反馈“开门到位”后,开门继电器断开,同时启动一个关门延时定时器,延时时间一般设3到5秒。关门流程:关门延时时间到达后,置位关门继电器,门限位反馈“关门到位”后,关门继电器断开,电梯才能重新启动运行。
这个流程里的安全联锁是重头戏,尤其是门锁回路。真实电梯的门锁信号是串联在安全回路里的:任何一层厅门没关好,或者轿门没关好,门锁信号就会断开,PLC必须立即禁止电梯运行。就算程序逻辑错误导致上下行接触器吸合,门锁断开也会在硬件层面切断主回路电源,这才是真正的安全兜底。我在程序里把门锁信号作为运行允许的先决条件:
// 只有门锁闭合且无急停时,才允许输出运行信号 #runPermit := #doorLocked AND NOT #emergencyStop AND NOT #maintenance;另外两个常见的门控细节是超载和光幕。超载信号有效时,关门继电器强制断开,开门继电器保持吸合,直到超载信号消失。光幕或安全触板触发时,无论关门到什么程度,必须立即停止关门并重新开门,防止夹人。要注意这些东西在仿真里可能没有真实信号源,在线调试时需要通过强制变量来模拟。
4. 程序编写实操:SCL代码详解
4.1 主程序框架与组织块调用
OB1是我整个电梯程序的总入口。它的内容很简短,就是按顺序调用各功能块,真正的逻辑都在FC和FB内部。这个习惯来自于我踩过的一次坑:早期把所有逻辑堆在OB1里,程序上千行,在线监控时变量列表长到翻好几页,排查一个门锁问题花了整整半天。后来拆块之后,任何功能异常都能在5分钟内定位到具体程序块。
OB1的调用代码差不多是这样:
// 主扫描周期 "FC_Elevator_Core"( innerCall => #innerCall, upCall => #upCall, downCall => #downCall, currentFloor => #currentFloor, direction => #direction, ... ); "FC_Display"( currentFloor := #currentFloor, direction := #direction, floorCode => #floorCode, upLamp => #upLamp, downLamp => #downLamp );用FC的好处是每次调用不需要背景数据块,变量全部通过接口参数传递,逻辑独立。但我前面也提过,如果程序功能复杂、需要做多次实例化,用FB会更合适。对于单部八层电梯来说,FC足够用。
4.2 呼梯登记与方向决策的完整代码
下面我给出一个相对完整的核心程序代码。这段代码可以在博图V16的SCL编辑器中直接编译,结构上把前面讲到的逻辑都串了起来。
// ---------- 呼梯登记 ---------- // 内呼登记(边沿检测) FOR #i := 1 TO 8 DO #edgeInner[#i] := #btnInner[#i] AND NOT #lastInner[#i]; IF #edgeInner[#i] THEN #innerCall[#i] := TRUE; END_IF; #lastInner[#i] := #btnInner[#i]; END_FOR; // 外呼上行登记 FOR #i := 1 TO 8 DO #edgeUp[#i] := #btnUp[#i] AND NOT #lastUp[#i]; IF #edgeUp[#i] THEN #upCall[#i] := TRUE; END_IF; #lastUp[#i] := #btnUp[#i]; END_FOR; // 外呼下行登记 FOR #i := 1 TO 8 DO #edgeDown[#i] := #btnDown[#i] AND NOT #lastDown[#i]; IF #edgeDown[#i] THEN #downCall[#i] := TRUE; END_IF; #lastDown[#i] := #btnDown[#i]; END_FOR; // ---------- 方向决策 ---------- #upExist := FALSE; #downExist := FALSE; FOR #i := #currentFloor + 1 TO 8 DO IF #innerCall[#i] OR #upCall[#i] OR #downCall[#i] THEN #upExist := TRUE; END_IF; END_FOR; FOR #i := 1 TO #currentFloor - 1 DO IF #innerCall[#i] OR #upCall[#i] OR #downCall[#i] THEN #downExist := TRUE; END_IF; END_FOR; IF #direction = 1 THEN // 上行中:前方还有呼叫就继续,否则换向 IF #upExist THEN // 保持上行 ELSIF #downExist THEN #direction := -1; ELSE #direction := 0; END_IF; ELSIF #direction = -1 THEN IF #downExist THEN // 保持下行 ELSIF #upExist THEN #direction := 1; ELSE #direction := 0; END_IF; ELSE // 静止:优先上行 IF #upExist THEN #direction := 1; ELSIF #downExist THEN #direction := -1; END_IF; END_IF;这段代码里的#upExist和#downExist是临时变量,每次扫描周期根据呼梯情况重新计算。这里有一个新手容易犯的错:把“是否继续上行”的结果存在保持型变量里,导致电梯到了8楼上方的呼梯已经清除,但方向还保持上行,程序怎么跑都不对。方向相关的中间变量一定用临时变量或者每周期重新计算的变量。
4.3 楼层显示编码与指示灯处理
八层电梯需要把当前楼层显示在轿厢和厅外的数码管上。S7-1200输出可以直接驱动带锁存的BCD数码管显示模块,也可以用七段数码管驱动芯片。程序里我做一个简单的编码转换函数,把当前楼层值转成BCD码输出:
// 楼层显示编码:把整数楼层转换为BCD码输出格式 #floorCode := 0; CASE #currentFloor OF 1: #floorCode := 16#01; 2: #floorCode := 16#02; 3: #floorCode := 16#03; 4: #floorCode := 16#04; 5: #floorCode := 16#05; 6: #floorCode := 16#06; 7: #floorCode := 16#07; 8: #floorCode := 16#08; END_CASE;然后把这个字节的低4位和高4位分别输出到Q0.4~Q0.7和Q1.0~Q1.1即可。方向指示灯的逻辑更简单:上行时输出上行灯,下行时输出下行灯,停止时两个灯都灭。实际电梯方向灯在轿厢内和每个厅外都有,但PLC只需要输出一个总的方向信号,由外部驱动电路分流到各个指示灯。
4.4 初始化与检修模式
上电初始化放在OB100里,这个块只在PLC上电或从STOP切到RUN时执行一次。在电梯断电再上电后,之前的呼梯记忆需要保留吗?我的处理是:内呼信号不需要断电保持,因为乘客早就离开了;但外呼信号应该保持,因为厅外乘客还在等电梯。如果你用的是非保持型M变量或DB里的非保持型Bool,断电后会丢失。解决方法是把记忆变量定义成保持型,或者在OB100里从上一次保存的数据区域恢复。不过仿真环境比较简单,DB变量默认在CPU断电时保持,这个点提一下,现场项目要单独处理。
检修模式是电梯现场维护必备的功能,在程序里要单独考虑。当检修开关切到有效状态时,自动运行逻辑全部失效,电梯只能通过检修盒上的按钮点动上下行,门也只能手动开关。实现方式很简单:
// 检修模式下强制方向为零,禁止自动运行 IF #maintenance THEN #direction := 0; #runPermit := FALSE; // 点动控制:按下点动上行按钮,直接驱动上行接触器 IF #manUp AND NOT #manDown THEN #upContactor := TRUE; ELSE #upContactor := FALSE; END_IF; // 点动下行类似 ELSE // 正常自动运行逻辑 END_IF;检修模式虽然代码短,但在真实项目里是安全功能的一部分,绝对不能省。
5. PLCSIM仿真验证与调试实录
5.1 把程序下载到PLCSIM并开始仿真
程序写完并不代表完事,必须仿真验证。博图V16自带S7-PLCSIM,操作流程很固定:在项目树里选中PLC站点,点击“仿真”图标,软件会提示启动仿真器。启动后,PLCSIM窗口会模拟一个S7-1200 CPU,显示RUN/STOP状态和LED指示灯。这时把硬件组态和程序下载到仿真PLC里,点击运行,然后用“监控表”或者“变量表”在线监视和修改变量。
有一点要注意:PLCSIM不能仿真真实的扩展硬件模块,如果你的组态里有SM 1221或SM 1222而仿真器不支持,往往会提示下载错误。常规做法是在仿真前临时把扩展模块删掉或禁用,程序里用到的I/O地址改成内部变量来模拟。我在做这个项目时,程序里的DI和DO变量全部用符号名访问,仿真时就手动给这些符号名赋值,不影响逻辑验证。
5.2 用辅助定时器模拟轿厢位置移动
在PLCSIM里,没有真实的编码器和平层开关,怎么模拟电梯从一个楼层运行到另一个楼层?我这里分享一个实测很好用的技巧:在程序中加一个仿真辅助块,用定时器模拟电梯经过每层的时间。
// 仿真辅助:楼层切换模拟(仅用于PLCSIM调试) IF #direction <> 0 AND #runPermit AND #simEnable THEN #simTimer(IN := TRUE, PT := T#2S); IF #simTimer.Q THEN #simTimer(IN := FALSE); #currentFloor := #currentFloor + #direction; // 到达新楼层后短暂触发平层信号 #levelSignal := TRUE; END_IF; ELSE #simTimer(IN := FALSE); END_IF;这样每过2秒,当前楼层变量自动加1或减1,电梯就像真的在每一层之间移动一样。如果你想模拟得更精细,还可以在楼层变化前先触发换速信号,观察减速逻辑是否生效。这个技巧在课堂演示和课设答辩时非常加分,因为评审老师能看到电梯的动态运行过程,而不是只能对着变量表干瞪眼。
5.3 典型测试用例与验证结果
我列一下自己调试时用过的典型测试用例,这些场景基本覆盖了八层电梯的主要逻辑分支:
| 用例编号 | 测试场景 | 期望结果 | 实测结果 |
|---|---|---|---|
| T01 | 1楼静止,按8楼内呼 | 电梯上行,2秒后停靠2楼?——不,直到8楼才停 | 方向=上行,当前楼层逐步增加到8,到达8后停层开门,内呼清除 |
| T02 | 1楼静止,5楼上外呼、3楼下外呼同时登记 | 电梯优先上行到5楼,服务完后换向下行到3楼 | 实测正确,方向1→0→-1切换正常 |
| T03 | 电梯在5楼静止,7楼上外呼、6楼下外呼 | 优先上行到7楼,再下行到6楼 | 实测正确 |
| T04 | 电梯在6楼正在上行,7楼上外呼、4楼内呼 | 电梯先到7楼,再换向下行到4楼 | 实测正确 |
| T05 | 电梯停靠1楼,1楼内呼 | 电梯不开门?——不,直接开门 | 实测需要额外逻辑:当前层呼叫,开门请求立即置位 |
| T06 | 电梯运行中急停 | 上下行接触器立刻断开,方向置0 | 实测正确 |
| T07 | 关门过程中光幕触发 | 停止关门,重新开门 | 实测正确 |
T05这个场景是很容易漏掉的:乘客已经站在轿厢里,按了当前楼层的按钮,理论上电梯不需要运行,直接开门就行。所以停层判断里还要加一个条件:当电梯静止且当前楼层有内呼或外呼时,直接触发开门。
5.4 仿真调试中的常见问题与排查方法
调试过程中我遇到最多的问题集中在几个点,这里给出一份速查表:
问题1:电梯总是“冲过站”不停车。
排查思路:先看方向决策的#upExist和#downExist是否在到达目标楼层后正确复位。如果目标楼层的呼梯记忆没有被清除,电梯当然不会停。再看停层判断是否放在了方向决策之前,因为方向决策会清除或修改停层信号。正确的顺序是先做呼梯登记,再做停层判断,最后做方向决策。
问题2:门开了就再也关不上。
排查思路:检查关门延时的定时器是否被反复触发。如果开门到位信号在关门过程中一直有效,而程序里“开门到位”又被同时用作关门定时器启动条件,就会导致关门信号和开门信号互相竞争。解决办法是给关门定时器加一个边沿启动条件,不要用量态触发。
问题3:外呼灯亮了很久不灭。
排查思路:大概率是呼梯消除逻辑没写全。电梯到达该层后,只清了内呼而没清外呼,或者只清了上行呼叫而没清下行呼叫。电梯停层后,该层的所有呼梯信号都应该清除。
问题4:PLCSIM下载程序时报错。
排查思路:检查PLCSIM版本是否与博图V16匹配,检查硬件组态中是否有PLCSIM不支持的模块。把扩展模块临时禁用,或将程序中的I/O地址改为M变量模拟,就能绕过去。
问题5:检修模式下电梯还在自动响应呼梯。
排查思路:检修模式的判断应该在最顶层,一旦检修开关有效,直接切断所有运行输出,并且让方向决策的输出不生效。顺序问题导致检修开关被上下文逻辑覆盖了,把检修判断提到最前面即可。
6. 从仿真到真实工程的差距与扩展方向
6.1 仿真和真实电梯之间的关键差异
PLCSIM能帮我们验证控制逻辑的正确性,但它和真实电梯之间还有几条明显的“鸿沟”。
最大的差异是信号的真实性。仿真里,我给按钮变量赋一个TRUE,它就一直保持为TRUE;但在真实系统里,按钮触点会抖动,信号会长短不一,传感器可能时好时坏,信号线可能受到干扰。所以真实程序里,输入信号处理必不可少:按钮要加去抖,传感器要加滤波,安全信号要做断线检测。这部分工作往往比逻辑本身更花时间。
第二个差异是速度控制。真实电梯不是简单的“启动-匀速-停止”,而是有变频器、编码器、抱闸参与的复杂运动控制系统。PLC给出的是方向、多段速或模拟量速度给定,真正的加减速曲线由变频器完成。高精度平层还需要编码器计算距离,单靠平层开关停车只适用于速度很低的爬行阶段。这些内容在仿真里完全体现不出来,需要结合变频器调试和井道定位知识。
第三个差异是安全冗余。真实电梯的门锁回路、急停回路、上下极限开关是按照安全规范硬接线的,PLC就算程序跑飞也碰不到安全链。仿真时PLC是唯一的安全层,这并不真实。
6.2 进一步扩展与优化方向
如果做完八层电梯想再进一步,可以考虑下面几个方向。
把外呼逻辑升级成真正的“顺向截梯”调度。我前面的实现只做了“方向决策+同向停靠”,已经能处理最常见场景。但实际电梯还会有“电梯上行经过5楼时,5楼下外呼刚按下,要不要停”这种即时截梯问题,需要更精细的时间窗和方向判断。可以把每层呼梯时间戳记录下来,实现更智能的调度。
加入群控逻辑。有两部以上电梯时,要判断哪部电梯响应哪个呼梯最合理,这是电梯行业里一个很有趣的算法问题。博图用S7-1200做两到三部电梯的简单群控是完全可行的。
接入HMI触摸屏或上位机监控。博图可以组态一个HMI画面,把电梯运行状态、呼梯按钮、故障记录都做出来。PLCSIM也能和HMI仿真联动,这样整个项目看起来就是一个完整的电梯模型,特别适合课程设计或成品展示。
这里我多说一句关于HMI联动仿真的技巧:HMI按钮的变量要和PLC的输入变量关联,但PLCSIM不能直接仿真物理输入点,所以在HMI上设计的“呼梯按钮”关联的应该是PLC内部变量。也就是把程序中的呼梯信号源从I点改成内部M或DB变量,然后HMI按钮操作这些变量。逻辑一致,联动效果也非常逼真。
6.3 个人实操心得与经验补充
我做了几个电梯类项目后,最大的感触是:这种项目考的不是代码写得多花哨,而是逻辑的严谨性和完整性。一个看似简单的“电梯不动了”,可能要检查方向决策、门锁回路、平层信号、急停状态四个地方。调试不是靠猜,而是靠在线监控把关键变量的变化过程看透。
所以如果你今天准备动手做这个项目,我建议按这个顺序推进:先花一个小时把需求模块拆清楚,再花时间把变量表和DB结构设计好,然后才开始写代码。代码写完后,先在仿真里把T01到T07这些测试用例跑通,再考虑增加其他特殊功能。过程中一定要养成用符号名而不是绝对地址编程的习惯,不然程序还没写完,自己先乱了。
另外,仿真调试时多利用“强制变量”和“监控表”这两个工具。比如想模拟电梯在6楼往上运行、7楼有人呼梯,直接在当前楼层变量赋6、方向变量赋1,然后在7楼外呼变量上强制一个TRUE,观察程序中的停层和换向输出是否正确。整个过程不需要任何硬件,几分钟就能验证一个逻辑分支,效率非常高。