4、状态图核心概念:状态、转移、动作(entry/during/exit)、条件与事件、转移优先级

Stateflow 是 MathWorks 公司推出的基于有限状态机(FSM)的图形化建模工具,它与 Simulink 深度集成,用于描述复杂系统的控制逻辑、调度策略和模式切换行为。在联合开发中,状态图(Stateflow Chart)是承载逻辑的核心载体。要真正掌握 Stateflow,不能只停留在“拖拽方块、连线”的操作层面,而必须深刻理解其底层的形式化语义:状态(State)、转移(Transition)、动作(Action)、条件(Condition)、事件(Event)以及转移优先级(Transition Priority)。本章将围绕这六大核心概念展开,从原理到实战,帮助您建立扎实的 Stateflow 建模思维。

Stateflow 核心概念关系图 状态 (State) on/off, 模式A/B 转移 (Transition) 连接状态的有向弧 动作 (Action) entry/during/exit 条件与事件 [条件] 事件名 转移优先级 从上到下, 从外到内 执行顺序: 条件判断 → 事件触发 → 转移 → 动作执行 核心要点 状态是“记忆”,转移是“路径”,动作是“行为”,条件是“门槛”,事件是“触发器”,优先级是“仲裁规则” 六者协同工作,构成完整的有限状态机语义

上图展示了 Stateflow 六大核心概念之间的逻辑关系。状态是系统在某一时刻的“记忆”或“模式”;转移是状态之间的有向连接,定义了系统如何从一个状态切换到另一个状态;动作则附着在状态或转移上,描述进入状态、处于状态期间以及离开状态时执行的具体行为;条件与事件是触发转移的“门槛”和“扳机”;而转移优先级则决定了当多个转移同时满足条件时,系统按照何种规则进行仲裁。下面我们逐一深入剖析。

4.1 状态(State):系统的“记忆单元”

核心原理:状态是 Stateflow 中最基本的构建块,它代表系统在特定时刻所处的模式或条件。在有限状态机理论中,状态是系统对过去输入历史的“记忆”。例如,一个电机控制系统可以具有“停止”、“加速”、“匀速”、“减速”四个状态。状态具有“激活”(active)和“非激活”(inactive)两种属性,同一时刻,在同一个父状态(父级状态)下,只能有一个子状态处于激活状态(除非使用并行分解,将在后续章节讨论)。

判断方法与操作流程:在 Stateflow 编辑器中,状态用圆角矩形表示。创建状态时,需要定义状态名称(必须唯一)以及状态动作。状态动作分为三类:entry(进入动作)、during(持续动作)、exit(退出动作)。这些动作在状态的生命周期内按特定顺序执行。

动作类型触发时机典型应用场景示例代码
entry状态被激活的瞬间(仅执行一次)初始化变量、置位标志位、开启设备entry: motor_on = 1;
during状态保持激活期间,每个仿真步长执行一次实时计算、PID控制、数据采集during: speed = speed + accel*dt;
exit状态即将被切换离开时(仅执行一次)保存数据、关闭设备、复位标志位exit: motor_on = 0;

实战场景:以汽车车窗升降系统为例。假设有“上升中”、“下降中”、“停止”三个状态。在“上升中”状态的 entry 动作中,我们给电机驱动信号置高电平;在 during 动作中,持续检测车窗位置传感器,并计算剩余行程;在 exit 动作中,将电机驱动信号置低电平,防止误动作。这种设计确保了无论从哪个状态切换到“上升中”,电机都能被正确启动;无论切换到哪个状态,电机都能被安全停止。

常见误区:初学者常将 during 动作与 Simulink 中的连续积分混淆。注意,during 动作在每个仿真步长执行一次,但它是离散事件语义,不涉及连续时间积分。如果需要在状态内进行连续动态计算,应使用 Simulink 中的连续模块,并通过 Stateflow 进行模式切换控制。

4.2 转移(Transition):状态之间的“桥梁”

核心原理:转移定义了系统从一个状态切换到另一个状态的路径。一个完整的转移由源状态(Source)、目标状态(Destination)以及转移标签(Label)组成。转移标签的语法格式为:event[condition]{condition_action}/transition_action。其中,事件和条件可以省略,但转移动作和条件动作是可选的。

