事件驱动状态机与循环扫描状态机:两种计算模型的比较研究

摘要

状态机作为一种基础的计算模型,广泛应用于嵌入式系统、工业控制和软件工程领域。然而,其具体实现呈现出两种截然不同的范式:以 qf4net 为代表的事件驱动状态机(Event-Driven State Machine, EDSM)和以 PLC 阶梯图/SFC 为代表的循环扫描状态机(Cyclic Scan State Machine, CSSM)。本文从执行模型、触发机制、资源消耗、实时性和应用场景五个维度对两种范式进行系统性比较,并通过工业实例阐明各自的优势与局限。研究表明,EDSM 适用于逻辑复杂、资源受限的上位机系统,而 CSSM 则主导于对确定性和安全性要求严苛的工业控制领域。本文旨在为后继学习者提供一个清晰的认知框架,帮助其在工程实践中做出合理的技术选型。

关键词:状态机;事件驱动;循环扫描;qf4net;PLC;SFC


1 引言

状态机是计算机科学中最古老也最持久的概念之一。从 Turing 机的理论构想,到 UML 状态图的工程实践,再到工业控制器中的顺序功能图(Sequential Function Chart, SFC),状态机的核心思想始终如一:系统在任何时刻处于有限个状态之一,状态的迁移由特定条件触发。

然而,当我们深入考察具体的工程实现时,会发现一个有趣的现象:同样是表达"当前在哪、做什么、何时走"这三个基本问题,不同的技术栈给出了截然不同的答案。在 .NET 平台上广泛使用的 qf4net 框架,将状态机构建为纯粹的事件驱动模型;而在工业自动化领域占据统治地位的 PLC(Programmable Logic Controller),则采用循环扫描的方式驱动状态变迁。

这两种范式之间的差异不仅仅是实现细节的不同,而是反映了两种根本不同的计算哲学。理解这种差异,对于从事嵌入式开发、工业控制和软件架构设计的工程师而言,具有重要的实践意义。


2 两种模型的形式化描述

2.1 事件驱动状态机(EDSM)

事件驱动状态机可以形式化地定义为六元组:

M_{ed} = (S, s_0, E, T, G, A)

其中:

  • (S) 是有限状态集合
  • (s_0 \in S) 是初始状态
  • (E) 是事件集合
  • (T: S \times E \times G \to S) 是转移函数
  • (G) 是守卫条件集合
  • (A) 是动作集合(entry、exit、转移动作)

EDSM 的执行模型是:

复制代码
loop
    e ← wait_for_event()   // 阻塞等待事件
    s_current ← dispatch(s_current, e)  // 处理事件,可能转移
end loop

关键特征是:状态机在没有事件到达时处于空闲状态,不消耗计算资源。

2.2 循环扫描状态机(CSSM)

循环扫描状态机可以形式化地定义为五元组:

M_{cs} = (S, s_0, C, G, A)

其中:

  • (S) 是有限状态集合
  • (s_0 \in S) 是初始状态
  • (C) 是输入条件集合(对应 PLC 的物理输入)
  • (G: S \times C \to {true, false}) 是守卫条件函数
  • (A) 是动作集合

CSSM 的执行模型是:

复制代码
loop
    c ← read_all_inputs()           // 读取所有物理输入
    g ← evaluate_guards(s_current, c) // 评估当前状态的守卫
    if g = true then
        s_current ← execute_transition(s_current)  // 转移
    end if
    write_all_outputs()             // 更新物理输出
    wait_for_next_cycle()           // 等待下一个周期
end loop

关键特征是:状态机在每个扫描周期无条件执行,即使没有任何有意义的变化。


3 一个贯穿全文的实例:物料搬运系统

为了便于比较,我们设计一个简单的物料搬运系统作为贯穿全文的案例。

3.1 系统描述

一个自动物料搬运站包含以下组件:

  • 一个放料台,由传感器检测是否有物料放置
  • 一个激光检测器,检测取料位是否有物料残留
  • 一个气动夹爪,用于抓取和释放物料
  • 一个升降轴,用于上下移动

工作流程:

  1. 等待放料完成信号
  2. 检测取料位无物料残留
  3. 执行取料动作
  4. 执行放料动作
  5. 回到等待状态

此外,系统需要处理以下异常:

  • 取料失败时重试最多两次
  • 急停信号触发时立即停止所有动作

3.2 状态定义

无论采用何种实现方式,系统的状态空间是相同的:

