3、状态与转移详解:状态类型(普通、子状态)、转移条件、事件触发、默认转移与历史节点
在Stateflow中,状态(State)与转移(Transition)是构成有限状态机(FSM)的两大核心要素。状态定义了系统在某一时刻所处的稳定行为模式,而转移则描述了系统如何从一个状态切换到另一个状态。本章将深入剖析状态的类型划分(普通状态与子状态)、转移条件的构造方法、事件触发机制、默认转移的语义,以及历史节点(History Junction)这一高级特性。理解这些概念是设计健壮、可维护控制逻辑的基础,也是从“能画图”走向“会建模”的关键一步。
3.1 状态类型:普通状态与子状态
核心原理
Stateflow中的状态分为两大类:普通状态(Atomic State)和子状态(Substate)。普通状态是最基本的执行单元,它内部不再包含其他状态,直接承载entry、during、exit等动作。子状态则嵌套在父状态(Superstate)内部,父状态可以包含多个互斥(Exclusive)或并行(Parallel)的子状态。这种层级结构使得复杂控制逻辑可以被分解为多个层次,每一层只关注本层的状态转移,从而降低模型的认知复杂度。
从执行语义上看,当系统进入一个父状态时,必须同时激活其内部的一个(互斥模式)或多个(并行模式)子状态。父状态的entry动作在进入时执行一次,子状态的entry动作紧随其后执行。在状态机运行期间,父状态的during动作和活动子状态的during动作都会在每个时间步执行。退出时,先执行活动子状态的exit动作,再执行父状态的exit动作。这种“先子后父”的退出顺序和“先父后子”的进入顺序是理解嵌套状态行为的关键。
判断方法
- 状态内部是否有子状态框:如果状态内部绘制了子状态框,则为父状态;否则为普通状态。
- 状态图标差异:在Stateflow编辑器中,父状态通常显示为圆角矩形,内部包含子状态区域;普通状态则没有内部结构。
- 动作执行顺序:父状态的entry动作先于子状态执行,exit动作后于子状态执行。通过观察动作执行顺序可以判断嵌套关系。
- 并行分解标记:如果父状态内部有虚线分割,表示并行分解(AND),所有子状态同时激活;实线分割表示互斥分解(OR),同一时刻仅一个子状态激活。
案例拆解:电机控制中的状态嵌套
以直流电机控制系统为例,设计一个“运行”父状态,内部包含“加速”、“匀速”、“减速”三个互斥子状态。父状态“运行”的entry动作执行“使能驱动器”,exit动作执行“禁用驱动器”。子状态“加速”的during动作执行“PWM占空比递增”,“匀速”的during动作执行“PWM占空比保持”,“减速”的during动作执行“PWM占空比递减”。
当系统从“停止”状态转移到“运行”状态时,默认进入“加速”子状态。此时执行顺序为:停止状态的exit → 运行状态的entry(使能驱动器)→ 加速状态的entry(初始化PWM=0)。当电机转速达到目标值时,从“加速”转移到“匀速”,执行顺序为:加速状态的exit → 匀速状态的entry。这种嵌套设计使得每个子状态只关注自身的速度调节逻辑,而父状态统一管理驱动器的使能与禁用,避免了重复代码。
3.2 转移条件与事件触发
核心原理
转移(Transition)定义了状态之间的切换路径。一条完整的转移由触发事件(Event)、条件(Condition)和转移动作(Transition Action)三部分组成,其标准语法为:event [condition] {transition_action}。事件是触发转移的“信号”,可以是Stateflow内部定义的输入事件、输出事件,也可以是本地事件;条件是一个布尔表达式,只有事件发生且条件为真时,转移才被激活;转移动作在转移被激活后、目标状态entry动作执行前执行。
事件触发分为多种类型:隐式事件(如entry、exit、during)由状态自身行为触发;显式事件由外部输入或内部逻辑显式广播;超时事件(after(n, tick))基于仿真时钟或事件计数触发;输入事件由Simulink信号边沿触发。条件则支持关系运算、逻辑运算、算术运算以及状态自身数据的访问。
操作流程:构造转移条件
- 确定触发源:分析系统需求,确定转移是由外部信号(如传感器值变化)、内部定时(如超时)还是逻辑判断(如状态完成)触发。
- 定义事件:在Stateflow中创建输入事件(从Simulink输入端口接收)或本地事件(内部广播)。
- 编写条件表达式:使用状态数据、输入数据、常量等构造布尔表达式。例如:
[temperature > 85 && fan_speed < 2000]。 - 设置转移动作:在转移标签的
{}中编写赋值、函数调用等动作。例如:{error_count = 0;}。 - 处理优先级:当多个转移同时满足条件时,需要设置转移优先级(通过转移顺序编号),避免冲突。
案例拆解:温度控制系统的转移设计
设计一个温度控制系统,包含“加热”、“保温”、“冷却”三个状态。输入事件temp_high和temp_low由Simulink中的比较器产生。转移设计如下:
- “加热” → “保温”:事件
temp_high触发,条件[temp >= 60],转移动作{heater_power = 0.3;}。 - “保温” → “冷却”:事件
temp_high触发,条件[temp >= 80],转移动作{cooler_on = 1;}。 - “冷却” → “保温”:事件
temp_low触发,条件[temp <= 65],转移动作{cooler_on = 0;}。
这里事件提供了“何时检查”的时机,条件提供了“是否切换”的判据。如果只使用条件而没有事件,则每个时间步都会检查条件,可能导致频繁转移;如果只使用事件而没有条件,则事件一旦发生必然转移,无法实现阈值判断。两者结合才能精确控制转移时机。
[temp >= 60],而“保温→冷却”的条件是[temp >= 80],当温度恰好为80时,两个转移都可能被激活。此时必须通过转移优先级或条件互斥(如[temp >= 60 && temp < 80])来消除歧义。
3.3 默认转移与历史节点
核心原理
默认转移(Default Transition)用于指定当父状态首次被激活时,默认进入哪个子状态。它用一条带箭头的曲线表示,起点是一个实心圆点,终点指向默认子状态。默认转移只在父状态被激活时执行一次,且不依赖任何事件或条件(除非显式添加条件)。历史节点(History Junction)则用于记录父状态上次退出时处于哪个活动子状态,当父状态再次被激活时,可以直接恢复到上次退出的子状态,而不是从默认转移重新开始。
历史节点分为两种:浅历史(Shallow History)记录当前层的活动子状态;深历史(Deep History)记录整个嵌套层级中所有层的活动状态。在图形上,浅历史用“H”表示,深历史用“H*”表示。历史节点通常与默认转移配合使用:如果父状态从未被激活过(即没有历史记录),则使用默认转移指定的子状态;如果有历史记录,则恢复到历史记录的子状态。
判断方法
| 特性 | 默认转移 | 浅历史节点 | 深历史节点 |
|---|---|---|---|
| 触发时机 | 父状态首次激活 | 父状态重新激活且有历史 | 父状态重新激活且有历史 |
| 恢复层级 | 仅指定一层子状态 | 仅恢复当前层的子状态 | 恢复所有嵌套层的活动状态 |
| 无历史时行为 | 始终执行默认转移 | 回退到默认转移 | 回退到默认转移 |
| 图形表示 | 实心圆点+箭头 | 圆圈内写“H” | 圆圈内写“H*” |
案例拆解:设备模式切换中的历史恢复
考虑一个工业设备,具有“自动运行”父状态,内部包含“加工”、“检测”、“待机”三个子状态。设备在运行过程中可能因紧急停止而退出“自动运行”进入“急停”状态。当急停复位后,操作员希望设备恢复到急停前的工作阶段,而不是从头开始。
实现方案:在“自动运行”父状态内部添加一个浅历史节点,并将默认转移指向“待机”子状态。当设备从“自动运行”退出时,历史节点记录当前活动子状态(例如“加工”)。当设备从“急停”恢复到“自动运行”时,由于历史节点存在,系统直接恢复到“加工”子状态,而不是“待机”。如果设备是第一次进入“自动运行”(无历史),则默认进入“待机”。
该设计避免了在“急停”状态中保存和恢复大量中间变量,简化了逻辑。但需要注意:如果子状态内部还有更深层的嵌套,且需要恢复所有层级,则应使用深历史节点(H*)。
3.4 常见误区与总结复盘
误区一:混淆事件与条件
初学者常将事件和条件混为一谈。事件是“何时”触发转移的时机信号,条件是“是否”允许转移的逻辑判据。例如,E1 [x>0]表示事件E1发生时检查x>0,若为真则转移。如果写成[E1 && x>0],则E1被当作布尔变量使用,语义完全不同。正确做法是:事件放在方括号前,条件放在方括号内。
误区二:忽略转移优先级
当多个转移从同一状态出发且同时满足条件时,Stateflow按照转移的优先级顺序(从上到下、从左到右)依次判断。如果不显式设置优先级,可能导致非预期的转移路径。建议在复杂模型中,通过转移编号(在转移属性中设置Execution Order)明确指定优先级。
误区三:默认转移与历史节点冲突
默认转移只在“无历史记录”时生效。如果父状态已经有过活动历史,则历史节点优先于默认转移。有些设计者误以为默认转移每次都会执行,导致在恢复历史时出现逻辑错误。正确理解是:默认转移是“首次进入”的兜底方案,历史节点是“再次进入”的恢复方案。
误区四:子状态动作执行顺序错误
在嵌套状态中,动作执行顺序为:父状态entry → 子状态entry → 子状态during → 父状态during → 子状态exit → 父状态exit。如果设计者将父状态的初始化动作放在子状态中,可能导致初始化不完整或重复执行。建议将公共动作放在父状态,将特定动作放在子状态。
总结复盘
本章围绕状态与转移的核心概念展开。状态类型决定了模型的层级结构,普通状态承载具体行为,子状态实现逻辑分解;转移条件与事件触发决定了状态切换的时机与条件,需要精确设计以避免冲突;默认转移与历史节点解决了“初始进入”和“恢复进入”两种场景的路径选择问题。在实际建模中,建议先绘制状态图,明确状态层级和转移路径,再逐步添加事件、条件和动作。通过合理使用嵌套状态、历史节点和转移优先级,可以构建出结构清晰、行为准确的Stateflow模型。