判断方法与操作流程:在绘制转移时,需要注意以下几点:

  1. 转移方向:箭头指向目标状态,表示系统从源状态流向目标状态。
  2. 自环转移:允许从状态指向自身的转移,用于实现状态内的重新初始化或条件判断。
  3. 默认转移:每个父状态必须有一个默认转移(带实心圆点的箭头),用于指定进入父状态时首先激活哪个子状态。
  4. 转移标签书写:事件名写在方括号之前,条件写在方括号内,条件动作写在花括号内,转移动作以“/”开头。
// 转移标签示例
E1[temp > 100]{count++} / alarm_on = 1;
// 含义:当事件E1发生,且temp大于100时,执行count自增,然后转移到目标状态,并置alarm_on为1

经验提醒:转移动作(/ 后面的部分)在转移“被确认有效”之后、目标状态 entry 动作之前执行。而条件动作({} 中的部分)在条件判断为真时立即执行,但此时转移尚未最终确定。理解这个执行顺序对于避免逻辑错误至关重要。

4.3 动作(Action):行为的“执行者”

核心原理:动作是 Stateflow 中执行具体行为的代码片段。除了状态动作(entry/during/exit)外,还有转移动作和条件动作。动作可以调用 MATLAB 函数、Simulink 函数、C 代码(通过 Stateflow API)或执行简单的赋值和逻辑运算。

操作流程与规范:编写动作时,应遵循以下原则:

  • 原子性:动作应尽量短小精悍,避免在动作中执行耗时操作(如文件读写、复杂循环),否则会拖慢仿真速度。
  • 可读性:使用有意义的变量名,并添加必要的注释。
  • 层次性:动作可以访问父状态和子状态的变量,但要注意变量的作用域。
  • 类型安全:确保动作中使用的变量类型与定义一致,避免隐式类型转换带来的精度损失。
动作类型执行顺序(相对于转移)执行次数
条件动作 {}条件为真时立即执行,在转移确认前最多一次
转移动作 /转移确认后,目标状态 entry 之前最多一次
源状态 exit转移确认后,源状态退出时一次
目标状态 entry目标状态激活时一次
目标状态 during目标状态激活期间每个步长多次

案例拆解:考虑一个电池充电管理系统。状态“恒流充电”的 during 动作中,我们计算当前充电电流并更新累计充电容量;当电池电压达到阈值时,触发转移条件 [V_bat >= 4.2],转移动作 / charge_mode = 2; 将充电模式切换为恒压充电。在“恒压充电”状态的 entry 动作中,我们重新设定 PWM 占空比初始值。整个逻辑通过动作的合理分配,实现了充电阶段的平滑切换。

4.4 条件与事件:转移的“门槛”与“扳机”

核心原理:事件(Event)是 Stateflow 中的“触发器”,它可以是外部输入事件(来自 Simulink 的触发端口)、内部事件(由 Stateflow 内部逻辑产生)或时间事件(基于时间、相对时间或时序逻辑)。条件(Condition)是布尔表达式,只有条件为真时,转移才可能被触发。事件和条件可以组合使用,也可以单独使用。

判断方法与操作流程:

  1. 事件类型选择:如果转移需要由外部信号上升沿触发,使用“边沿触发事件”;如果需要周期性触发,使用“时间事件”(如 after(5, tick) 表示 5 个 tick 后触发)。
  2. 条件书写:条件表达式支持关系运算符(>, <, ==, !=)、逻辑运算符(&&, ||, !)以及 MATLAB 函数调用。
  3. 无事件转移:如果省略事件,则只要条件为真,且源状态处于激活状态,转移就会在每个仿真步长被评估。
  4. 事件广播:可以使用 send(event_name) 函数向其他状态或 Simulink 发送事件。
// 时间事件示例
after(10, sec)  // 10秒后触发
after(5, tick)  // 5个仿真步长后触发
every(2, sec)   // 每2秒周期性触发

// 条件与事件组合
E1[count > 5 && mode == 2]

常见误区:初学者容易混淆“无事件转移”和“条件为真即转移”的概念。在 Stateflow 中,如果没有指定事件,转移在每个时间步都会被评估;如果指定了事件,则只有事件发生时才会评估条件。此外,条件动作 {} 中的代码在条件为真时立即执行,但此时转移尚未最终确定,如果后续转移被更高优先级的转移抢占,条件动作的执行结果可能不会生效。

4.5 转移优先级:冲突的“仲裁规则”

