4、状态动作与转移动作:entry、during、exit动作的编写与执行顺序,转移动作的触发时机

在Stateflow中,状态机之所以能够描述复杂的逻辑行为,核心在于其动作(Action)机制。动作是附着在状态或转移上的代码片段,它们决定了系统在特定时刻执行何种操作。初学者往往容易混淆状态动作与转移动作的区别,导致逻辑执行顺序混乱,甚至产生难以排查的时序Bug。本章将深入剖析entry(进入)、during(持续)、exit(退出)三种状态动作的编写方法与执行顺序,并详细讲解转移动作的触发时机及其与状态动作的优先级关系。理解这些底层机制,是设计可靠控制逻辑的基石。

状态动作与转移动作执行顺序模型 状态 A entry: A_en(); during: A_dur(); exit: A_exit(); 执行顺序: ① entry → ② during → ③ during... ④ exit(离开时) 状态 B entry: B_en(); during: B_dur(); exit: B_exit(); 执行顺序: ① entry → ② during → ③ during... ④ exit(离开时) 转移条件: [cond] 转移动作: {trans_act();} 完整执行序列(一个时间步内) 1. A_exit() 2. 转移动作 3. B_entry() 4. B_during() 先执行源状态 的exit动作 转移动作在条件 成立后、目标状态 entry之前执行 再执行目标 状态的entry 最后执行目标 状态的during 注意:若转移条件不成立,则只执行 during 动作,不触发 exit 和 entry

上图清晰地展示了从状态A转移到状态B时,一个完整时间步内的动作执行顺序。接下来,我们将从原理、方法、案例和误区四个维度展开详细讲解。

4.1 核心原理:状态动作的生命周期与执行语义

Stateflow中的每个状态都有其独立的生命周期。状态动作就是在这个生命周期不同阶段挂接的代码。理解生命周期是掌握动作编写的前提。

  • entry 动作(进入动作):当状态从非激活变为激活时执行。它只执行一次,通常用于初始化该状态所需的局部变量、置位输出信号、开启定时器或记录进入时间戳。例如,在电机控制中,进入“加速”状态时,需要将目标转速变量赋值并启动PID控制器。
  • during 动作(持续动作):只要状态保持激活,在每个时间步都会执行。它是状态机的“心脏”,用于执行持续的控制算法、读取传感器数据、更新输出或进行条件判断。例如,在“匀速运行”状态中,during动作会持续计算速度误差并输出PWM占空比。
  • exit 动作(退出动作):当状态从激活变为非激活时执行。它也只执行一次,通常用于清理资源、保存状态参数、关闭执行器或发出状态离开的指示信号。例如,退出“急停”状态时,需要清除故障标志位。

这三个动作与状态的关系可以用一个简单的类比来理解:entry 是“进门时开灯”,during 是“在房间里持续工作”,exit 是“出门时关灯”。

执行顺序铁律:在一个时间步内,如果状态保持激活,则只执行 during 动作。只有当发生转移时,才会依次执行 exit(源状态)→ 转移动作 → entry(目标状态)→ during(目标状态)。

4.2 判断方法与操作流程:如何正确编写与放置动作

在实际建模中,工程师经常面临“这段代码该放在entry还是during里”的困惑。以下提供一套判断流程,帮助您做出正确决策。

  1. 判断执行频率:该操作是需要只执行一次,还是每个时间步都执行?只执行一次的放在 entry 或 exit;需要持续执行的放在 during。
  2. 判断执行时机:该操作是在进入状态时必须完成,还是在离开状态时必须完成?进入时的初始化放在 entry,离开时的清理放在 exit。
  3. 判断是否与转移条件相关:如果某段代码只有在特定转移条件成立时才执行,且与源状态或目标状态的内部逻辑无关,则应将其放在转移动作中。
  4. 检查依赖关系:如果目标状态的 entry 动作依赖于转移动作中计算出的变量值,那么该计算必须放在转移动作中,且转移动作的执行顺序在 entry 之前。

在Stateflow编辑器中,双击状态图形,即可在属性对话框中看到 entry、during、exit 三个标签页,分别编写对应的代码。转移动作则写在转移连线上的事件标签位置,格式为 事件[条件]{转移动作}。

