2. Stateflow建模基础回顾

Stateflow 的本质是一台可执行的有限状态机,它把控制逻辑拆成“状态 + 转移”两张皮:状态负责记住系统当前处于什么工况,转移负责描述工况之间如何切换。量产代码开发中,Stateflow 最终会被自动代码生成工具翻译成 C 代码,因此建模阶段每一个元素都必须有明确、可判定的语义,否则生成的代码会出现不可预期的分支或死循环。

状态:系统的记忆单元

状态(State)表示系统在某一时刻所处的工况。每个状态都有激活与非激活两种属性,同一时刻同一层级内通常只有一个状态处于激活态(互斥 OR 分解)。状态可以携带三类动作:entry(进入时执行一次)、during(激活期间每个执行步执行)、exit(退出时执行一次)。量产建模中,during 动作要格外谨慎,因为它会在每个采样周期重复执行,若其中包含计数器累加或赋值,必须确认其重复执行是否符合控制意图。

状态还可以定义默认转移(Default Transition),用于指定父状态首次被激活时进入哪个子状态。缺少默认转移的父状态在激活后可能停留在父层而不进入任何子状态,这在量产模型中属于典型缺陷。

转移、事件、条件与动作

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

事件[条件]{条件动作}/转移动作

四部分均可省略,但至少要有一个触发源(事件或条件),否则该转移会在每个执行步无条件触发,形成“自激”路径。量产规范通常要求:转移必须显式写出条件,禁止仅靠事件触发而不加条件判断,以避免事件丢失或重复触发导致的逻辑抖动。

事件(Event)分为输入事件、本地事件和输出事件。输入事件由外部信号驱动,本地事件由模型内部广播,输出事件用于通知外部。事件可以是边沿触发(如 rise、fall、either),也可以是函数调用触发。量产建模中,事件命名应体现信号来源与极性,例如 ev_IgnOn_rise,避免使用 e1、event1 这类无意义名称。

条件(Condition)是布尔表达式,只有为真时转移才可能发生。条件中引用的变量必须来自数据字典或输入端口,禁止在条件中直接写魔法数字。动作(Action)分为条件动作和转移动作:条件动作在条件成立、转移被选中但尚未离开源状态时执行;转移动作在离开源状态、进入目标状态的过程中执行。两者执行时机不同,量产中若涉及输出赋值,必须明确放在哪一侧。

层次结构与并行状态

Stateflow 支持父子层次(Superstate/Substate)和并行(AND)分解。层次结构用于把公共转移和公共动作上提到父状态,减少重复;并行状态用于表达同时激活的多个独立子系统,例如“电机控制”与“故障监控”可以并行运行,各自维护自己的状态机。

并行状态之间通过虚线分隔,每个并行区域内部仍然是互斥的 OR 状态。量产建模中,并行区域的数量不宜过多,通常建议不超过 3~4 个,否则生成代码的可读性和可测试性会明显下降。层次深度同样建议控制在 3 层以内,过深的嵌套会让状态转移路径难以追踪。

Stateflow 层次结构与并行状态示意 父状态:Ctrl_Main entry: sys_init(); during: watchdog_feed(); 默认转移 并行分解(AND) 区域A:Motor_Ctrl(互斥 OR) Idle entry: pwm=0 Run during: pwm=calc() Fault entry: pwm=0 [start && !fault] [fault] [reset] 区域B:Fault_Monitor(互斥 OR) Normal during: chk_sensor() Alarm entry: set_dtc() [sensor_err] [clear] 区域A与区域B同时激活,各自独立转移;父状态动作在每个执行步统一执行

量产建模常用规范

量产 Stateflow 模型与教学模型最大的区别在于:它必须可生成、可追溯、可测试。以下规范在多数整车厂和 Tier1 的建模指南中反复出现。

  • 命名规范:状态名使用大驼峰或下划线分隔,如 Motor_Run;事件名体现来源与极性,如 ev_BrakePressed_rise;数据名与信号矩阵保持一致,禁止使用 tmp、flag1 等临时名。
  • 转移条件:禁止空条件转移;禁止在条件中使用 == 比较浮点数;条件表达式长度建议不超过 80 字符,过长时拆分为中间变量。
  • 动作规范:禁止在 during 中写阻塞式循环;禁止在状态动作中直接调用未在数据字典中声明的函数;输出赋值必须明确在 entry、during 或 exit 中,不允许依赖默认保持。
  • 层次与并行:层次深度 ≤ 3;并行区域 ≤ 4;禁止在并行区域之间直接交叉转移,跨区域通信必须通过数据或事件。
  • 默认转移:每个父状态必须有默认转移;默认转移的目标必须是直接子状态,不允许跨层。
  • 可测试性:每个状态和转移应能通过输入事件与条件组合被单独激活,禁止存在不可达状态或不可触发转移。
建模完成后,建议用 Stateflow 自带的“模型检查”功能扫描未使用状态、不可达转移、空条件转移和命名违规项。量产项目中,这些检查项通常会被集成到 CI 流程中,作为代码生成前的强制门禁。

一个可对照的建模示例

以“电机使能控制”为例,输入信号为 IgnOn(点火)、StartReq(启动请求)、Fault(故障);输出为 MotorEn(使能)和 PwmDuty(占空比)。建模时可将父状态设为 MotorCtrl,内部并行两个区域:区域A负责使能与PWM输出,区域B负责故障监控。区域A的转移条件可写为:

Idle -> Run: [IgnOn && StartReq && !Fault] {MotorEn = 1;}
Run -> Fault: [Fault] {MotorEn = 0; PwmDuty = 0;}
Fault -> Idle: [IgnOn && !Fault] {MotorEn = 0;}

区域B的转移条件为:Normal -> Alarm: [Fault] {set_dtc();},Alarm -> Normal: [!Fault] {clear_dtc();}。这样,两个区域各自独立响应故障信号,区域A负责安全停机,区域B负责诊断记录,互不干扰。生成代码后,可通过 MIL 测试注入 Fault=1,观察 MotorEn 是否在一个执行步内变为 0,以及 DTC 是否被置位,从而验证模型行为与需求一致。

需要强调的是,上述参数与信号名均为教学示例,实际项目中应以信号矩阵和需求文档为准。建模阶段多花时间在命名、条件和默认转移上,远比在代码生成后排查逻辑缺陷更经济。