核心原理:当多个转移同时满足条件(或同时被事件触发)时,Stateflow 需要确定执行哪个转移。转移优先级规则如下:

  • 父级优先:如果存在从父状态出发的转移和从子状态出发的转移,且两者都有效,则父状态的转移优先执行(即“外部转移优先”)。
  • 顺序优先:对于同一源状态的多个转移,按照它们在图形中从上到下、从左到右的顺序进行判断,先满足条件的转移被执行。
  • 非互斥性:如果两个转移的源状态和目标状态不同,且没有重叠,则它们可以同时执行(在并行分解中)。

操作流程与判断方法:在绘制状态图时,应仔细规划转移的布局顺序。将最紧急或最优先的转移放在最上方或最左侧。同时,可以利用“转移优先级”的概念来设计防抖逻辑或安全保护逻辑。

场景转移A转移B实际执行
同一源状态E1[cond1]E1[cond2]先判断cond1,若为真执行A;否则判断cond2
父子状态父状态转移子状态转移父状态转移优先执行
不同源状态状态A→C状态B→D若A和B都激活,则两个转移都可能执行

实战案例:设计一个工业机械臂的急停逻辑。机械臂有“自动运行”、“手动调试”、“暂停”三个状态。急停信号 E_stop 是一个高优先级事件。我们在父状态“运行模式”上绘制一个转移:E_stop / power_off = 1; 指向“急停”状态。同时,在“自动运行”子状态上也有一个转移:E_stop / alarm = 1; 指向“急停”状态。根据“父级优先”规则,当急停事件发生时,父状态的转移会优先执行,从而确保电源被立即切断,而子状态的报警动作可能不会执行。这个设计避免了逻辑冲突,保证了安全。

经验总结:转移优先级是 Stateflow 建模中最容易出错的地方之一。建议在模型设计阶段就绘制出状态转移表,明确每个转移的源状态、目标状态、触发事件、条件、动作以及优先级顺序。在代码生成后,通过 Simulink 的“逻辑覆盖度”工具检查是否有不可达的转移或冗余的优先级设置。

4.6 综合案例:电梯门控制系统

为了将上述概念融会贯通,我们设计一个简化的电梯门控制系统。系统包含“开门”、“开门保持”、“关门”、“关门受阻”四个状态。输入信号包括:开门按钮(btn_open)、关门按钮(btn_close)、门位置传感器(pos_sensor,0表示全关,1表示全开)、光幕传感器(light_curtain,1表示有人或物阻挡)。

状态定义与动作:

  • 开门:entry: motor_dir = 1; motor_on = 1;;during: if(pos_sensor >= 0.99) send(open_done);;exit: motor_on = 0;
  • 开门保持:entry: hold_timer = 0;;during: hold_timer = hold_timer + 1; if(hold_timer >= 50) send(hold_timeout);
  • 关门:entry: motor_dir = 0; motor_on = 1;;during: if(pos_sensor <= 0.01) send(close_done);;exit: motor_on = 0;
  • 关门受阻:entry: motor_on = 0; alarm = 1;;during: if(light_curtain == 0) send(obstacle_clear);;exit: alarm = 0;

转移定义与优先级:

  1. 默认转移 → 开门
  2. 开门 → 开门保持:open_done(事件)
  3. 开门保持 → 关门:hold_timeout(事件)
  4. 关门 → 开门:btn_open(事件)
  5. 关门 → 关门受阻:light_curtain == 1(条件,无事件)
  6. 关门受阻 → 开门:obstacle_clear(事件)
  7. 关门受阻 → 关门:btn_close(事件)

优先级分析:在“关门”状态下,如果同时发生 btn_open 事件和光幕被遮挡(条件为真),则转移4(关门→开门)和转移5(关门→关门受阻)都有效。根据“从上到下”的规则,转移4位于转移5的上方,因此优先执行“开门”转移。这符合安全逻辑:即使有人阻挡,只要按下开门按钮,就应该优先开门。

结果与复盘:通过这个案例,我们看到了状态动作如何管理硬件输出,事件如何驱动状态切换,条件如何实现实时监测,以及优先级如何解决冲突。在实际工程中,还需要考虑传感器噪声、通信延迟等因素,可以通过增加“去抖”状态或使用 Simulink 中的滤波模块来优化。

风险提示:在状态图中使用时间事件(如 after(50, tick))时,要注意仿真步长的影响。如果仿真步长是可变的,时间事件的触发精度会受到影响。建议在固定步长求解器下使用时间事件,或者使用 after(50, sec) 并确保采样时间足够小。