复制代码
IDLE          --- 空闲等待
WAITING_RELEASE --- 等待放料完成
WAITING_LASER --- 等待激光检测无料
PICKING       --- 取料中
PICK_RETRY    --- 取料重试
PLACING       --- 放料中
COMPLETED     --- 完成
FAULT         --- 故障
ESTOP         --- 急停

4 事件驱动实现:以 qf4net 为例

4.1 事件的定义

在 qf4net 中,系统的所有变化都被建模为事件:

csharp 复制代码
class MaterialHandlingSignals
{
    public static readonly QSignal ReleaseDone  = new();  // 放料完成
    public static readonly QSignal LaserEmpty   = new();  // 激光无料
    public static readonly QSignal PickSuccess  = new();  // 取料成功
    public static readonly QSignal PickFail     = new();  // 取料失败
    public static readonly QSignal PlaceSuccess = new();  // 放料成功
    public static readonly QSignal EStop        = new();  // 急停
    public static readonly QSignal EStopReset   = new();  // 急停复位
}

4.2 状态机的实现

csharp 复制代码
class MaterialHandler : QActive
{
    private int pickRetryCount;
    private QEQueue deferredQueue = new();

    // 空闲状态
    private QState Idle(IQEvent e)
    {
        if (e.IsSignal(QSignals.Entry))
        {
            pickRetryCount = 0;
            return HANDLED;
        }

        if (e.IsSignal(MaterialHandlingSignals.ReleaseDone))
        {
            TransitionTo(WaitingLaser);
            return HANDLED;
        }

        if (e.IsSignal(MaterialHandlingSignals.EStop))
        {
            TransitionTo(EStopped);
            return HANDLED;
        }

        return TopState;
    }

    // 等待激光检测
    private QState WaitingLaser(IQEvent e)
    {
        if (e.IsSignal(QSignals.Entry))
        {
            // 进入时立即检查激光状态
            if (CheckLaserEmpty())
                Post(new QEvent(MaterialHandlingSignals.LaserEmpty));
            return HANDLED;
        }

        if (e.IsSignal(MaterialHandlingSignals.ReleaseDone))
        {
            // 又来一个放料完成,先 defer 起来
            Defer(e, deferredQueue);
            return HANDLED;
        }

        if (e.IsSignal(MaterialHandlingSignals.LaserEmpty))
        {
            TransitionTo(Picking);
            return HANDLED;
        }

        if (e.IsSignal(MaterialHandlingSignals.EStop))
        {
            TransitionTo(EStopped);
            return HANDLED;
        }

        return TopState;
    }

    // 取料中
    private QState Picking(IQEvent e)
    {
        if (e.IsSignal(QSignals.Entry))
        {
            pickRetryCount = 0;
            StartPickAction();
            return HANDLED;
        }

        if (e.IsSignal(MaterialHandlingSignals.PickSuccess))
        {
            TransitionTo(Placing);
            return HANDLED;
        }

        if (e.IsSignal(MaterialHandlingSignals.PickFail))
        {
            pickRetryCount++;
            if (pickRetryCount < 2)
                TransitionTo(Picking);  // 自转移重试
            else
                TransitionTo(Fault);
            return HANDLED;
        }

        if (e.IsSignal(MaterialHandlingSignals.EStop))
        {
            TransitionTo(EStopped);
            return HANDLED;
        }

        return TopState;
    }

    // 急停状态
    private QState EStopped(IQEvent e)
    {
        if (e.IsSignal(QSignals.Entry))
        {
            EmergencyStopAllActions();
            return HANDLED;
        }

        if (e.IsSignal(MaterialHandlingSignals.EStopReset))
        {
            TransitionTo(Idle);
            return HANDLED;
        }

        return TopState;
    }
}

4.3 EDSM 的关键特征

事件即一切。在这个实现中,没有循环、没有轮询、没有周期性检查。系统的每一次状态变迁都是由一个明确的事件触发的:

  • 物理传感器检测到物料 → 产生 ReleaseDone 事件
  • 激光传感器状态变化 → 产生 LaserEmpty 事件
  • 取料动作完成 → 产生 PickSuccess 或 PickFail 事件

守卫只在事件到达时求值 。注意 WaitingLaser 状态的 entry 动作:它检查激光传感器状态,但这不是轮询,而是"进入这个状态时查一次"。如果之后激光状态发生变化,必须有另一个 LaserEmpty 事件来通知状态机。

空闲时不消耗 CPU 。当系统处于 Idle 状态且没有任何事件发生时,活动对象的事件循环阻塞在 wait_for_event() 上,CPU 占用率接近零。


5 循环扫描实现:以 PLC SFC 为例

5.1 输入输出定义

