3、状态与转移详解:状态类型(普通、子状态)、转移条件、事件触发、默认转移与历史节点

在Stateflow中,状态(State)与转移(Transition)是构成有限状态机(FSM)的两大核心要素。状态定义了系统在某一时刻所处的稳定行为模式,而转移则描述了系统如何从一个状态切换到另一个状态。本章将深入剖析状态的类型划分(普通状态与子状态)、转移条件的构造方法、事件触发机制、默认转移的语义,以及历史节点(History Junction)这一高级特性。理解这些概念是设计健壮、可维护控制逻辑的基础,也是从“能画图”走向“会建模”的关键一步。

Stateflow 状态与转移核心语义图 父状态 A(含子状态) A1(普通) entry: 开阀 A2(普通) entry: 关阀 [压力>5] 默认转移 H 历史节点 状态 B(普通) entry: 计数清零 during: 计数++ exit: 保存数据 状态 C(普通) entry: 报警置位 exit: 报警复位 E1 [条件] E2 [x>10] E3 [复位] 触发机制 • 事件触发:E1, E2, E3 • 条件触发:[压力>5] • 无条件转移:always • 超时触发:after(5, tick) • 输入事件:E1_Input • 输出事件:E1_Output 转移标签语法: event [condition] {action} 例如:E1 [x>0] {y=1;} 图例 父状态(含子状态) 普通状态 历史节点 默认转移决定首次进入父状态时的初始子状态;历史节点则记录上次退出时的活动子状态

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。这种嵌套设计使得每个子状态只关注自身的速度调节逻辑,而父状态统一管理驱动器的使能与禁用,避免了重复代码。

经验提醒:在建模时,优先将公共动作(如硬件初始化、资源申请)放在父状态的entry中,将公共清理动作(如资源释放、硬件复位)放在父状态的exit中。这样既保证了动作的唯一性,又避免了在多个子状态中重复编写相同代码。

3.2 转移条件与事件触发

核心原理

转移(Transition)定义了状态之间的切换路径。一条完整的转移由触发事件(Event)、条件(Condition)和转移动作(Transition Action)三部分组成,其标准语法为:event [condition] {transition_action}。事件是触发转移的“信号”,可以是Stateflow内部定义的输入事件、输出事件,也可以是本地事件;条件是一个布尔表达式,只有事件发生且条件为真时,转移才被激活;转移动作在转移被激活后、目标状态entry动作执行前执行。

事件触发分为多种类型:隐式事件(如entry、exit、during)由状态自身行为触发;显式事件由外部输入或内部逻辑显式广播;超时事件(after(n, tick))基于仿真时钟或事件计数触发;输入事件由Simulink信号边沿触发。条件则支持关系运算、逻辑运算、算术运算以及状态自身数据的访问。

操作流程:构造转移条件

  1. 确定触发源:分析系统需求,确定转移是由外部信号(如传感器值变化)、内部定时(如超时)还是逻辑判断(如状态完成)触发。
  2. 定义事件:在Stateflow中创建输入事件(从Simulink输入端口接收)或本地事件(内部广播)。
  3. 编写条件表达式:使用状态数据、输入数据、常量等构造布尔表达式。例如:[temperature > 85 && fan_speed < 2000]。
  4. 设置转移动作:在转移标签的{}中编写赋值、函数调用等动作。例如:{error_count = 0;}。
  5. 处理优先级:当多个转移同时满足条件时,需要设置转移优先级(通过转移顺序编号),避免冲突。

案例拆解:温度控制系统的转移设计

设计一个温度控制系统,包含“加热”、“保温”、“冷却”三个状态。输入事件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模型。

实战要点:在模型评审时,重点检查以下内容:1)每个父状态是否都有默认转移;2)是否存在无事件触发的条件转移(可能导致每个时间步都判断);3)历史节点的使用是否符合需求;4)转移动作是否修改了条件中使用的变量(可能导致转移条件不稳定)。通过系统化检查,可以显著减少逻辑缺陷。