4. 状态动作类型

在 Stateflow 中,状态不只是“处于某个位置”的静态标记,它还可以携带动作。动作决定了状态被激活、保持、退出或收到事件时,系统要执行什么逻辑。Stateflow 把状态动作按触发时机分成几类,最常用的是 entry、during、exit,以及 on event_name。理解它们的触发边界和执行次数,是避免状态机出现重复初始化、漏执行清理、事件响应错位等问题的关键。

先看一个典型的状态动作写法:

state MotorRun
  entry: speed_ref = 0; mode = 1;
  during: speed_ref = speed_ref + 1;
  exit: mode = 0; brake_flag = true;
  on OVERHEAT: fault = 1; shutdown();
end

这段代码中,entry 在进入 MotorRun 时执行一次,during 在状态保持激活的每个仿真步长或每个超级步中执行,exit 在离开该状态时执行一次,on OVERHEAT 则是在状态激活期间收到名为 OVERHEAT 的事件时执行。四类动作的触发条件、执行次数和适用场景完全不同,混用会直接改变状态机的行为。

核心判断原则:entry 管“进入时的一次性准备”,during 管“保持期间的持续行为”,exit 管“离开前的一次性收尾”,on event 管“激活期间对特定事件的即时响应”。

状态动作触发时机与执行次数 以 MotorRun 状态为例:进入 → 保持 → 事件响应 → 退出 时间 MotorRun 处于激活状态 进入 退出 entry 触发:进入状态瞬间 次数:每次进入 1 次 用途:初始化、置初值 示例:speed_ref = 0 during 触发:状态保持激活期间 次数:每个步长执行 1 次 用途:持续计算、监控 示例:speed_ref = speed_ref + 1 on event_name 触发:激活期间收到指定事件 次数:每次事件到达 1 次 用途:事件响应、故障处理 示例:on OVERHEAT: fault = 1 exit 触发:离开状态瞬间 次数:每次退出 1 次 用途:清理、复位、保存 示例:mode = 0 执行次数对比(以 10 个仿真步长、期间收到 2 次 OVERHEAT 为例) 动作类型 触发条件 执行次数 典型误用 entry 进入状态瞬间 1 次 把持续计算写进 entry during 每个步长保持激活 10 次 在 during 中做一次性初始化 on OVERHEAT 收到 OVERHEAT 事件 2 次 用 on 代替条件判断 exit 离开状态瞬间 1 次 忘记在 exit 中清理资源

从图中可以清楚看到,entry 和 exit 是“边界动作”,只在状态切换的瞬间各执行一次;during 是“驻留动作”,只要状态还处于激活状态,每个仿真步长都会执行;on event_name 是“事件动作”,只在状态激活期间、且指定事件到达时才执行。三者的执行次数差异,直接决定了它们适合承载什么逻辑。

entry:进入时的一次性初始化

entry 动作在状态被激活的那一刻执行,且只执行一次。它最适合做初始化工作,例如给输出变量赋初值、设置模式标志、启动定时器、记录进入时间戳等。以电机控制为例,进入 MotorRun 状态时把 speed_ref 清零、把 mode 置为 1,这是合理的 entry 用法。

常见误用是把需要持续更新的计算写进 entry。例如把 speed_ref = speed_ref + 1 写在 entry 中,那么它只在进入状态时加一次,之后不再变化,状态保持期间速度参考值就“卡住”了。这类错误在仿真中表现为:状态明明还在激活,但输出量不再更新,容易被误判为“状态机没跑起来”。

during:保持期间的持续行为

during 动作在状态保持激活的每个仿真步长中执行。它的执行次数与状态驻留时间成正比,适合做持续计算、周期监控、积分累加、超时计数等。例如在 MotorRun 中每个步长让 speed_ref 递增,或者判断 if temp > 80 then ...,都属于 during 的典型场景。