在 PLC 中,物理输入输出直接映射到内存地址:

复制代码
// 物理输入
I0.0 : 放料完成传感器
I0.1 : 激光检测有料
I0.2 : 取料到位传感器
I0.3 : 放料到位传感器
I0.4 : 急停按钮

// 物理输出
Q0.0 : 夹爪夹紧
Q0.1 : 夹爪松开
Q0.2 : 升降轴上升
Q0.3 : 升降轴下降

// 内部变量
MW10 : 取料重试计数
M0.0 : 取料成功标志
M0.1 : 取料失败标志

5.2 SFC 实现

SFC 的程序结构如下(以 IEC 61131-3 标准表述):

复制代码
PROGRAM MaterialHandler
VAR
    RetryCount : INT := 0;
    PickSuccess : BOOL := FALSE;
    PickFail : BOOL := FALSE;
END_VAR

// Step 定义
STEP Idle:
    // 空闲状态,无动作
END_STEP

TRANSITION FROM Idle TO WaitingLaser:
    := I0.0;  // 放料完成传感器 ON
END_TRANSITION

STEP WaitingLaser:
    // 等待激光检测,无动作
END_STEP

TRANSITION FROM WaitingLaser TO Picking:
    := NOT I0.1;  // 激光检测无料
END_TRANSITION

STEP Picking:
    // 执行取料动作
    Q0.0 := TRUE;   // 夹爪夹紧
    Q0.2 := TRUE;   // 升降轴上升
    
    // 取料完成判断(简化示例)
    IF I0.2 THEN
        PickSuccess := TRUE;
    ELSE
        PickFail := TRUE;
    END_IF
END_STEP

TRANSITION FROM Picking TO Placing:
    := PickSuccess;
END_TRANSITION

TRANSITION FROM Picking TO Picking:
    := PickFail AND RetryCount < 2;
    // 自转移重试,同时 RetryCount 递增
    // 注:实际 PLC 中重试计数的递增需在 Step 动作中处理
END_TRANSITION

TRANSITION FROM Picking TO Fault:
    := PickFail AND RetryCount >= 2;
END_TRANSITION

// ... 后续步骤类似

// 急停处理(全局优先级最高)
TRANSITION FROM * TO EStopped:
    := I0.4;  // 急停按钮 ON
END_TRANSITION

STEP EStopped:
    // 停止所有输出
    Q0.0 := FALSE;
    Q0.1 := FALSE;
    Q0.2 := FALSE;
    Q0.3 := FALSE;
END_STEP

TRANSITION FROM EStopped TO Idle:
    := NOT I0.4;  // 急停按钮释放
END_TRANSITION

5.3 CSSM 的关键特征

每个周期都在检查。PLC 在每个扫描周期都会执行当前 Step 的动作,并评估所有可能的转移条件。这意味着:

  • 即使没有传感器状态变化,转移条件也被反复计算
  • 响应时间取决于扫描周期长度,而非事件传递时间

守卫是唯一的驱动力。在 CSSM 中,没有显式的"事件"概念。转移的发生完全取决于守卫条件的布尔值:

复制代码
TRANSITION FROM WaitingLaser TO Picking:
    := NOT I0.1;

这段代码的含义是:每个周期检查 I0.1(激光检测),如果它为 False,就转移到 Picking。 不需要一个叫做"激光无料"的事件来触发。

周期性执行保证了确定性。由于扫描周期是固定的(通常为 1ms-50ms),系统的最大响应时间是确定的。这对于安全关键的工业应用至关重要。


6 两种模型的深度对比

6.1 执行模型对比

复制代码
EDSM 的时间线:
事件:    ----R-------L---P-------P-------C---------------
状态:    IDLE  WAIT  PICK  PICK  PLACE  COMPLETE  IDLE
         ↑事件驱动   ↑守卫求值   ↑守卫求值
         
CSSM 的时间线:
周期:    |--|--|--|--|--|--|--|--|--|--|--|--|--|--|--
状态:    IDLE IDLE IDLE WAIT WAIT PICK PICK PLACE COMPLETE
         ↑每个周期都检查守卫

在 EDSM 中,状态变迁发生在事件到达的时刻,两次变迁之间状态机处于静止状态。在 CSSM 中,状态变迁发生在某个周期的守卫求值时刻,即使没有变化,状态机也在持续运转。

6.2 资源消耗对比

考虑一个典型的场景:系统在 95% 的时间里处于空闲状态,只有 5% 的时间在处理物料。

