3、状态机设计模式:Mealy与Moore状态机区别、单层与分层状态机、状态机与任务调度
状态机这东西,说白了就是控制逻辑的骨架。我做了十几年运动控制,见过太多项目因为状态机设计不合理,后期改得死去活来。今天咱们就把这块掰开揉碎了聊。
3.1 Mealy与Moore状态机:输出时机决定一切
先问个问题:你的输出是跟状态走,还是跟跳转走?
这就是Mealy和Moore最本质的区别。
Moore状态机:输出只取决于当前状态。换句话说,只要状态不变,输出就不变。我早期做的一个步进电机驱动器,用的就是Moore。每个状态对应一组固定的IO输出——比如状态A输出脉冲,状态B输出方向信号。逻辑清晰,调试起来也省心。
Mealy状态机:输出不仅取决于当前状态,还取决于输入。这意味着同一状态下,输入不同,输出可能完全不同。我在做伺服驱动器位置同步时用过Mealy——同样是“同步等待”状态,如果收到位置偏差过大信号,立即输出急停;如果偏差正常,则输出继续运行。同一个状态,两种输出。
| 对比项 | Moore | Mealy |
|---|---|---|
| 输出依赖 | 仅当前状态 | 当前状态 + 输入 |
| 响应速度 | 慢一拍(需等时钟沿) | 快(输入变化立即响应) |
| 状态数量 | 通常较多 | 通常较少 |
| 安全性 | 高(输出稳定) | 需注意毛刺 |
| 典型场景 | 安全联锁、模式切换 | 实时响应、数据流控制 |
我的经验之谈:运动控制中,安全相关的逻辑(比如急停、限位)我强烈建议用Moore。输出稳定,不会因为输入抖动导致误动作。而性能相关的逻辑(比如速度跟随、位置补偿)可以用Mealy,响应更快。
小技巧:如果你不确定用哪种,先画Moore。因为Moore可以无损转换为Mealy,反过来却不行。我习惯先用Moore搭框架,性能瓶颈处再局部改成Mealy。
3.2 单层与分层状态机:别让状态图变成蜘蛛网
你见过那种一张纸上画了50个状态、100条跳转线的状态图吗?我见过。那是我刚入行时一个同事画的,他自己后来都看不懂了。
这就是单层状态机的极限。所有状态平铺在一个层级里,状态多了,跳转关系就乱成一锅粥。
分层状态机的思路很简单:把相关的状态打包成一个“子状态机”,上层只关心这个子状态机的入口和出口。就像写代码一样,把复杂逻辑封装成函数。
举个例子。一个工业机器人控制,整体状态机可以分成三层:
- 顶层:待机、运行、故障、急停
- 中层:运行状态下,再分回零、示教、自动执行
- 底层:自动执行状态下,再分直线插补、圆弧插补、点位运动
每一层只关心自己的事。顶层不用管底层怎么插补,底层也不用管顶层要不要急停。各司其职。
注意:分层不是越多越好。我见过有人分了七八层,结果一个跳转要穿透四层才能到达目标状态,调试时脑子都要炸了。一般三层足够,最多四层。
下面是我画的一个分层状态机结构图,你看一眼就明白了:
3.3 状态机与任务调度:谁指挥谁?
这个问题我当年也困惑过:状态机跑在任务里,还是任务跑在状态机里?
答案是:状态机是逻辑框架,任务调度是执行引擎。
我常用的模式是这样的:
// 伪代码:状态机驱动的任务调度
void main_loop() {
while(1) {
// 1. 读取输入(传感器、指令、定时器)
input = read_inputs();
// 2. 状态机跳转
next_state = fsm_transition(current_state, input);
// 3. 执行状态动作
fsm_action(next_state);
// 4. 任务调度(非阻塞)
schedule_tasks();
// 5. 更新状态
current_state = next_state;
// 6. 等待下一个周期
delay(1); // 1ms周期
}
}
这里的关键是非阻塞。每个状态动作执行时间必须远小于调度周期。我在做多轴同步控制时,每个状态动作控制在100微秒以内,剩下的900微秒留给任务调度。
避坑指南:我曾经在一个项目里把状态动作写成了阻塞式——比如在“等待到位”状态里用while循环死等。结果其他任务全被卡死了,电机直接飞车。后来改成状态机+定时器轮询,才解决问题。
任务调度这块,我推荐用时间片轮转或者优先级抢占。运动控制里,优先级一般是:
- 最高:急停、限位、故障处理(硬实时)
- 高:位置环、速度环(1ms以内)
- 中:状态机跳转、IO刷新(5ms以内)
- 低:通信、日志、界面刷新(10ms以上)
你想想看,如果状态机跳转和急停抢CPU,那后果是什么?所以状态机本身不应该占用太多CPU,它只是“决策者”,不是“执行者”。
我的习惯:状态机代码单独放一个文件,任务调度代码放另一个文件。接口就两个函数:fsm_run() 和 fsm_get_state()。这样换平台或者换调度策略时,状态机逻辑完全不用动。
最后说一句:状态机设计没有银弹。Mealy还是Moore,单层还是分层,取决于你的具体场景。但有一条原则永远不会错——让逻辑清晰可读,比追求花哨更重要。毕竟,代码是写给人看的,顺便给机器执行。