第十章:Stateflow 状态机
第十章:Stateflow 状态机
10.1 Stateflow 基础
10.1.1 什么是 Stateflow
Stateflow 是 Simulink 的扩展工具箱,用于设计和仿真有限状态机(FSM)和流程图。
Stateflow 解决的问题: 纯 Simulink 擅长: 连续/离散数学运算 信号处理 控制算法 Stateflow 擅长: 逻辑判断和状态切换 事件驱动的行为 模式管理 安全监控逻辑 典型应用: 汽车变速箱控制(P/R/N/D档位切换) 交通灯控制(红/黄/绿状态循环) 电机控制模式(启动/运行/停止/故障) 通信协议状态机(握手/传输/断开) 飞行控制模式(起飞/巡航/降落/紧急)10.1.2 有限状态机概念
有限状态机(FSM)由以下要素组成: ① 状态集合 S = {S1, S2, S3, ...} 系统在任意时刻处于且仅处于一个状态 ② 输入事件集合 E = {e1, e2, e3, ...} 触发状态转移的事件 ③ 转移函数 δ: S × E → S 在状态S收到事件e后,转移到新状态 ④ 初始状态 S0 系统启动时的初始状态 ⑤ 输出函数 在特定状态或转移时产生的输出 示例:简单开关状态机 状态:{OFF, ON} 事件:{press_button} 转移: OFF + press_button → ON ON + press_button → OFF 初始状态:OFF10.1.3 Stateflow 与 Simulink 的关系
集成方式: Simulink 模型 └── Chart(Stateflow图表) ├── 状态(States) ├── 转移(Transitions) ├── 事件(Events) └── 数据(Data) Stateflow Chart 作为 Simulink 的一个模块存在: 输入端口:接收 Simulink 信号(触发事件或数据) 输出端口:向 Simulink 输出数据 数据流向: Simulink 信号 ──→ Stateflow Chart ──→ Simulink 信号 (连续/离散信号) (逻辑状态机) (控制信号/状态输出)10.2 Stateflow 基本元素
10.2.1 状态(State)
状态表示系统的一种工作模式或条件。
状态的图形表示: ┌─────────────────────────────┐ │ StateName │ ├─────────────────────────────┤ │ entry: 进入时执行的动作 │ │ during: 每个时间步执行的动作 │ │ exit: 离开时执行的动作 │ └─────────────────────────────┘ 状态类型: 普通状态(AND/OR): OR 状态:同一层级只有一个激活 AND 状态:同一层级同时激活(并行状态) 超状态(Superstate): 包含子状态的状态 实现层次化状态机 历史节点(History Junction): 记住上次离开超状态时的子状态 重新进入超状态时恢复到历史子状态状态动作(Actions):
entry(进入动作): 进入该状态时执行一次 用于初始化、复位操作 例:entry: speed = 0; led = ON; during(持续动作): 在该状态中每个时间步执行 用于持续计算或监控 例:during: temperature = read_sensor(); exit(退出动作): 离开该状态时执行一次 用于清理、保存操作 例:exit: save_data(); alarm = OFF; on event_name(事件动作): 收到特定事件时执行 例:on BUTTON_PRESS: count = count + 1;10.2.2 转移(Transition)
转移定义了状态之间的切换条件和动作。
转移的图形表示: [源状态] ──────────────────────────→ [目标状态] 条件[guard]/动作{action} 转移语法: event[condition]{action} event:触发转移的事件(可省略) [condition]:转移条件(布尔表达式) {action}:转移时执行的动作 示例: TIMEOUT[speed > 0]{brake = ON} → 收到 TIMEOUT 事件,且速度大于0时, 执行 brake=ON,然后转移到目标状态 默认转移(Default Transition): 没有源状态的转移 用于指定初始状态 图形:实心圆点 ●──→ [初始状态]转移优先级:
当多个转移同时满足条件时: 优先级由转移的编号决定(数字越小优先级越高) 可以在转移属性中设置优先级 最佳实践: 避免多个转移同时满足条件 使用互斥条件确保唯一性10.2.3 事件(Event)
事件是触发状态转移的信号。
事件类型: ① 输入事件(Input Event): 来自 Simulink 的触发信号 在 Chart 输入端口显示为触发端口 ② 输出事件(Output Event): Stateflow 向 Simulink 发送的事件 可以触发 Simulink 中的触发子系统 ③ 局部事件(Local Event): Chart 内部使用的事件 用于状态间的内部通信 ④ 时间事件(Temporal Event): 基于时间的事件 after(n, sec):n秒后触发 after(n, tick):n个时间步后触发 every(n, sec):每n秒触发一次 示例: after(5, sec):5秒后触发 after(10, tick):10个仿真步后触发 every(1, sec):每秒触发一次10.2.4 数据(Data)
Stateflow 中的数据用于存储和传递数值。
数据作用域: Local(局部): 只在 Chart 内部使用 不与 Simulink 交换 Input(输入): 从 Simulink 接收数据 对应 Chart 的输入端口 Output(输出): 向 Simulink 发送数据 对应 Chart 的输出端口 Parameter(参数): 常量参数,不随时间变化 从 MATLAB 工作区获取 Constant(常数): 固定不变的值 数据类型: double / single / int8 / uint8 / boolean / ... 与 Simulink 数据类型一致10.2.5 动作(Action)
动作是在状态或转移中执行的代码。
支持的语法: 赋值: x = 5; y = x + 1; flag = true; 条件语句: if x > 0 y = x; else y = -x; end 循环: for i = 1:10 sum = sum + i; end 函数调用: y = my_function(x); [a, b] = another_function(x, y); MATLAB 函数(部分支持): y = abs(x); y = sqrt(x); y = mod(x, 10); 发送事件: send(EVENT_NAME); 广播事件: send(EVENT_NAME, target_state);10.3 搭建状态机
10.3.1 创建 Chart
方法1:从 Library Browser 添加 Library Browser → Stateflow → Chart 拖拽到 Simulink 模型画布 方法2:从菜单创建 Simulink 模型中: 菜单 Diagram → New → Chart 方法3:命令行 add_block('stateflow/Chart', 'mymodel/MyChart')Chart 属性配置:
双击 Chart 进入编辑界面 右键 Chart → Properties: General 标签: Action Language: MATLAB(推荐,语法熟悉) C(性能更高,代码生成) Update method: Inherited(继承 Simulink 采样时间) Discrete(指定采样时间) Continuous(连续更新) Enable super step semantics: 允许在一个时间步内执行多次转移10.3.2 添加状态和转移
添加状态:
方法1:工具栏 Chart 编辑器工具栏 → 状态图标(矩形)→ 点击画布 方法2:右键菜单 在画布空白处右键 → Add State 方法3:快捷键 按住 S 键 + 点击画布 配置状态: 双击状态 → 输入状态名称 在状态内部输入动作代码: entry: 初始化代码 during: 持续执行代码 exit: 退出代码添加转移:
方法1:拖拽 鼠标悬停在源状态边缘 出现蓝色箭头时拖拽到目标状态 方法2:工具栏 选择转移工具 → 点击源状态 → 点击目标状态 配置转移: 双击转移线 → 输入转移标签 格式:event[condition]{action} 例:TIMEOUT[speed > 0]{brake = ON}添加默认转移(指定初始状态):
方法: 工具栏选择默认转移工具 点击目标状态(初始状态) → 生成从实心圆点到初始状态的箭头 每个层级必须有且只有一个默认转移10.3.3 配置转移条件
转移条件语法示例: 简单条件: [speed > 100] [temperature >= 80] [flag == true] 复合条件: [speed > 100 && temperature < 50] [mode == 1 || mode == 2] [~alarm_active] 时间条件: [after(5, sec)] → 5秒后 [after(100, tick)] → 100个时间步后 [every(1, sec)] → 每秒(用于周期性转移) 事件+条件: BUTTON_PRESS[count > 3] → 收到 BUTTON_PRESS 事件且 count > 3 时转移 无条件转移(立即转移): 不写任何标签 → 进入源状态后立即转移到目标状态 (通常用于连接节点)10.3.4 仿真调试
Stateflow 调试工具:
运行仿真时,Chart 编辑器实时显示: 当前激活的状态(高亮显示) 已执行的转移(箭头高亮) 数据的当前值 调试功能: 断点:右键状态/转移 → Set Breakpoint 单步执行:仿真暂停后,逐步执行 数据监视:查看变量当前值常见调试问题:
问题1:状态机不转移 检查:转移条件是否正确 检查:事件是否正确触发 检查:数据类型是否匹配 问题2:状态机在两个状态间振荡 原因:转移条件互相满足,来回切换 解决:添加时间延迟或计数器防抖 问题3:初始状态不正确 检查:默认转移是否指向正确的初始状态 检查:entry 动作中的初始化是否正确10.4 实战:交通灯控制系统
10.4.1 需求分析
交通灯控制需求: 状态: RED(红灯):持续 30 秒 GREEN(绿灯):持续 25 秒 YELLOW(黄灯):持续 5 秒 转移规则: RED → GREEN:红灯持续 30 秒后 GREEN → YELLOW:绿灯持续 25 秒后 YELLOW → RED:黄灯持续 5 秒后 输出: red_light:boolean(红灯开关) green_light:boolean(绿灯开关) yellow_light:boolean(黄灯开关) countdown:double(倒计时秒数) 初始状态:RED10.4.2 状态机设计
状态机图:
●(默认转移) │ ↓ ┌─────────────────────────────────────┐ │ RED │ │ entry: red=1; green=0; yellow=0; │ │ countdown=30; │ │ during: countdown=countdown-dt; │ └─────────────────────────────────────┘ │ │ [after(30, sec)] ↓ ┌─────────────────────────────────────┐ │ GREEN │ │ entry: red=0; green=1; yellow=0; │ │ countdown=25; │ │ during: countdown=countdown-dt; │ └─────────────────────────────────────┘ │ │ [after(25, sec)] ↓ ┌─────────────────────────────────────┐ │ YELLOW │ │ entry: red=0; green=0; yellow=1; │ │ countdown=5; │ │ during: countdown=countdown-dt; │ └─────────────────────────────────────┘ │ │ [after(5, sec)] └──────────────────────────────────→ RED(循环)10.4.3 Simulink 集成
搭建步骤:
Step 1:新建 Simulink 模型 Step 2:添加 Stateflow Chart 模块 Library Browser → Stateflow → Chart Step 3:配置 Chart 输出端口 在 Chart 中定义输出数据: 右键画布 → Add Data → Output 添加:red_light, green_light, yellow_light, countdown Step 4:在 Chart 内部搭建状态机 添加三个状态:RED, GREEN, YELLOW 添加默认转移到 RED 添加三条转移(使用 after() 时间条件) 在每个状态的 entry 动作中设置灯光输出 Step 5:连接 Simulink 模块 Chart 输出端口 → Display 模块(显示倒计时) Chart 输出端口 → Scope(显示灯光状态) Step 6:配置仿真 Stop Time = 120s(观察两个完整周期) Solver = Fixed-step, Step size = 1sChart 内部代码:
RED 状态: entry: red_light = true; green_light = false; yellow_light = false; countdown = 30; GREEN 状态: entry: red_light = false; green_light = true; yellow_light = false; countdown = 25; YELLOW 状态: entry: red_light = false; green_light = false; yellow_light = true; countdown = 5; 转移条件: RED → GREEN:[after(30, sec)] GREEN → YELLOW:[after(25, sec)] YELLOW → RED:[after(5, sec)]10.4.4 仿真验证
%% 交通灯仿真验证脚本% 运行仿真simOut=sim('traffic_light','StopTime','120');t=simOut.tout;% 获取输出数据red=simOut.logsout.getElement('red_light').Values.Data;green=simOut.logsout.getElement('green_light').Values.Data;yellow=simOut.logsout.getElement('yellow_light').Values.Data;% 绘图figure;subplot(3,1,1);stairs(t,double(red),'r-','LineWidth',2);ylabel('红灯');ylim([-0.11.1]);grid on;title('交通灯控制仿真');subplot(3,1,2);stairs(t,double(green),'g-','LineWidth',2);ylabel('绿灯');ylim([-0.11.1]);grid on;subplot(3,1,3);stairs(t,double(yellow),'Color',[10.70],'LineWidth',2);ylabel('黄灯');ylim([-0.11.1]);grid on;xlabel('时间 (s)');% 验证时序fprintf('=== 交通灯时序验证 ===\n');% 找红灯开始时间red_starts=t(diff([0;double(red)])>0);fprintf('红灯开始时刻:');fprintf('%.0f ',red_starts(1:min(3,end)));fprintf('...\n');% 验证周期iflength(red_starts)>=2period=red_starts(2)-red_starts(1);fprintf('交通灯周期:%.0f 秒(期望:60秒)\n',period);end综合实战:电机控制模式管理
系统描述:
工业电机控制模式管理: 模式(状态): IDLE(空闲):电机停止,等待命令 STARTING(启动):电机加速到目标转速 RUNNING(运行):电机以目标转速运行 STOPPING(停止):电机减速到零 FAULT(故障):检测到故障,紧急停机 转移条件: IDLE → STARTING:收到 START 命令 STARTING → RUNNING:转速达到目标值(speed >= target_speed) RUNNING → STOPPING:收到 STOP 命令 STOPPING → IDLE:转速降到零(speed <= 0) 任意状态 → FAULT:检测到故障(fault_detected == true) FAULT → IDLE:故障清除(fault_cleared == true) 输出: motor_enable:boolean(电机使能) speed_setpoint:double(转速设定值) status_code:int8(状态码:0=空闲,1=启动,2=运行,3=停止,4=故障)状态机代码:
IDLE 状态: entry: motor_enable = false; speed_setpoint = 0; status_code = 0; STARTING 状态: entry: motor_enable = true; speed_setpoint = target_speed; status_code = 1; RUNNING 状态: entry: status_code = 2; during: % 监控运行状态 if speed < target_speed * 0.9 % 转速下降,可能有负载变化 speed_setpoint = target_speed * 1.1; % 适当提高设定值 else speed_setpoint = target_speed; end STOPPING 状态: entry: speed_setpoint = 0; status_code = 3; exit: motor_enable = false; FAULT 状态: entry: motor_enable = false; speed_setpoint = 0; status_code = 4; alarm = true; exit: alarm = false; 转移: IDLE → STARTING:START[~fault_detected] STARTING → RUNNING:[speed >= target_speed * 0.95] RUNNING → STOPPING:STOP STOPPING → IDLE:[speed <= 1.0] IDLE → FAULT:[fault_detected] STARTING → FAULT:[fault_detected] RUNNING → FAULT:[fault_detected] STOPPING → FAULT:[fault_detected] FAULT → IDLE:[fault_cleared]本章小结
本章核心要点: ✅ Stateflow 用于设计逻辑状态机,补充 Simulink 的数学计算能力 ✅ 状态机三要素:状态(State)/ 转移(Transition)/ 动作(Action) ✅ 状态动作:entry(进入时)/ during(持续)/ exit(退出时) ✅ 转移语法:event[condition]{action} ✅ 时间条件:after(n, sec) / every(n, sec) ✅ 默认转移:指定初始状态(实心圆点箭头) ✅ 层次化状态机:超状态包含子状态,实现复杂逻辑 ✅ 并行状态(AND):多个状态同时激活 ✅ 历史节点:记住上次离开时的子状态 ✅ 调试:仿真时高亮显示当前激活状态和转移课后练习
搭建简单开关状态机:
- 状态:OFF / ON
- 转移:按钮按下时切换
- 输出:LED 状态(0/1)
- 在 Simulink 中用 Pulse Generator 模拟按钮
搭建交通灯控制系统:
- 按照本章示例完整搭建
- 添加紧急模式:收到 EMERGENCY 信号时,所有灯闪烁黄灯
- 验证时序是否正确
搭建电梯控制状态机:
- 状态:IDLE / MOVING_UP / MOVING_DOWN / DOOR_OPEN
- 转移:楼层请求、到达目标楼层、开关门
- 输出:当前楼层、运动方向、门状态
将电机控制模式管理集成到 Simulink:
- Stateflow 管理模式
- Simulink 实现电机动力学模型
- 测试各种模式切换场景
- 测试故障注入和恢复