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),任何时刻程序只执行当前步对应的动作和迁移判断,逻辑互不干扰。第二个好处是迁移条件显式化:每一步只回答“我该输出什么”和“什么条件下进入下一步”,而不是在一大段梯形图里隐式地推断先后关系。

状态机主链(自动模式) S0 待机 全部停止 S10 启M3 等运行反馈 S20 启M2 M3已运行 S30 启M1 M2已运行 S40 正常运行 三台均运行 启动 M3运行 M2运行 M1运行 S50 停M1 排空进料 S60 停M2 排空中间 S70 停M3 排空出料 停止命令 M1停 M2停 S0 待机 复位完成 S90 故障 急停/过载 任一状态检测到故障 → 跳转 S90,按安全策略停机 程序分层结构 OB1 主循环 读输入→模式调度 →调子程序→写输出 FC_Auto 状态机步进 输出与迁移 FC_Manual 单机点动 互锁与限位 FC_Safe 急停/故障处理 输出复位 手动模式调用 安全优先调用

主程序与子程序划分:让 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。这个超时判断是实际调试中最容易被忽略、却最能暴露机械卡阻或接触器拒动的一环。

搭建框架时的自检顺序建议:先只验证模式切换与输出选择(手动点动每台设备,确认无双线圈冲突),再单独测试 FC_Auto 的步进(用强制表模拟运行反馈,观察 StepNo 是否按 0→10→20→30→40 推进),最后联调停止链与故障跳转。分三段验证,比一次性联调更容易定位问题出在调度层还是状态机层。

框架搭好后,后续增加工位只需在状态机中插入新步、在输出选择处增加一行映射,主程序结构不变。这正是状态机加分层的价值:把变化限制在局部,让顺序逻辑始终可读、可测、可扩展。