3. 状态层次与并行机制

Stateflow 的状态不是一张平铺的清单,而是一棵可以嵌套的树。理解这棵树的结构规则,是读懂任何稍复杂状态机的第一步。本节围绕四个相互关联的问题展开:状态如何形成父子层次、互斥分解与并行分解有什么区别、进入一个父状态时哪些子状态会被激活、以及一个广播事件在层次结构中究竟按什么顺序被处理。

父状态与子状态:层次来自“包含”

在 Stateflow 中,把一个状态图形画在另一个状态图形内部,就建立了父子关系。外层是父状态(superstate),内层是子状态(substate)。父状态本身也是一个完整状态,它可以有自己的 entry、during、exit 动作,也可以有自己的转移;子状态则描述父状态“内部”更细的行为阶段。

层次带来的第一个直接好处是动作复用。假设一个“电机运行”父状态,其内部有“加速”“匀速”“减速”三个子状态。无论当前处于哪个子状态,只要还在“电机运行”内部,父状态的 during 动作都会执行。于是“每步刷新转速显示”这类公共行为只需写在父状态一次,而不必在三个子状态里各写一遍。同理,父状态的 entry 动作在进入该父状态时执行一次,exit 动作在离开整个父状态时执行一次,与中间经过了多少个子状态无关。

层次还带来转移的简化。从任意子状态回到“停止”状态,可以画一条从父状态“电机运行”出发的转移,而不必从每个子状态各画一条。这条转移的源是父状态,意味着“只要还在电机运行内部,条件满足就整体退出”。

互斥分解与并行分解

父状态内部的子状态有两种组织方式,由父状态的分解类型决定。

互斥分解(OR 分解)是默认方式:同一时刻父状态内只有一个子状态处于激活状态。子状态之间用普通转移连接,从一个子状态进入另一个子状态时,前者退出、后者进入。这适合描述“阶段互斥”的流程,例如“加速→匀速→减速”。

并行分解(AND 分解)则让父状态内的多个子状态同时激活。在 Stateflow 中,把父状态的分解类型设为 AND,其内部的多个区域(region)会并行运行。典型例子是一个“系统运行”父状态,内部并行放置“温度监控”和“压力监控”两个区域,两者各自维护自己的子状态,互不干扰地同时推进。

并行区域之间可以互相发事件、读共享数据,但不能用普通转移直接从一个区域跳到另一个区域——跨区域协作要靠事件广播或共享数据,而不是转移。这是初学者最容易出错的地方:试图在两个并行区域之间画一条转移,语义上是不成立的。

互斥分解(OR)与并行分解(AND)的结构对比 互斥分解 OR:同一时刻仅一个子状态激活 父状态:电机运行 子状态:加速 子状态:匀速 子状态:减速 条件满足 条件满足 并行分解 AND:多个区域同时激活 父状态:系统运行 区域A 温度监控 正常 过温 独立推进 区域B 压力监控 正常 超压 独立推进 两区域同时激活,靠事件/共享数据协作

状态激活顺序:默认转移决定入口

当父状态被进入时,它内部的哪个子状态会被激活?答案由默认转移决定。默认转移是一条没有源、只有目标的短箭头,从父状态的边界指向某个子状态,表示“进入本父状态时,默认先进入这里”。如果父状态是互斥分解,默认转移只能指向一个子状态;如果是并行分解,每个区域各有一条默认转移,进入父状态时所有区域的默认子状态同时激活。

激活顺序遵循“由外向内、逐层深入”的规则:先激活最外层父状态,执行其 entry 动作;再进入被默认转移指向的子状态,执行该子状态的 entry 动作;若该子状态还有嵌套,继续向内。退出时顺序相反,由内向外逐层执行 exit 动作。这个顺序直接决定了 entry/exit 动作的副作用次序,例如“先初始化外设再启动采集”还是相反,必须靠层次结构来保证。

并行区域的激活顺序则按区域在图形中的排列次序(通常是从左到右、从上到下)依次执行各自的默认转移。虽然它们“同时”处于激活状态,但 entry 动作的执行仍有先后,若两个区域的 entry 都写同一个共享变量,就要注意这个次序。

广播事件在层次结构中的传播

广播事件(broadcast event)是 Stateflow 中跨层次通信的主要手段。当某处执行 send(E) 或直接写事件名时,事件被放入队列,随后在层次结构中传播。传播遵循两条关键规则。

第一,事件沿层次向上冒泡,寻找能处理它的转移。若当前激活的子状态没有针对该事件的转移,事件会交给其父状态检查;父状态也没有,再向上。因此,把“急停”这类全局响应画在顶层父状态上,就能让任意深层子状态在收到急停事件时统一退出。

第二,同一层次内,先检查子状态,再检查父状态。也就是说,子状态对事件的响应优先于父状态。这允许子状态对某事件做特殊处理,而父状态只处理子状态未覆盖的情况。

下面这段示例展示了事件传播的典型写法。父状态“运行”上挂一条对 ESTOP 的转移,子状态“加速”上挂一条对 ESTOP 的转移,用于记录故障码后再退出:

% 父状态 运行 的转移
[ESTOP] -> 停止

% 子状态 加速 的转移(优先于父状态被检查)
[ESTOP] { faultCode = 3; } -> 停止

% 并行区域中广播事件
区域A: [temp > 85] { send(OVERHEAT); }
区域B: [OVERHEAT] -> 降载

需要注意,广播事件是异步排队的:send 只是把事件放入队列,当前步的动作会继续执行完,事件在后续步才被处理。因此不要假设“发出事件后立刻跳转”。若需要立即生效,应使用直接转移条件而不是广播事件。

判断一个状态机该用互斥还是并行,可以问一句:这些子行为是“轮流发生”还是“同时存在”?轮流发生用 OR,同时存在用 AND。若两个行为必须严格同步推进,优先考虑并行分解;若它们有明确的先后阶段,用互斥分解更清晰。
常见错误:在并行区域之间画转移。并行区域之间没有转移语义,跨区域协作只能通过广播事件或共享数据完成。另一个常见错误是忘记给并行区域设置默认转移,导致进入父状态时该区域没有激活的子状态,仿真会报“无默认转移”错误。

把本节四点串起来看:父子层次提供动作复用与转移简化,分解类型决定子状态是互斥还是并行,默认转移决定进入时的激活入口,而广播事件则沿着这棵层次树向上冒泡、由内向外寻找处理者。掌握这四条规则,就能在阅读或设计复杂状态机时,准确预测每一步到底发生了什么。