动作类型 执行次数 典型用途 编写位置
entry 1次(进入时) 初始化局部变量、置位输出、启动定时器 状态属性 → entry 标签
during 每次时间步 控制算法、实时数据采集、输出刷新 状态属性 → during 标签
exit 1次(退出时) 保存参数、复位输出、关闭设备 状态属性 → exit 标签
转移动作 1次(转移发生时) 记录转移事件、计算中间变量、触发一次性操作 转移连线 → 动作标签

4.3 案例拆解:汽车自动空调的温度调节控制逻辑

为了更直观地理解上述概念,我们以一个汽车自动空调温度调节系统为例进行拆解。该系统需要根据车内温度与设定温度的差值,控制鼓风机风速和压缩机状态。

背景与约束:车辆启动后,空调系统上电。设定目标温度为24°C。系统需要快速降温,但又要避免压缩机频繁启停。控制周期为100ms。

状态划分:

  • IDLE(待机):空调关闭,无输出。
  • COOLING(制冷):压缩机开启,鼓风机高速运转。
  • MAINTAIN(维持):压缩机间歇工作,鼓风机低速运转。

动作编写:

// 状态: COOLING
entry: 
    compressor_on = TRUE;   // 开启压缩机
    fan_speed = HIGH;       // 鼓风机高速
    start_time = get_tick(); // 记录进入时刻

during: 
    temp_error = current_temp - target_temp; // 计算温差
    if (temp_error < 2.0) 
    {
        // 温差小于2度,准备切换到维持状态
        // 注意:这里不直接切换,而是通过转移条件判断
    }
    // 持续监测温度并调整风速
    if (temp_error > 5.0) fan_speed = HIGH;
    else if (temp_error > 3.0) fan_speed = MEDIUM;

exit: 
    compressor_on = FALSE;  // 关闭压缩机
    fan_speed = LOW;        // 鼓风机降速
    last_run_duration = get_tick() - start_time; // 记录本次运行时长

转移动作:

// 从 COOLING 转移到 MAINTAIN
COOLING [temp_error < 2.0] { transition_count++; log_event("Cooling to Maintain"); }

分析判断过程:当车内温度从30°C降至26°C时,temp_error 为2°C,满足转移条件。此时,在一个时间步内,Stateflow先执行 COOLING.exit(关闭压缩机、记录时长),然后执行转移动作(计数加1),最后执行 MAINTAIN.entry(设置维持模式参数)。如果温度回升,则从 MAINTAIN 转移回 COOLING,同样遵循 exit → 转移动作 → entry 的顺序。

结果对比与复盘:如果错误地将 compressor_on = FALSE 写在 during 动作中,那么只要状态处于 COOLING,压缩机就会在每次时间步被关闭,导致系统无法制冷。通过这个案例,可以清晰地看到 exit 动作对于资源释放和状态清理的重要性。

风险提示:在 during 动作中,应避免编写可能阻塞或耗时过长的代码,例如复杂的浮点运算或无限循环。这会拖慢整个状态机的执行周期,导致控制实时性下降。

4.4 常见误区与总结复盘

在实际工程中,以下三个误区最为常见,需要特别警惕。

  • 误区一:在 during 中重复初始化。有些工程师习惯在 during 里对变量赋值,导致每次时间步都重置变量,使得累加器或计数器永远无法正常工作。正确做法是:初始化放在 entry,更新放在 during。
  • 误区二:混淆转移动作与 during 动作。转移动作只在转移发生时执行一次,而 during 在每个时间步都执行。如果希望某个操作在状态激活期间持续生效,应放在 during;如果只是想在切换瞬间做一件事,应放在转移动作中。
  • 误区三:忽略 exit 动作的清理作用。在状态切换时,如果不在 exit 中复位输出,可能会导致执行器在错误的状态下继续运行。例如,从“急停”状态退出时,必须清除急停标志,否则系统会一直处于急停锁定状态。

总结复盘:本章的核心知识点可以归纳为“一个顺序、两个频率、三个动作”。一个顺序是指 exit → 转移动作 → entry → during 的严格执行顺序;两个频率是指 entry/exit 只执行一次,而 during 每个时间步都执行;三个动作分别对应状态的进入、持续和退出阶段。掌握这些基础,您就能构建出逻辑清晰、行为可预测的状态机模型。在下一节中,我们将进一步探讨转移条件的优先级与事件广播机制,敬请期待。