3. 顺序控制程序架构设计
当输送线的工位数量超过三四个、动作之间存在明确的先后依赖时,把所有逻辑堆在 OB1 里会迅速变得难以维护:一个气缸的手动点动和自动联锁混在同一段网络中,排查故障时只能靠逐行阅读。本节要解决的核心问题,是把“顺序”这件事从零散逻辑中抽出来,用状态机作为骨架,用主程序与子程序的分层把自动流程、手动操作、模式切换各自隔离,最终搭出一个可扩展、可调试的程序框架。
用状态机表达顺序:把流程变成可枚举的步
顺序控制的本质是“在满足条件时从一个稳定状态迁移到下一个稳定状态”。以一条典型的三段皮带输送线为例:进料皮带 M1、中间皮带 M2、出料皮带 M3,启动顺序为 M3→M2→M1(逆物料流向启动,防止堵料),停止顺序为 M1→M2→M3(顺物料流向停止,排空物料)。若用状态机描述,可定义如下状态:
- S0 待机:所有皮带停止,等待启动命令;
- S10 启动M3:输出 M3 运行,等待 M3 运行反馈到位;
- S20 启动M2:M3 已运行后启动 M2;
- S30 启动M1:M2 已运行后启动 M1,全部运行后进入 S40;
- S40 正常运行:三台皮带均运行,监视故障与停止命令;
- S50 停止M1 → S60 停止M2 → S70 停止M3:按顺流向逐台停止;
- S90 故障:任一皮带故障或急停触发,按安全策略停机。
状态机带来的第一个好处是“当前步”变成一个整数变量(例如 StepNo : INT),任何时刻程序只执行当前步对应的动作和迁移判断,逻辑互不干扰。第二个好处是迁移条件显式化:每一步只回答“我该输出什么”和“什么条件下进入下一步”,而不是在一大段梯形图里隐式地推断先后关系。
主程序与子程序划分:让 OB1 只做调度
推荐的分层是“一个主循环 + 三个功能块”。OB1 不写具体动作逻辑,只做四件事:读取物理输入到过程映像、判断当前模式、按模式调用对应子程序、把结果写回输出。这样 OB1 的扫描顺序固定,任何时候打开主程序都能一眼看清“这一周期到底执行了哪条路径”。
三个子程序的职责边界要划清:FC_Auto 只负责状态机的步进与自动输出,内部维护 StepNo,每一步先置位本步输出,再判断迁移条件;FC_Manual 只处理单台设备的点动、正反转和就地操作,必须带互锁(例如 M2 未运行时不允许点动 M1 的进料方向,防止堆料);FC_Safe 独立于模式,无论自动还是手动,只要急停、过载或拉绳开关动作,立即把相关输出复位并置故障标志。把安全逻辑单独成块,是为了避免它被模式切换“绕过”。
一个常见的实现细节是输出归属。若 FC_Auto 和 FC_Manual 都直接写同一个 Q0.0(M1 接触器),手动与自动切换瞬间会出现输出抖动甚至双线圈冲突。规范做法是:两个子程序都只写中间变量 M_Auto_M1、M_Man_M1,由 OB1 在调度末尾按当前模式选择一路赋给实际输出,保证每个物理输出在整个程序中只有一个写入点。
自动/手动模式切换架构
模式切换不是简单地把一个开关量接到 IF 上,而要处理三个问题:切换时机、状态保持、无扰切换。工程上通常设置 AutoMode : BOOL 作为模式标志,由面板旋钮或 HMI 按钮驱动,并加一个“模式切换允许”条件——只有在设备全部停止、或处于安全状态时才允许从手动切到自动,避免运行中切换导致状态机从半途接管。
状态保持方面,切入手动时保留 StepNo 不清零,但自动输出全部撤销;切回自动时,程序先判断当前实际设备状态是否与 StepNo 期望一致,不一致则回到 S0 重新起步,一致才继续步进。这种“状态校验后再恢复”的策略,比盲目续跑更安全。无扰切换的关键是输出选择逻辑放在 OB1 末尾统一执行,切换瞬间不会出现两个子程序同时驱动同一输出的窗口。
程序框架搭建实战
下面给出与上述架构对应的 OB1 调度骨架(以结构化文本示意,变量名可按实际 PLC 平台调整)。示例参数为教学假设:M1/M2/M3 分别对应 Q0.0/Q0.1/Q0.2,运行反馈为 I0.0/I0.1/I0.2,急停为 I1.0(常闭,正常为 1)。
(* OB1 主循环:只做调度,不写动作逻辑 *)
"Input_Image" := 读取物理输入(); (* 过程映像刷新 *)
"FC_Safe"(); (* 安全块始终优先执行 *)
IF "FaultFlag" THEN
"AutoMode" := FALSE; (* 故障时强制退出自动 *)
END_IF;
IF "AutoMode" AND NOT "FaultFlag" THEN
"FC_Auto"(); (* 状态机步进 *)
ELSE
"FC_Manual"(); (* 手动点动与互锁 *)
END_IF;
(* 输出选择:每个物理输出仅此一处写入 *)
"Q0.0" := ("AutoMode" AND "M_Auto_M1") OR (NOT "AutoMode" AND "M_Man_M1");
"Q0.1" := ("AutoMode" AND "M_Auto_M2") OR (NOT "AutoMode" AND "M_Man_M2");
"Q0.2" := ("AutoMode" AND "M_Auto_M3") OR (NOT "AutoMode" AND "M_Man_M3");
FC_Auto 内部用 CASE 语句组织状态机,每个分支只做两件事:给本步输出中间变量赋值、判断迁移条件并改写 StepNo。例如 S10 分支中,置 M_Auto_M3 := TRUE,当 I0.2 为 1(M3 运行反馈到位)时把 StepNo 置为 20;若超过设定时间(示例取 5 s)仍未收到反馈,则置故障标志跳 S90。这个超时判断是实际调试中最容易被忽略、却最能暴露机械卡阻或接触器拒动的一环。
框架搭好后,后续增加工位只需在状态机中插入新步、在输出选择处增加一行映射,主程序结构不变。这正是状态机加分层的价值:把变化限制在局部,让顺序逻辑始终可读、可测、可扩展。