1. 项目背景与核心挑战
凌晨三点的实验室里,咖啡杯沿已经结了一圈褐色的渍痕。我们三个机械专业出身的学生,正死死盯着屏幕上六部电梯的仿真界面——这是2021年西门子杯工业自动化挑战赛的初赛题目,要求用PLC实现六部十层电梯的智能调度系统。原本熟悉的齿轮和轴承突然变成了ST语言里的结构体和函数,这种跨界带来的刺激感让所有人异常亢奋。
这个被我们戏称为"电梯侠"的项目,核心难点在于多电梯的协同调度。想象一下早高峰的写字楼,20多个楼层呼叫信号同时涌入时,系统需要像经验丰富的交通警察一样,实时决定哪部电梯响应哪个请求。我们最初尝试的直接分配策略很快暴露出问题:当两部电梯同时奔向同一个楼层时,系统会出现"电梯竞速"现象,导致响应时间急剧增加。
2. 系统架构设计
2.1 状态机模型
每部电梯被抽象为一个状态机,包含五个核心状态:
- 空闲态(IDLE):电梯静止且无任务
- 上行态(ASCENDING):电梯正在上升
- 下行态(DESCENDING):电梯正在下降
- 停靠态(DOCKED):电梯到达目标楼层
- 故障态(FAULT):电梯出现异常
st复制TYPE ELEVATOR_STATE : (
IDLE := 0,
ASCENDING := 1,
DESCENDING := 2,
DOCKED := 3,
FAULT := 4
);
END_TYPE
2.2 数据结构设计
电梯状态寄存器采用结构体封装,这是整个系统的"记忆中枢"。经过多次迭代,最终版本包含以下字段:
st复制TYPE ELEVATOR_STATUS : STRUCT
CurrentFloor : INT; // 当前楼层(1-10)
State : ELEVATOR_STATE; // 当前状态
Direction : INT; // 运动方向(-1/0/+1)
TargetFloors : ARRAY[1..10] OF BOOL; // 目标楼层位图
DoorStatus : BOOL; // 门状态(TRUE=开启)
Overload : BOOL; // 超载信号
AssignedCalls : ARRAY[1..20] OF CALL_RECORD; // 已分配呼叫
END_STRUCT
END_TYPE
关键设计细节:TargetFloors采用位图而非列表存储,使得楼层状态查询的复杂度从O(n)降到O(1)。这在处理高峰期呼叫时尤为关键。
3. 核心算法实现
3.1 动态成本计算算法
当新呼叫到达时,调度器会为每部电梯计算响应成本。这个算法经历了三个版本的迭代:
st复制FUNCTION CalculateCost : INT
VAR_INPUT
elevator : ELEVATOR_STATUS;
callFloor : INT;
callDir : INT;
END_VAR
VAR
baseCost : INT := ABS(elevator.CurrentFloor - callFloor) * 2;
directionBonus : INT;
loadPenalty : INT;
END_VAR
// 方向匹配度计算
CASE elevator.Direction OF
callDir:
directionBonus := (callDir = 1) ? (10 - callFloor) : callFloor;
0:
directionBonus := 5;
ELSE:
directionBonus := -10;
END_CASE
// 负载惩罚项
loadPenalty := COUNT_TRUE(elevator.TargetFloors) * 3;
RETURN baseCost - directionBonus + loadPenalty;
算法调优时的经验教训:
- 初始版本忽略了负载因素,导致部分电梯过度集中
- 方向奖励系数通过200次仿真测试确定最优值
- 乘法系数选择2的幂次方,便于PLC快速计算
3.2 防死锁机制
在连续48小时的压力测试中,我们发现系统存在三种典型死锁场景:
- 环形等待:A等B释放3层,B等C释放5层,C等A释放7层
- 饥饿现象:边缘楼层呼叫长期得不到响应
- 僵尸电梯:电梯卡在非整数楼层无法移动
解决方案是引入心跳检测和强制重分配机制:
st复制// 在1秒定时中断中执行
IF heartbeatTimer.Q THEN
FOR i := 1 TO 6 DO
IF elevators[i].State = DOCKED
AND elevators[i].TargetFloors <> 0
AND NOT elevators[i].DoorStatus
AND T#30S <= dwellTimer[i] THEN
TRIGGER_REALLOCATE(i);
dwellTimer[i](IN:=FALSE);
END_IF
END_FOR
heartbeatTimer(IN:=TRUE);
END_IF
4. 性能优化技巧
4.1 实时性保障措施
-
中断分级处理:
- 紧急停止信号(最高优先级)
- 楼层传感器中断
- 呼叫按钮中断
- 状态更新中断
-
关键路径优化:
- 将成本计算表预编译为查找表
- 使用位操作替代算术运算
- 限制递归调用深度不超过3层
4.2 仿真测试方法论
我们建立了三级测试体系:
| 测试层级 | 覆盖场景 | 验证指标 |
|---|---|---|
| 单元测试 | 单电梯功能 | 响应时间<200ms |
| 集成测试 | 3电梯协同 | 吞吐量≥30次/分钟 |
| 压力测试 | 6电梯满负荷 | 死锁率<0.1% |
测试数据生成策略:
python复制# pytest参数化测试用例示例
@pytest.mark.parametrize("call_sequence", [
[(1,5), (3,8), (2,1)], # 常规场景
[(i%10+1, (i+3)%10+1) for i in range(20)], # 连续呼叫
[(1,10), (10,1)]*5 # 极限往返
])
def test_elevator_performance(call_sequence):
sim = ElevatorSimulator()
for call in call_sequence:
sim.add_call(*call)
assert sim.run().avg_wait_time < 15.0
5. 工程实践中的经验总结
5.1 调试技巧宝典
- 状态可视化:将电梯运行轨迹实时绘制为SVG图像,异常时自动截图
- 时间旅行调试:记录最近30秒的状态快照,支持回放定位问题
- 噪声注入测试:随机插入1%的错误信号,验证系统鲁棒性
5.2 常见故障处理指南
故障现象1:电梯群全部停在中部楼层
- 检查方向标志更新逻辑
- 验证心跳检测周期是否被阻塞
- 查看最近10次呼叫分配记录
故障现象2:特定楼层响应延迟
- 检查该楼层传感器状态
- 查看对应楼层的位图索引
- 验证成本计算函数参数
故障现象3:门反复开闭
- 检查光幕传感器信号
- 验证DoorStatus与物理状态同步
- 测试过载信号触发阈值
6. 架构演进思考
在项目后期,我们尝试了两种进阶方案:
分布式架构:
- 每部电梯作为独立Agent
- 通过消息总线交换状态
- 采用合同网协议协商任务
预测调度:
- 基于历史数据训练LSTM模型
- 预测未来5分钟的呼叫分布
- 提前调度电梯到热点区域
实测发现分布式架构使响应时间降低了18%,但增加了15%的CPU占用率。而预测调度在早高峰场景下效果显著,可将平均等待时间从22秒缩短到14秒。
这个项目最宝贵的收获不是比赛名次,而是让我们理解了控制系统的本质——在确定性与不确定性之间寻找平衡点。当看到六部电梯在仿真界面画出流畅的运行曲线时,那种纯粹的工程美感,或许就是自动化技术的魅力所在。
