2. Stateflow核心语义与执行模型

Stateflow 的语义核心可以概括为一句话:在超步(Super Step)中,按层级与优先级反复扫描有效转移,直到系统进入稳定状态为止。理解这句话,需要先分清几个基本对象各自的职责,再理解它们在一次超步中如何协同。

一、状态:活动与层级

状态(State)是 Stateflow 中最基本的驻留单元,它表示系统在某一时刻所处的模式。每个状态有“活动(active)”与“非活动(inactive)”两种基本属性。一个父状态被激活时,并不意味着它的所有子状态都被激活——子状态中必须有一个被“默认转移”或显式转移选中,才能成为活动状态。

状态可以嵌套,形成父子层级。层级带来两个直接后果:其一,父状态的 entry、during、exit 动作会包裹子状态的生命周期;其二,转移的搜索范围受层级约束,内层转移优先于外层转移。常见的动作类型包括:

  • entry:进入该状态时执行一次;
  • during:状态处于活动期间,每个超步执行一次;
  • exit:离开该状态时执行一次;
  • on event_name:该状态活动期间,指定事件发生时执行。

例如一个电机控制状态 Running,其 entry: speed = 0; 表示进入运行态时先把速度清零,during: speed = speed + 1; 表示每个超步递增一次,exit: brake = 1; 表示退出时置刹车标志。这些动作的执行时机由超步机制严格决定,而不是由仿真时间随意决定。

二、转移、条件与动作

转移(Transition)是状态之间的有向连接,其完整语法为:

源状态 --> 目标状态
[条件表达式] / {条件动作}
{转移动作}

其中方括号内是条件(Condition),斜杠后是条件动作(Condition Action),花括号内是转移动作(Transition Action)。三者的执行顺序是:先判断条件,条件为真时先执行条件动作,再执行转移动作,最后进入目标状态并执行其 entry 动作。条件动作与转移动作的区别在于:条件动作在条件成立后立即执行,而转移动作在真正完成状态切换时执行;当存在多个候选转移时,这个区别会影响副作用发生的次数。

转移上还可以标注事件名,如 e_start[enable == 1],表示只有事件 e_start 发生且条件成立时,该转移才被考虑。没有事件标注的转移称为“无条件转移”,它在每个超步中都会被评估。

三、默认转移与历史节点

默认转移(Default Transition)解决“父状态刚被激活时,从哪个子状态开始”的问题。它没有源状态,只有目标状态,通常画成一条从父状态边界指向某个子状态的短箭头。若父状态有多个子状态而没有默认转移,模型在激活父状态时会因无法确定初始子状态而报错。

历史节点(History Junction)解决“重新进入父状态时,回到上次离开的子状态”的问题。它分为浅历史(Shallow History,记作 H)和深历史(Deep History,记作 H*)。浅历史只记住父状态直接子层中最后活动的那个子状态;深历史则记住整个子树中最后活动的叶状态。历史节点必须配合默认转移使用:首次进入时走默认转移,之后再次进入时走历史节点记录的状态。

四、超步执行机制

超步是 Stateflow 执行模型的时间单位。一个超步内,仿真器会反复执行“评估转移—执行转移—进入新状态”的循环,直到没有任何有效转移可以触发,系统进入稳定配置,这个超步才结束。下一个超步再重新评估所有转移。这意味着:同一超步内可以连续发生多次状态切换,而 during 动作在每个超步中最多执行一次。

转移的评估遵循明确的优先级:同一层级内,按图形上从左到右、从上到下的顺序;内层转移优先于外层转移;带事件的转移优先于无条件转移。理解优先级,才能预测当多个条件同时成立时,模型究竟走哪条路径。

Stateflow 超步执行与状态层级语义 父状态 MotorCtrl entry: brake=0; during: tick=tick+1; exit: brake=1; 默认转移 Idle entry: speed=0; Running during: speed=speed+1; e_start[enable==1] / {cmd=1} e_stop / {cmd=0} H 浅历史节点 重新进入时恢复上次子状态 Fault entry: brake=1; [speed>200] 超步(Super Step)循环 ① 评估所有有效转移 按层级与优先级排序 ② 执行条件动作 条件成立后立即执行 ③ 执行转移动作 退出源状态、进入目标状态 ④ 执行目标 entry 动作 并重新评估转移 无有效转移 → 稳定,超步结束 同一超步内可多次切换

五、一次超步的完整推演

假设初始时 MotorCtrl 处于 Idle,enable = 1,speed = 0,tick = 0。在某个超步中发生事件 e_start:

  1. 评估 Idle → Running 转移,条件 enable == 1 成立;
  2. 执行条件动作 cmd = 1;
  3. 执行转移动作(本例为空),退出 Idle,执行 Idle.exit(若有);
  4. 进入 Running,执行 Running.entry(若有);
  5. 重新评估转移,此时 e_stop 未发生、speed > 200 不成立,无有效转移;
  6. 执行 Running.during,speed = speed + 1 = 1,tick = tick + 1 = 1;
  7. 超步结束,系统稳定在 Running。

若在后续某个超步中 speed 累加到 201,则 [speed > 200] 成立,系统在同一超步内从 Running 切换到 Fault,执行 Fault.entry: brake = 1,随后再次评估转移,若无其他有效转移则超步结束。此时 Running.during 在该超步中不会执行,因为 Running 已不再是活动状态。

判断依据:观察一个 Stateflow 模型是否按预期运行时,重点看三处——活动状态配置是否与预期一致、during 动作在每个超步中是否只执行一次、以及同一超步内是否发生了多次状态切换。若 during 动作执行次数与超步数不符,通常说明转移条件或优先级设置有误。

六、常见语义陷阱

第一,把条件动作与转移动作混用。条件动作在条件成立后立即执行,即使后续因优先级更高而改走其他转移,条件动作的副作用也已经发生。若希望副作用只在真正切换时发生,应使用转移动作。

第二,忽略默认转移。父状态有多个子状态却未定义默认转移,模型在激活父状态时会报“无法确定初始子状态”。

第三,历史节点与默认转移的配合。历史节点只负责“记住”,首次进入仍需要默认转移来指定初始子状态;若只画历史节点而不画默认转移,首次进入时同样会报错。

第四,超步内多次切换导致的动作重复。若一个超步内发生 A→B→C 两次切换,则 A 的 exit、B 的 entry 和 exit、C 的 entry 都会执行,而 B 的 during 不会执行。设计动作时需考虑这种“过路状态”的副作用。

掌握以上语义后,就能在建模时准确预测每个超步中哪些动作会执行、执行几次,以及系统最终稳定在哪个状态配置上。这是后续进行事件驱动逻辑设计、调试与验证的基础。