2. Stateflow建模语义与代码映射

Stateflow 的代码生成并不是“把图形翻译成 if-else”这么简单。它真正做的事情,是把一张具有层次、并发、事件驱动语义的状态图,按照确定的执行语义展开成可读、可验证的 C 代码。要能预测生成结果、定位生成代码中的问题,就必须先建立一套稳定的“图形元素 → 代码结构”映射心智模型。本节围绕状态、转移、条件、动作和层次结构五个核心语义对象,逐一说明它们在生成代码中的落点。

一、状态:从图形节点到状态变量与分支

Stateflow 中每个状态在生成代码里通常对应两样东西:一个用于记录“当前是否处于该状态”的布尔量(或位域成员),以及一段“进入该状态时执行”的分支代码。以常见的量产配置为例,状态活动性一般被组织进一个结构体,例如 Chart_DW.is_c1_Chart,其中每个状态对应一个枚举常量或位标志。判断当前状态时,生成代码不会去“读图”,而是读这个变量:

switch (Chart_DW.is_c1_Chart) {
  case IN_Idle:
    /* Idle 状态的 during 动作 */
    break;
  case IN_Running:
    /* Running 状态的 during 动作 */
    break;
  default:
    break;
}

这里有一个关键点:状态的 entry、during、exit 三类动作在代码中的位置完全不同。entry 动作只在状态被激活的那一拍执行一次,通常出现在状态切换的赋值之后;during 动作在状态保持期间每拍执行;exit 动作在离开该状态前执行。如果建模时把本该放在 entry 的初始化逻辑写进了 during,生成代码就会每拍重复执行,这在量产中往往表现为计数器被反复清零、标志位被反复覆盖。

二、转移:从箭头到条件判断与状态赋值

转移是 Stateflow 中最容易被低估的语义对象。一条带条件的转移,在生成代码中通常展开为“判断条件 → 执行转移动作 → 更新状态变量”三步。以 Idle --[start == 1]/{cnt = 0;}--> Running 为例,生成代码大致是:

if (Chart_DW.is_c1_Chart == IN_Idle) {
  if (start == 1) {
    cnt = 0;                       /* 转移动作 */
    Chart_DW.is_c1_Chart = IN_Running;  /* 状态更新 */
  }
}

需要特别注意的是默认转移和转移优先级。当多个转移从同一状态出发时,Stateflow 按图形上的编号顺序(即绘制顺序)自上而下、自左而右求值,生成代码也严格保持这个顺序。因此,把“无条件转移”画在“条件转移”之前,会导致条件分支永远不可达——这是量产代码中最隐蔽的逻辑错误之一。建议在建模规范中明确要求:条件转移在前,默认转移在后,并在评审时检查转移编号。

三、条件与动作:表达式如何进入 C 代码

条件(condition)在生成代码中直接成为 if 或 switch 的判断表达式,因此它必须满足两个工程约束:一是可确定性求值,不能依赖未初始化变量;二是避免副作用,条件中不应写赋值或函数调用。动作(action)则按语义分为转移动作、状态动作和图形函数调用。转移动作在状态更新之前执行,状态 entry 动作在状态更新之后执行,这个先后顺序直接决定了“先改数据还是先改状态”。

判断顺序口诀:条件求值 → 转移动作 → 状态变量更新 → 新状态 entry 动作 → 新状态 during 动作。任何一步写错位置,都会在时序相关的量产问题中暴露出来。

四、层次结构与并发:父子状态与并行域的代码形态

层次结构在生成代码中体现为嵌套的 switch 或嵌套 if。父状态的 during 动作先于子状态执行,子状态的 exit 先于父状态的 exit 执行,这是“由外向内进入、由内向外退出”的固定语义。而并行(AND)状态则不同:同一父状态下的多个并行子状态在同一拍内全部执行,生成代码中表现为顺序排列但互不构成 else 关系的多个分支。如果误把并行画成互斥,生成代码会多出 else,导致本应同时运行的两条支路只剩一条生效。

Stateflow 语义元素 → 生成 C 代码映射 图形语义 状态 State entry / during / exit 转移 Transition 条件 + 转移动作 条件 Condition 布尔表达式 动作 Action 赋值 / 函数调用 层次 / 并发 父-子 / AND 并行 生成 C 代码结构 状态变量:Chart_DW.is_c1_Chart = IN_xxx entry 在状态赋值后执行,during 每拍执行 if (条件) { 转移动作; 状态变量 = 目标状态; } 按转移编号顺序求值,默认转移最后 if / switch 判断表达式 条件中禁止副作用,保证可确定性求值 赋值语句 / 图形函数调用 转移动作先于状态更新,entry 动作后于状态更新 嵌套 switch / 顺序并列分支 父 during 先于子;并行域无 else,同拍全执行 执行顺序:条件求值 → 转移动作 → 状态变量更新 → 新状态 entry → 新状态 during

五、把映射关系落到工程检查上

理解了上述映射,就可以把它转化为可执行的建模与评审规则。第一,状态动作必须按语义归类:初始化放 entry,周期性计算放 during,资源释放或标志复位放 exit。第二,转移条件只做判断,不做赋值;需要“边判断边改数据”时,把赋值放进转移动作。第三,同一状态的多条转移必须显式排序,默认转移放在最后。第四,并行域与互斥分支在图上必须一眼可辨,避免评审时误读。

在量产项目中,这些规则最终会体现为代码评审清单和模型静态检查项。例如,可以用脚本扫描生成代码中是否存在“同一状态变量在 during 中被重复赋值”的模式,或检查 switch 分支是否缺少 default。这些检查之所以有效,正是因为图形语义与代码结构之间存在稳定、可预测的对应关系。掌握这套映射,是从“会画 Stateflow”走向“能交付量产代码”的关键一步。