指标 EDSM CSSM
空闲时 CPU 占用 ≈ 0%(事件循环阻塞) 100%(持续扫描)
峰值 CPU 占用 事件处理期间 始终相同
内存占用 较低(仅有事件队列) 较低(仅有变量)
功耗 空闲时极低 持续恒定

对于电池供电的嵌入式设备,EDSM 的优势显而易见。但对于工业 PLC,功耗通常不是首要考虑因素。

6.3 实时性与确定性

指标 EDSM CSSM
响应时间下限 事件产生 + 队列延迟 + 处理时间 0(理论上,条件满足即刻转移)
响应时间上限 取决于事件队列长度和处理时间 扫描周期 × 最大转移深度
响应时间确定性 低(受操作系统调度影响) 高(扫描周期固定)
最坏情况响应 难以精确计算 可精确计算

这就是为什么安全相关的控制(如急停、光栅保护)几乎总是由 PLC 或专用安全控制器处理。它们的响应时间是确定的、可预测的。

6.4 复杂度扩展性

指标 EDSM CSSM
状态数量增加 线性增加代码量 线性增加代码量
嵌套层级 原生支持(HSM) 需要手动模拟
并发状态 正交区域支持 SFC 并行分支支持
事件携带数据 天然支持 需额外变量传递
defer/recall 框架内置 需自行实现队列

在逻辑复杂度方面,EDSM 具有明显优势。qf4net 的层次状态机(HSM)允许子状态继承父状态的行为,大大减少了重复代码。而 PLC 的 SFC 虽然也支持层级,但在实际工程中使用较少。

6.5 错误处理对比

考虑取料失败重试的场景:

EDSM 实现(已在 4.2 节展示):

  • 重试逻辑封装在 Picking 状态内部
  • 重试计数器是局部变量
  • 自转移自动执行 entry 动作(重新发起取料)

CSSM 实现(简化):

复制代码
STEP Picking:
    Q0.0 := TRUE;
    Q0.2 := TRUE;
    
    // 必须在 Step 动作中手动管理重试
    IF I0.2 THEN
        PickSuccess := TRUE;
        RetryCount := 0;
    ELSE
        // 超时或失败判断
        IF TimerExpired THEN
            PickFail := TRUE;
            RetryCount := RetryCount + 1;
        END_IF
    END_IF
END_STEP

TRANSITION FROM Picking TO Picking:
    := PickFail AND RetryCount < 2;
    
TRANSITION FROM Picking TO Fault:
    := PickFail AND RetryCount >= 2;

可以看到,CSSM 需要更多的辅助变量和手动管理逻辑。在 EDSM 中,自转移天然支持重试;在 CSSM 中,重试需要额外的标志位和计数器。


7 混合模式的工程实践

在实际工程中,纯粹的 EDSM 或 CSSM 并不总是最佳选择。许多系统采用混合模式,结合两者的优势。

7.1 上位机 + PLC 架构

最常见的混合模式是:

复制代码
┌─────────────────────────────────────────────────────┐
│                    上位机 (.NET)                      │
│  ┌──────────────────────────────────────────────┐   │
│  │  事件驱动状态机 (qf4net)                       │   │
│  │  负责:调度逻辑、配方管理、数据记录、人机交互   │   │
│  └──────────────────────────────────────────────┘   │
│                          ↕ 以太网 / 现场总线         │
├─────────────────────────────────────────────────────┤
│                    下位机 (PLC)                      │
│  ┌──────────────────────────────────────────────┐   │
│  │  循环扫描状态机 (SFC)                         │   │
│  │  负责:轴控、I/O、安全逻辑、实时闭环           │   │
│  └──────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────┘

在这种架构中:

  • PLC 负责实时性要求高的底层控制(伺服驱动、安全逻辑)
  • 上位机负责逻辑复杂的调度和数据处理
  • 两者通过现场总线(Profinet、EtherCAT 等)交换事件和数据

7.2 在 EDSM 中模拟周期性检查

有时需要在事件驱动系统中执行周期性任务,例如温度监控。qf4net 通过定时器事件实现:

csharp 复制代码
class TemperatureMonitor : QActive
{
    private QTimeEvt timer = new QTimeEvt();

    private QState Monitoring(IQEvent e)
    {
        if (e.IsSignal(QSignals.Entry))
        {
            timer.PostEvery(this, MySignals.Tick, 100); // 每 100ms
            return HANDLED;
        }

        if (e.IsSignal(MySignals.Tick))
        {
            float temp = ReadTemperature();
            if (temp > MAX_TEMP)
                TransitionTo(Overheat);
            return HANDLED;
        }

        return TopState;
    }
}

