文章目录

博途SCL的CASE语句在状态机中的应用

发布于 2026-03-22 21:00:46 · 浏览 4 次 · 评论 0 条

博途SCL的CASE语句是实现状态机控制的利器,相比梯形图的复杂跳转网络,它能用结构化文本清晰表达多状态切换逻辑。本文从实际工程角度,手把手教你用CASE语句构建可靠的状态机。


为什么选CASE语句做状态机

状态机的核心需求:根据当前状态和触发条件,决定下一步去哪。CASE语句天生匹配这个模型——它的结构就是"先查状态,再执行对应动作",代码可读性远超梯形图的线圈-触点网络。

实际优势体现在三方面:

  • 代码集中:一个CASE块包揽所有状态逻辑,无需跨页面追踪
  • 修改安全:新增状态时,不会意外触动既有状态的跳转条件
  • 移植方便:纯文本结构,复制到其他项目或PLC品牌时改动最小

状态机的三要素设计

动手写代码前,必须明确三个东西:状态定义、触发条件、动作输出。

第一步:定义状态变量

用枚举类型(自定义数据类型)管理状态,不要用裸数字。在博途的"PLC数据类型"中新建

类型名称: eMachineState
枚举值:
    - Idle := 0        (*待机*)
    - Init := 1        (*初始化*)
    - Running := 2     (*运行中*)
    - Error := 3       (*故障*)
    - Stop := 4        (*停止中*)

然后在程序块中声明状态变量:

VAR
    stateCurrent : eMachineState;    (*当前状态*)
    stateNext    : eMachineState;    (*下一状态预备*)
    timerState   : TON;              (*状态驻留计时*)
END_VAR

第二步:梳理状态转移图

用Mermaid可视化你的状态机,确保没有死循环和不可达状态。以下是一个典型传送带控制的状态机:

graph TD A["Idle\n待机"] -- "启动信号\n且急停未触发" --> B["Init\n初始化"] B -- "初始化完成\n定时2秒" --> C["Running\n运行中"] C -- "物料检测超时\n5秒无料" --> D["Stop\n停止中"] C -- "急停按下\n或变频器故障" --> E["Error\n故障"] D -- "完全停止\n定时1秒" --> A E -- "故障复位\n且异常解除" --> B C -- "正常停止按钮" --> D

第三步:设计每个状态的动作

状态 核心动作 跳转条件
Idle 所有输出置零,等待启动 startButton AND NOT eStop
Init 复位变频器、清计数器、预置速度 timerState.Q(2秒到)
Running 使能运行、监控物料流 停止信号/故障/无料超时
Error 急停输出、记录故障码、闪报警灯 resetButton AND faultGone
Stop 降速停车、保持制动 timerState.Q(1秒到)

核心代码:CASE语句的完整写法

在博途的SCL编辑器中,右键程序块选择"添加新块"→"函数块"→语言选SCL。

基础框架(单CASE结构)

FUNCTION_BLOCK "FB_StateMachine_Basic"
VAR_INPUT
    startButton : Bool;
    stopButton  : Bool;
    eStop       : Bool;      (*急停,常闭触点*)
    faultVFD    : Bool;      (*变频器故障*)
    materialSensor : Bool;   (*物料检测*)
END_VAR

VAR_OUTPUT
    runMotor    : Bool;
    alarmHorn   : Bool;
    statusLight : Byte;      (*0=灭, 1=绿, 2=黄, 3=红*)
END_VAR

VAR
    stateCurrent : eMachineState := eMachineState#Idle;
    timerState   : TON;
    tonMaterial  : TON;      (*物料监控定时*)
    faultCode    : Word;
END_VAR
BEGIN

