4. Stateflow函数与复用

在Stateflow中,函数不只是“写一段逻辑”的容器,它决定了逻辑放在哪里执行、由谁调用、能否被复用,以及生成代码时的边界。Stateflow提供四类可调用函数:图形函数(Graphical Function)、MATLAB函数、Simulink函数和真值表函数。它们共享同一套调用语法,但执行语义、调试方式和复用能力差异很大。理解这些差异,是构建可维护、可测试模型的前提。

四类函数的执行语义与适用边界

图形函数使用状态图自身的动作语言(如during、entry、转移条件)实现逻辑,内部可以包含状态、转移和局部数据。它由Stateflow求解器直接执行,不依赖MATLAB解释器,因此适合表达带状态记忆、分支判断或时序依赖的算法。图形函数的调用是同步的,调用者会等待其返回。典型场景是把一段复杂的条件分支封装成可复用的“子逻辑”,例如故障确认、模式仲裁。

MATLAB函数在函数体内写MATLAB代码,由MATLAB引擎执行。它适合矩阵运算、查表、复杂数值计算,以及需要调用MATLAB内置函数的场合。需要注意的是,MATLAB函数在仿真时逐次调用MATLAB解释器,执行开销高于图形函数;在代码生成时,若函数体只使用支持代码生成的子集,可以生成C代码,否则只能用于仿真。因此,MATLAB函数更适合算法原型验证和仿真阶段的快速迭代。

Simulink函数通过Simulink Function块定义,函数体是Simulink子系统。它把Simulink的连续/离散动态、积分器、传递函数等能力引入Stateflow调用链。调用Simulink函数时,实际执行的是对应的Simulink子系统,因此可以包含连续状态。它适合把控制律、被控对象模型或需要与Simulink求解器交互的计算封装为可调用单元。

真值表函数用条件-动作表描述组合逻辑,每一行是一个条件组合,对应一个动作输出。真值表在语义上等价于图形函数中的条件分支,但表达更紧凑,尤其适合条件维度多、逻辑规则明确的场景,例如安全联锁、模式选择。真值表的执行是同步的,且条件按行顺序求值,第一行匹配即执行,因此行序即优先级。

Stateflow 四类函数与复用封装关系 调用者 状态 / 转移 / 其他函数 图形函数 状态/转移/局部数据 Stateflow求解器执行 MATLAB函数 MATLAB代码/矩阵运算 MATLAB引擎执行 Simulink函数 Simulink子系统 含连续/离散动态 真值表函数 条件-动作表 行序即优先级 复用与封装策略 原子子状态图封装 独立执行、独立测试、可生成函数 库化复用 放入库模型,多模型引用同一函数 接口契约 输入/输出/数据类型/采样时间固定 测试隔离 函数级测试用例,独立于父图 调用语法统一:函数名(实参) → 返回值;差异在执行引擎、状态能力与代码生成支持

复用策略:从“复制粘贴”到“契约化封装”

四类函数都可以被同一张状态图内的多个状态调用,也可以被不同模型引用。复用的第一步是接口契约化:明确函数的输入、输出、数据类型、采样时间和作用域。例如,一个用于“故障确认”的图形函数,输入为faultRaw(boolean)、confirmTime(double,单位s),输出为faultConfirmed(boolean),内部使用局部变量timer计数。只要契约不变,函数体如何修改都不影响调用者。

第二步是库化。把函数所在的Stateflow图或Simulink Function块放入一个库模型(.slx库),其他模型通过库链接引用。这样,函数逻辑只有一份,修改库后所有引用模型同步更新。对于Simulink函数,还可以通过Simulink Function块的“函数原型”定义全局可见的函数,实现跨模型调用。

第三步是原子子状态图封装。原子子状态图(Atomic Subchart)是把一个子状态图当作原子单元执行:进入时执行入口动作,执行期间内部状态正常迁移,退出时执行出口动作,但父图不会看到其内部状态。它适合把一段完整的行为封装为可复用组件,并支持独立的测试用例。原子子状态图可以生成独立的函数,便于代码生成时的模块化。

真值表与状态图的配合

真值表函数适合表达组合逻辑,而状态图适合表达时序逻辑。两者配合的典型模式是:状态图负责“什么时候判断”,真值表负责“判断结果是什么”。例如,一个电池管理状态图在Monitoring状态中调用真值表函数evaluateSafety(voltage, current, temp),真值表根据三个输入的条件组合输出safe、warn或fault。状态图再根据返回值决定是否转移到Fault状态。这样,安全规则集中在真值表中,便于审查和修改,而状态迁移逻辑保持清晰。

实操示例:封装一个可复用的故障确认函数

下面以图形函数为例,给出一个可复用的故障确认函数定义。假设采样周期Ts = 0.01s,确认时间confirmTime = 0.5s,即需要连续50个采样周期故障原始信号为真才确认。

图形函数定义(在Stateflow中):
函数名:confirmFault
输入:faultRaw : boolean
输入:confirmTime : double
输出:faultConfirmed : boolean
局部数据:count : uint16

函数体(图形化):
[entry] count = 0;
[during]
  if faultRaw == true
    count = count + 1;
  else
    count = 0;
  end
  if count >= confirmTime / 0.01
    faultConfirmed = true;
  else
    faultConfirmed = false;
  end

调用方式:在状态Monitoring的during动作中写faultConfirmed = confirmFault(faultRaw, 0.5);。这样,故障确认逻辑只写一次,任何需要该逻辑的状态都可以调用。若后续确认时间需要调整,只改调用处的实参或函数默认值,不必修改函数体。

图形函数中的局部变量count在每次调用时保持其值,因为图形函数是同步执行的,且局部数据在函数调用期间持续存在。这与MATLAB函数不同:MATLAB函数每次调用重新初始化局部变量,除非使用persistent声明。

选择建议与常见陷阱

选择函数类型时,可以按以下顺序判断:需要状态记忆或时序分支,优先图形函数;需要矩阵运算或MATLAB内置函数,用MATLAB函数;需要连续动态或Simulink子系统,用Simulink函数;条件组合多且规则明确,用真值表。常见陷阱包括:在MATLAB函数中使用了不支持代码生成的函数,导致无法生成C代码;图形函数中误用during导致每次调用都执行多次;真值表行序错误导致优先级不符合预期;原子子状态图未设置“原子”属性,导致父图意外干预内部状态。

最后,复用不是目的,可验证性才是。无论选择哪种函数,都应保证函数可以被独立测试:输入明确、输出可观测、内部状态可复位。这样,当模型规模增长时,函数级测试用例可以持续运行,成为回归验证的稳定基线。