Skip to content

状态机:让单片机知道自己正在做什么 ​

导入 ​

刚开始写单片机程序时,我很喜欢直接堆 if:

c
if (key) motor_start();
if (stop_line) motor_stop();
if (error) led_on();

功能少的时候看不出问题。功能一多,就会出现“这个条件到底在哪个阶段生效”的疑问。

例如小车还没启动时检测到了停车线,应该停车吗?已经进入故障状态后,按下启动键还能直接启动吗?

状态机就是把“当前处于什么阶段”这件事明确写出来。

主要思想 ​

一个状态机主要包含三样东西:

  • 状态:系统现在正在做什么
  • 事件:发生了什么
  • 转移:发生事件后应该去哪个状态

以循迹小车为例:

text
待机 --按下启动键--> 循迹 --检测到停车线--> 停车
                         |
                         +--传感器异常--> 故障

状态机并不是什么复杂算法。它只是要求我在写功能前,先回答两个问题:

  1. 这段代码属于哪个状态?
  2. 什么条件下允许切换到另一个状态?

简单实现 ​

先定义状态:

c
typedef enum
{
    CAR_IDLE,
    CAR_TRACKING,
    CAR_STOP,
    CAR_ERROR
} CarState_t;

static CarState_t car_state = CAR_IDLE;

再根据当前状态分别处理:

c
void Car_StateMachine(void)
{
    switch (car_state)
    {
    case CAR_IDLE:
        Motor_Stop();
        if (Key_Start())
            car_state = CAR_TRACKING;
        break;

    case CAR_TRACKING:
        Track_Control();
        if (Sensor_Error())
            car_state = CAR_ERROR;
        else if (Find_Stop_Line())
            car_state = CAR_STOP;
        break;

    case CAR_STOP:
        Motor_Stop();
        break;

    case CAR_ERROR:
        Motor_Stop();
        LED_Error_On();
        break;
    }
}

把它放进固定周期任务中运行即可:

c
while (1)
{
    Car_StateMachine();
    Delay_ms(10);
}

实际工程里更推荐使用定时器或系统节拍调用,而不是阻塞式延时。这里使用 Delay_ms() 只是为了让例子足够直观。

进入状态时只执行一次 ​

有些操作只应该在切换状态时执行一次,例如清空PID:

c
void Car_SetState(CarState_t new_state)
{
    if (new_state == car_state)
        return;

    car_state = new_state;

    if (car_state == CAR_TRACKING)
        PID_Reset();
}

之后不要直接修改 car_state,统一调用 Car_SetState()。这样进入状态时的初始化不会散落在各处。

容易踩的坑 ​

一个状态做了太多事 ​

如果一个 case 已经写了几百行,说明状态还可以继续拆分。比如“启动”可以拆成传感器校准、电机准备、开始运行。

在很多文件里直接修改状态 ​

这样很难找到状态为什么突然变化。最好把状态切换集中在一个函数或一个模块中。

忘记处理非法状态 ​

变量被意外修改后,switch 可能一个分支也不进入。可以增加默认保护:

c
default:
    car_state = CAR_ERROR;
    Motor_Stop();
    break;

个人理解 ​

我以前觉得状态机只是把 if 换成 switch,写完后才发现真正有用的不是语法,而是它强迫我先整理程序的行为。

没有状态机时,我想的是“检测到这个条件就执行什么”;用了状态机后,我会多想一步:“系统现在允许响应这个条件吗?”

对于小项目,状态机可能显得有点多余。但项目只要开始加入按键、传感器、PID、保护和不同工作模式,它就能明显减少那种改了这里、另一处突然出问题的情况。

总结 ​

状态机就是:

text
当前状态 + 发生的事件 -> 下一个状态

它不能让代码自动变正确,但能把程序的运行过程写得更清楚。对单片机来说,知道“现在该做什么”和知道“现在不该做什么”同样重要。

Released under the MIT License.