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求解器交互的计算封装为可调用单元。
真值表函数用条件-动作表描述组合逻辑,每一行是一个条件组合,对应一个动作输出。真值表在语义上等价于图形函数中的条件分支,但表达更紧凑,尤其适合条件维度多、逻辑规则明确的场景,例如安全联锁、模式选择。真值表的执行是同步的,且条件按行顺序求值,第一行匹配即执行,因此行序即优先级。
复用策略:从“复制粘贴”到“契约化封装”
四类函数都可以被同一张状态图内的多个状态调用,也可以被不同模型引用。复用的第一步是接口契约化:明确函数的输入、输出、数据类型、采样时间和作用域。例如,一个用于“故障确认”的图形函数,输入为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导致每次调用都执行多次;真值表行序错误导致优先级不符合预期;原子子状态图未设置“原子”属性,导致父图意外干预内部状态。
最后,复用不是目的,可验证性才是。无论选择哪种函数,都应保证函数可以被独立测试:输入明确、输出可观测、内部状态可复位。这样,当模型规模增长时,函数级测试用例可以持续运行,成为回归验证的稳定基线。