摘要
状态机作为一种基础的计算模型,广泛应用于嵌入式系统、工业控制和软件工程领域。然而,其具体实现呈现出两种截然不同的范式:以 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 系统描述
一个自动物料搬运站包含以下组件:
- 一个放料台,由传感器检测是否有物料放置
- 一个激光检测器,检测取料位是否有物料残留
- 一个气动夹爪,用于抓取和释放物料
- 一个升降轴,用于上下移动
工作流程:
- 等待放料完成信号
- 检测取料位无物料残留
- 执行取料动作
- 执行放料动作
- 回到等待状态
此外,系统需要处理以下异常:
- 取料失败时重试最多两次
- 急停信号触发时立即停止所有动作
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