4、状态图核心概念:状态、转移、动作(entry/during/exit)、条件与事件、转移优先级
Stateflow 是 MathWorks 公司推出的基于有限状态机(FSM)的图形化建模工具,它与 Simulink 深度集成,用于描述复杂系统的控制逻辑、调度策略和模式切换行为。在联合开发中,状态图(Stateflow Chart)是承载逻辑的核心载体。要真正掌握 Stateflow,不能只停留在“拖拽方块、连线”的操作层面,而必须深刻理解其底层的形式化语义:状态(State)、转移(Transition)、动作(Action)、条件(Condition)、事件(Event)以及转移优先级(Transition Priority)。本章将围绕这六大核心概念展开,从原理到实战,帮助您建立扎实的 Stateflow 建模思维。
上图展示了 Stateflow 六大核心概念之间的逻辑关系。状态是系统在某一时刻的“记忆”或“模式”;转移是状态之间的有向连接,定义了系统如何从一个状态切换到另一个状态;动作则附着在状态或转移上,描述进入状态、处于状态期间以及离开状态时执行的具体行为;条件与事件是触发转移的“门槛”和“扳机”;而转移优先级则决定了当多个转移同时满足条件时,系统按照何种规则进行仲裁。下面我们逐一深入剖析。
4.1 状态(State):系统的“记忆单元”
核心原理:状态是 Stateflow 中最基本的构建块,它代表系统在特定时刻所处的模式或条件。在有限状态机理论中,状态是系统对过去输入历史的“记忆”。例如,一个电机控制系统可以具有“停止”、“加速”、“匀速”、“减速”四个状态。状态具有“激活”(active)和“非激活”(inactive)两种属性,同一时刻,在同一个父状态(父级状态)下,只能有一个子状态处于激活状态(除非使用并行分解,将在后续章节讨论)。
判断方法与操作流程:在 Stateflow 编辑器中,状态用圆角矩形表示。创建状态时,需要定义状态名称(必须唯一)以及状态动作。状态动作分为三类:entry(进入动作)、during(持续动作)、exit(退出动作)。这些动作在状态的生命周期内按特定顺序执行。
| 动作类型 | 触发时机 | 典型应用场景 | 示例代码 |
|---|---|---|---|
| entry | 状态被激活的瞬间(仅执行一次) | 初始化变量、置位标志位、开启设备 | entry: motor_on = 1; |
| during | 状态保持激活期间,每个仿真步长执行一次 | 实时计算、PID控制、数据采集 | during: speed = speed + accel*dt; |
| exit | 状态即将被切换离开时(仅执行一次) | 保存数据、关闭设备、复位标志位 | exit: motor_on = 0; |
实战场景:以汽车车窗升降系统为例。假设有“上升中”、“下降中”、“停止”三个状态。在“上升中”状态的 entry 动作中,我们给电机驱动信号置高电平;在 during 动作中,持续检测车窗位置传感器,并计算剩余行程;在 exit 动作中,将电机驱动信号置低电平,防止误动作。这种设计确保了无论从哪个状态切换到“上升中”,电机都能被正确启动;无论切换到哪个状态,电机都能被安全停止。
常见误区:初学者常将 during 动作与 Simulink 中的连续积分混淆。注意,during 动作在每个仿真步长执行一次,但它是离散事件语义,不涉及连续时间积分。如果需要在状态内进行连续动态计算,应使用 Simulink 中的连续模块,并通过 Stateflow 进行模式切换控制。
4.2 转移(Transition):状态之间的“桥梁”
核心原理:转移定义了系统从一个状态切换到另一个状态的路径。一个完整的转移由源状态(Source)、目标状态(Destination)以及转移标签(Label)组成。转移标签的语法格式为:event[condition]{condition_action}/transition_action。其中,事件和条件可以省略,但转移动作和条件动作是可选的。
判断方法与操作流程:在绘制转移时,需要注意以下几点:
- 转移方向:箭头指向目标状态,表示系统从源状态流向目标状态。
- 自环转移:允许从状态指向自身的转移,用于实现状态内的重新初始化或条件判断。
- 默认转移:每个父状态必须有一个默认转移(带实心圆点的箭头),用于指定进入父状态时首先激活哪个子状态。
- 转移标签书写:事件名写在方括号之前,条件写在方括号内,条件动作写在花括号内,转移动作以“/”开头。
// 转移标签示例
E1[temp > 100]{count++} / alarm_on = 1;
// 含义:当事件E1发生,且temp大于100时,执行count自增,然后转移到目标状态,并置alarm_on为1
经验提醒:转移动作(/ 后面的部分)在转移“被确认有效”之后、目标状态 entry 动作之前执行。而条件动作({} 中的部分)在条件判断为真时立即执行,但此时转移尚未最终确定。理解这个执行顺序对于避免逻辑错误至关重要。
4.3 动作(Action):行为的“执行者”
核心原理:动作是 Stateflow 中执行具体行为的代码片段。除了状态动作(entry/during/exit)外,还有转移动作和条件动作。动作可以调用 MATLAB 函数、Simulink 函数、C 代码(通过 Stateflow API)或执行简单的赋值和逻辑运算。
操作流程与规范:编写动作时,应遵循以下原则:
- 原子性:动作应尽量短小精悍,避免在动作中执行耗时操作(如文件读写、复杂循环),否则会拖慢仿真速度。
- 可读性:使用有意义的变量名,并添加必要的注释。
- 层次性:动作可以访问父状态和子状态的变量,但要注意变量的作用域。
- 类型安全:确保动作中使用的变量类型与定义一致,避免隐式类型转换带来的精度损失。
| 动作类型 | 执行顺序(相对于转移) | 执行次数 |
|---|---|---|
| 条件动作 {} | 条件为真时立即执行,在转移确认前 | 最多一次 |
| 转移动作 / | 转移确认后,目标状态 entry 之前 | 最多一次 |
| 源状态 exit | 转移确认后,源状态退出时 | 一次 |
| 目标状态 entry | 目标状态激活时 | 一次 |
| 目标状态 during | 目标状态激活期间每个步长 | 多次 |
案例拆解:考虑一个电池充电管理系统。状态“恒流充电”的 during 动作中,我们计算当前充电电流并更新累计充电容量;当电池电压达到阈值时,触发转移条件 [V_bat >= 4.2],转移动作 / charge_mode = 2; 将充电模式切换为恒压充电。在“恒压充电”状态的 entry 动作中,我们重新设定 PWM 占空比初始值。整个逻辑通过动作的合理分配,实现了充电阶段的平滑切换。
4.4 条件与事件:转移的“门槛”与“扳机”
核心原理:事件(Event)是 Stateflow 中的“触发器”,它可以是外部输入事件(来自 Simulink 的触发端口)、内部事件(由 Stateflow 内部逻辑产生)或时间事件(基于时间、相对时间或时序逻辑)。条件(Condition)是布尔表达式,只有条件为真时,转移才可能被触发。事件和条件可以组合使用,也可以单独使用。
判断方法与操作流程:
- 事件类型选择:如果转移需要由外部信号上升沿触发,使用“边沿触发事件”;如果需要周期性触发,使用“时间事件”(如
after(5, tick)表示 5 个 tick 后触发)。 - 条件书写:条件表达式支持关系运算符(>, <, ==, !=)、逻辑运算符(&&, ||, !)以及 MATLAB 函数调用。
- 无事件转移:如果省略事件,则只要条件为真,且源状态处于激活状态,转移就会在每个仿真步长被评估。
- 事件广播:可以使用
send(event_name)函数向其他状态或 Simulink 发送事件。
// 时间事件示例
after(10, sec) // 10秒后触发
after(5, tick) // 5个仿真步长后触发
every(2, sec) // 每2秒周期性触发
// 条件与事件组合
E1[count > 5 && mode == 2]
常见误区:初学者容易混淆“无事件转移”和“条件为真即转移”的概念。在 Stateflow 中,如果没有指定事件,转移在每个时间步都会被评估;如果指定了事件,则只有事件发生时才会评估条件。此外,条件动作 {} 中的代码在条件为真时立即执行,但此时转移尚未最终确定,如果后续转移被更高优先级的转移抢占,条件动作的执行结果可能不会生效。
4.5 转移优先级:冲突的“仲裁规则”
核心原理:当多个转移同时满足条件(或同时被事件触发)时,Stateflow 需要确定执行哪个转移。转移优先级规则如下:
- 父级优先:如果存在从父状态出发的转移和从子状态出发的转移,且两者都有效,则父状态的转移优先执行(即“外部转移优先”)。
- 顺序优先:对于同一源状态的多个转移,按照它们在图形中从上到下、从左到右的顺序进行判断,先满足条件的转移被执行。
- 非互斥性:如果两个转移的源状态和目标状态不同,且没有重叠,则它们可以同时执行(在并行分解中)。
操作流程与判断方法:在绘制状态图时,应仔细规划转移的布局顺序。将最紧急或最优先的转移放在最上方或最左侧。同时,可以利用“转移优先级”的概念来设计防抖逻辑或安全保护逻辑。
| 场景 | 转移A | 转移B | 实际执行 |
|---|---|---|---|
| 同一源状态 | E1[cond1] | E1[cond2] | 先判断cond1,若为真执行A;否则判断cond2 |
| 父子状态 | 父状态转移 | 子状态转移 | 父状态转移优先执行 |
| 不同源状态 | 状态A→C | 状态B→D | 若A和B都激活,则两个转移都可能执行 |
实战案例:设计一个工业机械臂的急停逻辑。机械臂有“自动运行”、“手动调试”、“暂停”三个状态。急停信号 E_stop 是一个高优先级事件。我们在父状态“运行模式”上绘制一个转移:E_stop / power_off = 1; 指向“急停”状态。同时,在“自动运行”子状态上也有一个转移:E_stop / alarm = 1; 指向“急停”状态。根据“父级优先”规则,当急停事件发生时,父状态的转移会优先执行,从而确保电源被立即切断,而子状态的报警动作可能不会执行。这个设计避免了逻辑冲突,保证了安全。
经验总结:转移优先级是 Stateflow 建模中最容易出错的地方之一。建议在模型设计阶段就绘制出状态转移表,明确每个转移的源状态、目标状态、触发事件、条件、动作以及优先级顺序。在代码生成后,通过 Simulink 的“逻辑覆盖度”工具检查是否有不可达的转移或冗余的优先级设置。
4.6 综合案例:电梯门控制系统
为了将上述概念融会贯通,我们设计一个简化的电梯门控制系统。系统包含“开门”、“开门保持”、“关门”、“关门受阻”四个状态。输入信号包括:开门按钮(btn_open)、关门按钮(btn_close)、门位置传感器(pos_sensor,0表示全关,1表示全开)、光幕传感器(light_curtain,1表示有人或物阻挡)。
状态定义与动作:
- 开门:
entry: motor_dir = 1; motor_on = 1;;during: if(pos_sensor >= 0.99) send(open_done);;exit: motor_on = 0; - 开门保持:
entry: hold_timer = 0;;during: hold_timer = hold_timer + 1; if(hold_timer >= 50) send(hold_timeout); - 关门:
entry: motor_dir = 0; motor_on = 1;;during: if(pos_sensor <= 0.01) send(close_done);;exit: motor_on = 0; - 关门受阻:
entry: motor_on = 0; alarm = 1;;during: if(light_curtain == 0) send(obstacle_clear);;exit: alarm = 0;
转移定义与优先级:
- 默认转移 → 开门
- 开门 → 开门保持:
open_done(事件) - 开门保持 → 关门:
hold_timeout(事件) - 关门 → 开门:
btn_open(事件) - 关门 → 关门受阻:
light_curtain == 1(条件,无事件) - 关门受阻 → 开门:
obstacle_clear(事件) - 关门受阻 → 关门:
btn_close(事件)
优先级分析:在“关门”状态下,如果同时发生 btn_open 事件和光幕被遮挡(条件为真),则转移4(关门→开门)和转移5(关门→关门受阻)都有效。根据“从上到下”的规则,转移4位于转移5的上方,因此优先执行“开门”转移。这符合安全逻辑:即使有人阻挡,只要按下开门按钮,就应该优先开门。
结果与复盘:通过这个案例,我们看到了状态动作如何管理硬件输出,事件如何驱动状态切换,条件如何实现实时监测,以及优先级如何解决冲突。在实际工程中,还需要考虑传感器噪声、通信延迟等因素,可以通过增加“去抖”状态或使用 Simulink 中的滤波模块来优化。
风险提示:在状态图中使用时间事件(如 after(50, tick))时,要注意仿真步长的影响。如果仿真步长是可变的,时间事件的触发精度会受到影响。建议在固定步长求解器下使用时间事件,或者使用 after(50, sec) 并确保采样时间足够小。