(*状态机核心:CASE语句*)
CASE stateCurrent OF

    eMachineState#Idle:
        (*动作:待机状态*)
        runMotor    := FALSE;
        alarmHorn   := FALSE;
        statusLight := 0;    (*全灭*)

        (*跳转判断*)
        IF startButton AND NOT eStop THEN
            stateCurrent := eMachineState#Init;
        END_IF;

    eMachineState#Init:
        (*动作:初始化序列*)
        timerState(IN := TRUE, PT := T#2S);
        statusLight := 2;    (*黄灯闪烁,实际用闪烁位*)

        (*初始化完成判断*)
        IF timerState.Q THEN
            timerState(IN := FALSE);    (*复位定时器*)
            tonMaterial(IN := FALSE, PT := T#5S);  (*预清物料定时*)
            stateCurrent := eMachineState#Running;
        END_IF;

        (*紧急打断*)
        IF eStop THEN
            timerState(IN := FALSE);
            stateCurrent := eMachineState#Error;
        END_IF;

    eMachineState#Running:
        (*动作:正常运行*)
        runMotor    := TRUE;
        statusLight := 1;    (*绿灯常亮*)

        (*物料监控:5秒无料则停*)
        tonMaterial(IN := NOT materialSensor, PT := T#5S);

        (*跳转判断:优先级从高到低*)
        IF eStop OR faultVFD THEN
            faultCode := IF eStop THEN 16#0001 ELSE 16#0002;
            stateCurrent := eMachineState#Error;
        ELSIF stopButton THEN
            stateCurrent := eMachineState#Stop;
        ELSIF tonMaterial.Q THEN
            faultCode := 16#0003;    (*无料报警代码*)
            stateCurrent := eMachineState#Stop;
        END_IF;

    eMachineState#Error:
        (*动作:故障处理*)
        runMotor    := FALSE;
        alarmHorn   := TRUE;
        statusLight := 3;    (*红灯常亮*)

        (*跳转判断:严格的安全确认*)
        IF resetButton AND NOT eStop AND NOT faultVFD THEN
            alarmHorn := FALSE;
            stateCurrent := eMachineState#Init;    (*必须重新初始化*)
        END_IF;

    eMachineState#Stop:
        (*动作:有序停止*)
        runMotor    := FALSE;    (*变频器减速停车由外部处理*)
        timerState(IN := TRUE, PT := T#1S);

        IF timerState.Q THEN
            timerState(IN := FALSE);
            stateCurrent := eMachineState#Idle;
        END_IF;

ELSE
    (*异常防护:非法状态自动复位*)
    stateCurrent := eMachineState#Idle;
    faultCode := 16#00FF;

END_CASE;

END_FUNCTION_BLOCK

进阶:双CASE分离结构与动作

复杂设备建议把"状态转移逻辑"和"状态输出动作"分开,避免修改时牵一发而动全身。

第一CASE:只算下一个状态

(*状态转移计算 - 纯逻辑,无输出*)
CASE stateCurrent OF

    eMachineState#Idle:
        stateNext := IF startTrigger THEN eMachineState#Init 
                    ELSE stateCurrent;

    eMachineState#Init:
        stateNext := IF initDone THEN eMachineState#Running
                    ELSE IF eStop THEN eMachineState#Error
                    ELSE stateCurrent;

    (*...其他状态...*)

END_CASE;

(*上升沿检测:状态切换时刻*)
stateChanged := (stateCurrent <> stateNext);
IF stateChanged THEN
    stateCurrent := stateNext;
    timerState(IN := FALSE);    (*切状态时复位通用定时*)
END_IF;

第二CASE:只算输出动作

(*输出动作 - 纯组合逻辑,无状态跳转*)
CASE stateCurrent OF

    eMachineState#Idle:
        runMotor := FALSE;
        doSomethingElse := FALSE;

    eMachineState#Running:
        runMotor := TRUE;
        doSomethingElse := conditionA AND NOT timeout;

    (*...*)

END_CASE;

这种结构的优势:调试时可以强制stateCurrent到任意状态,观察输出是否正确,而不用担心触发意外跳转。


关键细节与防坑指南

定时器的正确用法

SCL中的TON调用要注意:每次扫描都必须执行,不能把调用藏在IF里。正确写法:

(*错误:定时器只在某状态运行,其他状态不刷新*)
IF stateCurrent = eMachineState#Init THEN
    timerState(IN := TRUE, PT := T#2S);    (*危险:退出状态时定时器冻结*)
END_IF;

(*正确:所有状态的定时器统一刷新,用IN位控制*)
timerState(IN := (stateCurrent = eMachineState#Init), PT := T#2S);

状态切换的上升沿处理

需要"进入某状态时只执行一次"的操作(如发脉冲命令、记录时间戳),用状态变化位触发:

stateChanged := (stateCurrent <> stateLast);
stateLast := stateCurrent;    (*下一周期用*)

IF stateChanged AND (stateCurrent = eMachineState#Init) THEN
    resetCounter := TRUE;     (*初始化时清计数器,只清一次*)
ELSE
    resetCounter := FALSE;
END_IF;

安全状态的兜底设计

务必保留ELSE分支,处理程序跑飞、断电恢复等异常情况:

CASE stateCurrent OF
    (*正常状态...*)
ELSE
    (*任何非法状态都到这里*)
    stateCurrent := eMachineState#Idle;
    logEvent(code := 16#8001, msg := "非法状态恢复");
    triggerSafeOutput();    (*触发安全输出*)
END_CASE;

实战:扩展为多层次状态机

真实设备往往需要"主状态机+子状态机"的层次结构。例如:

  • 主状态:生产模式 / 维护模式 / 调试模式
  • 子状态(生产模式下):上料→加工→检测→下料

实现方式:用结构体嵌套CASE,或分开多个FB。

TYPE eMainState : (Mode_Prod, Mode_Maint, Mode_Debug);
TYPE eSubState_Prod : (Sub_Load, Sub_Process, Sub_Check, Sub_Unload);

VAR
    mainState : eMainState;
    subState  : eSubState_Prod;
END_VAR

(*主状态机 - 模式切换*)
CASE mainState OF
    Mode_Prod:
        (*嵌入子状态机*)
        CASE subState OF
            Sub_Load:    (*...*) ;
            Sub_Process: (*...*) ;
            (*...*)
        END_CASE;

        IF switchToMaint THEN
            mainState := Mode_Maint;
        END_IF;

    Mode_Maint: (*...*);
    Mode_Debug: (*...*);
END_CASE;

调试技巧

场景 操作方法
强制某状态 博途在线→变量表→修改值stateCurrent触发一次
查看状态流 添加监控表,把stateCurrent拖入,观察数值跳变
抓取瞬态故障 ELSE分支添加faultCode记录,配合诊断缓冲区
单步执行 OB100启动复位+manualStep输入,配合按钮逐状态推进

掌握CASE语句状态机后,你会发现:原本需要几十行梯形图的互锁网络,现在十几个CASE分支清晰搞定;原本改一处逻辑要翻三页程序,现在上下扫一眼就能定位。这才是SCL该有的效率。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文