during 最常见的误用是“把一次性动作放进来”。比如在 during 中写 init_done = true;,由于每个步长都会执行,这个赋值会反复发生,虽然结果看似正确,但如果它后面还跟着 counter = counter + 1; 之类的语句,就会导致计数器被反复累加,产生与预期完全不同的数值。另一个误用是在 during 中执行耗时操作或调用有副作用的函数,导致每个步长都触发一次,仿真效率下降甚至逻辑错乱。

注意:during 的执行次数取决于仿真步长和状态驻留时间。在变步长求解器中,during 的调用次数并不等于固定步数,不能把它当作“固定周期任务”来设计。如果需要严格的周期行为,应结合 after、every 或外部函数调用子系统来处理。

exit:离开前的一次性收尾

exit 动作在状态被退出时执行一次,适合做清理、复位、保存现场、关闭使能等收尾工作。例如离开 MotorRun 时把 mode 置 0、把 brake_flag 置 true,或者把当前速度写入保持变量,都是合理的 exit 用法。

exit 的常见误用是“忘记写”或“写错位置”。如果状态退出时需要关闭某个使能信号,但该动作被写在 during 中,那么它会在状态保持期间反复执行,而真正退出时反而没有执行;如果写在 entry 中,则进入时就执行了,退出时不会触发。还有一种情况是把 exit 当作“状态结束后继续执行”的入口,实际上 exit 执行完毕后状态机已经离开该状态,后续逻辑应由目标状态的 entry 或转移条件承担。

on event_name:激活期间的事件响应

on event_name 动作在状态激活期间、收到指定事件时执行。它和转移上的事件触发不同:转移上的事件会引发状态切换,而状态内的 on 动作只执行动作本身,不改变状态。例如在 MotorRun 中写 on OVERHEAT: fault = 1; shutdown();,表示只要还在 MotorRun 状态且收到 OVERHEAT 事件,就置故障标志并执行关机,但状态本身不跳转。

on 动作的常见误用主要有三类。第一,把 on 当作“条件判断”使用,例如写 on temp > 80,这是不合法的,on 后面必须跟事件名,条件判断应放在 during 或转移条件中。第二,混淆 on 事件和转移事件,导致本应切换状态的事件被“吃掉”,状态机停在原地。第三,在 on 动作中执行与状态语义冲突的操作,例如在运行状态中收到停机事件却只置标志不退出状态,造成状态与输出不一致。

四类动作的配合与选择

实际建模时,四类动作往往配合使用。一个推荐的组织方式是:entry 负责“进入时把变量置为合法初值”,during 负责“保持期间按步长更新”,exit 负责“退出时把变量恢复到安全值或保存结果”,on 负责“对特定事件做即时响应”。判断某个动作该放哪里,可以问三个问题:它需要在进入时做一次吗?它需要在每个步长都做吗?它需要在退出时做一次吗?如果答案是“收到某个事件才做”,就放到 on 中。

动作类型触发时机执行次数适合承载典型误用
entry进入状态瞬间每次进入 1 次初始化、置初值、启动把持续计算写进 entry
during状态保持激活期间每个步长 1 次持续计算、监控、累加把一次性动作写进 during
exit离开状态瞬间每次退出 1 次清理、复位、保存忘记写或写到 during 中
on event_name激活期间收到指定事件每次事件 1 次事件响应、故障处理用 on 代替条件判断

最后需要强调执行顺序。在 Stateflow 的一个超级步中,通常先执行转移条件判断,再执行目标状态的 entry,然后执行 during,退出时执行 exit。如果同一状态同时存在 entry、during、exit 和 on 动作,它们的执行顺序和触发条件必须与状态机的语义一致。建议在建模完成后,用简单的计数器或日志变量验证每类动作的实际执行次数,例如在 entry 中写 entry_count = entry_count + 1;,在 during 中写 during_count = during_count + 1;,在 exit 中写 exit_count = exit_count + 1;,通过仿真观察这些计数是否符合预期。这是排查动作误用最直接、最有效的方法。