一、状态机基础:什么是状态机、为什么用在运动控制中、状态机的三要素

大家好,我是老张。在运动控制这行摸爬滚打了十几年,今天咱们聊聊状态机。

说实话,我刚入行那会儿,写运动控制程序就是一堆 if-else 堆砌。电机正转、反转、急停、回零……代码越写越长,最后自己都看不懂了。直到有一次,设备在客户现场出了故障——电机该停的时候没停,差点撞了夹具。那次之后,我才真正重视起状态机。

什么是状态机?

状态机,说白了就是一个“有限状态自动机”。

你想想看,一台运动控制器,它不可能同时做所有事。要么在“空闲”状态,要么在“运动中”,要么在“报警”状态。每个时刻,它只能处于一个状态。这就是状态机的核心思想。

我习惯用一个比喻来解释:状态机就像一台自动售货机。你投币(事件),它根据当前状态(比如“待机”或“已投币”),决定下一步做什么(转移)。

核心定义:状态机是一种数学模型,它由有限个状态、事件和转移规则组成。在任何时刻,系统只能处于一个状态,并通过事件触发转移到另一个状态。

为什么用在运动控制中?

这个问题我问过很多新人。答案五花八门,但核心就三点:

  • 安全可靠——状态机强制规定了“什么状态下能做什么事”。比如,电机在“高速运行”状态下,不允许直接进入“回零”状态。这种约束能避免很多低级错误。
  • 逻辑清晰——代码不再是面条式的 if-else。每个状态独立处理自己的事件,出了问题一眼就能定位。
  • 易于维护——我遇到过最夸张的项目,状态机有 30 多个状态。但因为有清晰的状态转移图,新同事接手时,看一遍图就能上手改代码。

嗯,这里要注意一点:不是所有运动控制场景都需要状态机。如果你只是控制一个风扇正反转,用 if-else 就够了。但一旦涉及多轴联动、安全互锁、复杂流程,状态机就是你的救命稻草。

状态机的三要素

状态机的三要素,我记了十年都没忘:状态、事件、转移

1. 状态(State)

状态就是系统在某一时刻的“快照”。在运动控制中,常见的状态有:

状态名称 含义 典型行为
IDLE(空闲) 电机未使能,等待指令 响应启动指令
HOMING(回零) 正在寻找原点 执行回零算法
MOVING(运动) 正在执行定位或轨迹 实时插补计算
ALARM(报警) 发生故障 停止输出,记录错误

我个人习惯给每个状态分配一个枚举值,比如 STATE_IDLE = 0STATE_HOMING = 1。这样在调试时,直接看变量值就知道当前状态,非常方便。

2. 事件(Event)

事件是触发状态转移的“导火索”。它可以是外部输入(比如按钮按下),也可以是内部条件(比如位置到达)。

我曾经犯过一个错:把“超时”和“位置到达”两个事件混在一起处理。结果有一次超时了,程序却误判为位置到达,直接执行了下一步动作……嗯,从那以后,我坚持每个事件独立编码。

常见的事件类型:

  • 用户指令(启动、停止、复位)
  • 传感器信号(限位开关、原点信号)
  • 内部定时器(超时、延时完成)
  • 计算完成(插补结束、路径规划完成)

3. 转移(Transition)

转移定义了“当事件发生时,从当前状态到下一个状态的路径”。

转移通常包含三个要素:

  • 源状态——当前状态
  • 触发事件——什么条件下转移
  • 目标状态——转移到哪里
  • 动作——转移过程中执行的操作(可选)

举个例子:

// 状态转移示例
当前状态: IDLE
触发事件: EVT_START
条件: 电机已使能 && 无报警
动作: 清除位置误差,设置目标位置
目标状态: MOVING

我的小技巧:在写转移逻辑时,我习惯用二维表格来梳理。行是当前状态,列是事件,交叉点就是目标状态。这样一眼就能看出有没有遗漏的转移路径。

状态机核心逻辑图

下面这张图,是我做运动控制项目时最常用的状态机框架。它包含了三个基本状态和它们之间的转移关系:

运动控制状态机基本框架 IDLE 空闲状态 MOVING 运动状态 ALARM 报警状态 EVT_START EVT_FINISH EVT_ERROR EVT_RESET 图例说明 状态节点 事件触发转移 自动完成转移

注意:这张图是最简版本。实际项目中,状态数可能多达几十个。但核心逻辑不变——每个状态只处理自己该处理的事,事件来了就按规则转移。

一个简单的代码示例

最后,给你看一段我常用的状态机框架代码。这是 C 语言风格,但思路在任何语言中都通用:

// 状态枚举
typedef enum {
    STATE_IDLE,
    STATE_HOMING,
    STATE_MOVING,
    STATE_ALARM
} MotorState;

// 事件枚举
typedef enum {
    EVT_NONE,
    EVT_START,
    EVT_HOME_DONE,
    EVT_MOVE_DONE,
    EVT_ERROR,
    EVT_RESET
} MotorEvent;

// 状态机处理函数
MotorState stateMachine(MotorState current, MotorEvent event) {
    switch (current) {
        case STATE_IDLE:
            if (event == EVT_START) {
                // 执行启动动作
                enableMotor();
                return STATE_HOMING;
            }
            break;
            
        case STATE_HOMING:
            if (event == EVT_HOME_DONE) {
                return STATE_MOVING;
            }
            if (event == EVT_ERROR) {
                disableMotor();
                return STATE_ALARM;
            }
            break;
            
        case STATE_MOVING:
            if (event == EVT_MOVE_DONE) {
                return STATE_IDLE;
            }
            if (event == EVT_ERROR) {
                disableMotor();
                return STATE_ALARM;
            }
            break;
            
        case STATE_ALARM:
            if (event == EVT_RESET) {
                clearAlarm();
                return STATE_IDLE;
            }
            break;
    }
    return current; // 无转移,保持当前状态
}

你看,代码结构非常清晰。每个状态只处理自己关心的事件,不相关的直接忽略。这就是状态机的魅力——逻辑不会乱,bug 无处藏。

避坑指南:我曾经在状态机里忘了处理“ALARM 状态下收到 EVT_START”的情况。结果客户按了启动按钮,设备没反应,他们以为死机了。记住:每个状态都要考虑“如果收到不该来的事件怎么办”。要么忽略,要么报错,但绝不能崩溃。

好了,状态机的基础就聊到这儿。三要素——状态、事件、转移——你记住了吗?下一节咱们聊聊怎么设计一个真正能用的运动控制状态机。


公众号:蓝海资料掘金营,微信deep3321