这实际上是 EDSM 对 CSSM 周期性行为的模拟,但更高效:只有在定时器事件到达时才执行检查。

7.3 在 CSSM 中模拟事件驱动

PLC 中也可以通过边沿检测模拟事件驱动:

复制代码
// 上升沿检测,相当于"事件"
IF RE(I0.0) THEN
    // 只在放料完成的上升沿执行一次
    StepActivate(WaitingLaser);
END_IF

这种方式结合了 CSSM 的周期性和 EDSM 的事件语义,在工业实践中非常常见。


8 选型指南

8.1 选择 EDSM 的场景

  • 逻辑复杂度高:多层嵌套状态、大量条件分支、需要 defer/recall
  • 资源受限:电池供电、CPU 性能有限、需要低功耗
  • 与 IT 系统集成:需要连接数据库、Web 服务、消息队列
  • 频繁变更需求:软件需要快速迭代和重构
  • 代表平台:qf4net、Qt SCXML、Boost.Statechart、Spring State Machine

8.2 选择 CSSM 的场景

  • 实时性要求高:响应时间需要精确到毫秒甚至微秒级
  • 安全关键:涉及人身安全、设备保护,需要确定性行为
  • 环境恶劣:高温、振动、电磁干扰,需要工业级可靠性
  • 维护人员背景:现场工程师更熟悉梯形图和 SFC
  • 代表平台:西门子 S7-GRAPH、三菱 MELSEC SFC、Codesys SFC

8.3 决策矩阵

需求 EDSM CSSM
状态数 > 10 ★★★★★ ★★★
嵌套深度 > 3 ★★★★★ ★★
响应时间 < 1ms ★ ★★★★★
安全完整性等级 SIL3 ★ ★★★★★
电池供电 ★★★★★ ★
与云端通信 ★★★★★ ★★
现场调试便利性 ★★★ ★★★★★
长期维护成本 ★★★★ ★★★

9 结语

事件驱动状态机和循环扫描状态机并非对立关系,而是针对不同问题的优化解。EDSM 追求的是计算效率的最大化和逻辑表达的清晰性,适合复杂多变的软件系统;CSSM 追求的是时间确定性和运行可靠性的最大化,适合严苛的工业环境。

理解两者的本质差异------一个是"有事叫我,没事待机",一个是"持续巡逻,随时响应"------是掌握状态机工程实践的关键一步。在实际项目中,明智的工程师会根据需求权衡取舍,甚至在同一个系统中融合两种模式,让每种范式在最擅长的领域发挥作用。

最后,无论选择哪种范式,状态机的核心价值始终不变:它将隐式的、分散在 if-else 和 bool 变量中的系统行为,转化为显式的、结构化的、可推理的状态空间。 这种转化所带来的可维护性和正确性提升,远远超过了具体实现范式之间的差异。


参考文献

1 Samek, M. (2008). Practical UML Statecharts in C/C++: Event-Driven Programming for Embedded Systems . Newnes.

2 Harel, D. (1987). Statecharts: A visual formalism for complex systems. Science of Computer Programming , 8(3), 231-274.

3 IEC 61131-3:2013. Programmable controllers - Part 3: Programming languages.

4 Douglass, B. P. (1999). Doing Hard Time: Developing Real-Time Systems with UML, Objects, Frameworks, and Patterns . Addison-Wesley.

5 qf4net Documentation. Quantum Leaps, LLC. https://www.state-machine.com/qtools/qf4net.html

相关推荐
longlongzihan2 小时前
LeetCode 24.两两交换链表中的节点:虚拟头结点与指针
算法·leetcode·链表
residual_fan2 小时前
基于控制感知域对抗网络的变工况故障诊断:从控制系统与运行状态协同的角度建立模型
人工智能·算法·数据挖掘·数据分析
翻山越岭的少年2 小时前
用控制信号理解状态变化:基于控制感知域对抗网络的变工况故障诊断
算法
longlongzihan2 小时前
从LeetCode 19. 删除链表的倒数第 N 个结点深入理解“哨兵节点”
c++·算法·leetcode·链表
DeroZan2 小时前
# jieba 分词 & 文本语料分析 & Python 内置函数笔记
算法
Gigavision2 小时前
CAS(ME)²数据集 感知图卷积网络算法 CAS(ME)²数据集下载
网络·算法
杜 硕2 小时前
快速排序02
算法·排序算法
careathers2 小时前
【数据结构】二叉树拓展
数据结构·算法
java_nnnn3 小时前
LeetCode刷题滑动窗口(长度最小的子数组)
算法·leetcode·职场和发展