状态机(FSM - Finite State Machine)是嵌入式开发中最常用的设计模式之一。它能够帮助开发者将复杂的逻辑分解为清晰的状态和转换,特别适合处理按键检测、协议解析、模式切换等场景。

什么是状态机?

状态机是一种抽象的计算模型,由以下要素组成:

要素 说明
状态 (State) 系统在某一时刻的稳定条件或工作模式
事件 (Event) 触发状态转换的外部信号或内部条件
转换 (Transition) 状态之间的有向转移
动作 (Action) 状态转换时执行的操作

状态转移图

状态机可以用状态转移图直观地表示:

                    +-----------------+
                    |                 |
                    |    IDLE         |<---------------+
                    |                 |                |
                    +--------+--------+                 |
                             |                          |
                    事件:EV_KEY_PRESS                   |
                             |                          |
                             v                          |
                    +--------+--------+                |
                    |                 |                |
                    |   PRESSED       |-------+        |
                    |                 |       |        |
                    +-----------------+        |        |
                                              |        |
                              定时器超时      |        | 按键释放
                              (消抖完成)      |        |
                                              |        |
                                              v        |
                    +-----------------+        +--------+------>+
                    |                 |                        |
                    |   RELEASED      |---------------------->+
                    |                 |     消抖完成,进入IDLE
                    +-----------------+

核心要素详解

状态定义

typedef enum {
    STATE_IDLE,       // 空闲状态
    STATE_PRESSED,    // 按键按下(消抖中)
    STATE_ACTIVE,     // 按键确认按下
    STATE_RELEASED,   // 按键释放(消抖中)
} KeyState;

事件定义

typedef enum {
    EV_KEY_PRESS,      // 按键按下
    EV_KEY_RELEASE,    // 按键释放
    EV_TIMEOUT,        // 定时器超时
} KeyEvent;

状态表

typedef struct {
    KeyState current_state;
    KeyEvent event;
    KeyState next_state;
    void (*action)(void);  // 转换时执行的动作
} StateTransition;

// 状态转换表
static const StateTransition g_trans_table[] = {
    {STATE_IDLE,      EV_KEY_PRESS,  STATE_PRESSED,  Start_DebounceTimer},
    {STATE_PRESSED,   EV_TIMEOUT,    STATE_ACTIVE,   On_KeyConfirmed},
    {STATE_ACTIVE,    EV_KEY_RELEASE,STATE_RELEASED, Start_DebounceTimer},
    {STATE_RELEASED,  EV_TIMEOUT,    STATE_IDLE,     On_KeyReleased},
};

实现方式

1. Switch-Case 实现

最简单直观的方式:

typedef enum {
    STATE_IDLE,
    STATE_HEATING,
    STATE_KEEPING,
    STATE_COOLING
} WaterHeaterState;

static WaterHeaterState g_state = STATE_IDLE;
static uint32_t g_temperature = 25;
static uint32_t g_target_temp = 60;

void WaterHeater_Process(void) {
    switch (g_state) {
        case STATE_IDLE:
            g_temperature = 25;
            if (g_target_temp > 25) {
                g_state = STATE_HEATING;
                Heater_On();
            }
            break;

        case STATE_HEATING:
            if (g_temperature >= g_target_temp) {
                g_state = STATE_KEEPING;
                Heater_Off();
            }
            break;

        case STATE_KEEPING:
            if (g_temperature < g_target_temp - 5) {
                g_state = STATE_HEATING;
                Heater_On();
            } else if (g_target_temp == 0) {
                g_state = STATE_COOLING;
                Heater_Off();
            }
            break;

        case STATE_COOLING:
            if (g_temperature <= 30) {
                g_state = STATE_IDLE;
            }
            break;

        default:
            g_state = STATE_IDLE;
            break;
    }
}

// 定时器中断中更新温度和处理超时
void Timer5ms_IRQHandler(void) {
    static uint8_t counter = 0;
    counter++;

    if (g_state == STATE_HEATING) {
        g_temperature += 2;  // 每5ms升温2度
    } else if (g_state == STATE_COOLING) {
        g_temperature -= 1; // 自然冷却
    }

    // 某些状态需要超时检测
    if (counter >= 200) {  // 1秒
        counter = 0;
        On_Timeout();
    }
}

优点:

  • 逻辑清晰,易于理解
  • 调试方便,可以单步跟踪状态变化
  • 适合状态较少的场景

缺点:

  • 状态和事件增多时,代码膨胀快
  • 状态转换逻辑分散,不易维护
  • 不易扩展新的状态转换

2. 状态表驱动实现

将状态转换关系数据化,更适合复杂状态机:

#include <stdint.h>
#include <stdbool.h>

// 状态和事件定义
typedef enum {
    ST_IDLE = 0,
    ST_CONNECTING,
    ST_CONNECTED,
    ST_DISCONNECTING,
    ST_ERROR,
    ST_MAX
} ConnState;

typedef enum {
    EV_START,
    EV_CONNECTED,
    EV_DISCONNECT,
    EV_TIMEOUT,
    EV_ERROR,
    EV_MAX
} ConnEvent;

// 状态转换表
typedef struct {
    ConnState next_state;
    void (*action)(void *param);
} Transition;

static const Transition g_fsm[ST_MAX][EV_MAX] = {
    // ST_IDLE
    {
        [EV_START]      = {ST_CONNECTING, do_connect},
        [EV_ERROR]     = {ST_ERROR,      do_error_handler},
    },
    // ST_CONNECTING
    {
        [EV_CONNECTED] = {ST_CONNECTED,  do_on_connected},
        [EV_TIMEOUT]   = {ST_ERROR,      do_timeout_handler},
        [EV_ERROR]     = {ST_ERROR,      do_error_handler},
    },
    // ST_CONNECTED
    {
        [EV_DISCONNECT]= {ST_DISCONNECTING, do_disconnect},
        [EV_ERROR]     = {ST_ERROR,         do_error_handler},
    },
    // ST_DISCONNECTING
    {
        [EV_CONNECTED] = {ST_IDLE,  do_cleanup},  // 完成清理
        [EV_TIMEOUT]   = {ST_IDLE,  do_cleanup},
    },
    // ST_ERROR
    {
        [EV_START]     = {ST_IDLE,  do_reset},
    },
};

// 状态机上下文
typedef struct {
    ConnState state;
    void *user_data;
    uint32_t error_count;
} FSM_Context;

static FSM_Context g_fsm_ctx = {.state = ST_IDLE, .error_count = 0};

// 状态机执行函数
bool FSM_HandleEvent(ConnEvent event, void *param) {
    if (g_fsm_ctx.state >= ST_MAX || event >= EV_MAX) {
        return false;
    }

    const Transition *t = &g_fsm[g_fsm_ctx.state][event];
    if (t->action) {
        t->action(param);
    }

    if (t->next_state != g_fsm_ctx.state) {
        g_fsm_ctx.state = t->next_state;
        return true;
    }
    return false;
}

优点:

  • 状态转换逻辑集中,易于维护
  • 新增状态和转换只需修改表数据
  • 适合复杂状态机
  • 便于自动化生成

缺点:

  • 需要额外的存储空间
  • 调试时不易查看状态流程
  • 初始设计和表构建需要更多精力

3. 层次状态机(HSM)

对于复杂系统,可以使用层次状态机,将相关状态分组:

typedef struct StateNode {
    const char *name;
    struct StateNode *parent;      // 父状态
    struct StateNode *initial;     // 初始子状态
    struct StateNode *active;       // 当前活跃子状态
} StateNode;

// 定义状态层次结构
static StateNode state_running = {
    .name = "RUNNING",
};

static StateNode state_error = {
    .name = "ERROR",
};

static StateNode state_machine = {
    .name = "ROOT",
    .parent = NULL,
    .initial = &state_running,
};

// 状态进入/退出动作
void StateMachine_Enter(StateNode *state) {
    printf("Entering: %s\n", state->name);
    if (state->initial) {
        state->active = state->initial;
        StateMachine_Enter(state->active);
    }
}

void StateMachine_Exit(StateNode *state) {
    if (state->active) {
        StateMachine_Exit(state->active);
    }
    printf("Exiting: %s\n", state->name);
}

void StateMachine_Transition(StateNode *target) {
    // 计算LCA,退出并重新进入
    StateMachine_Exit(&state_machine);
    state_machine.active = target;
    StateMachine_Enter(&state_machine);
}

实际应用案例

案例1:按键消抖处理

typedef enum {
    KEY_STATE_IDLE,
    KEY_STATE_DEBOUNCE,
    KEY_STATE_PRESSED,
    KEY_STATE_LONGPRESS,
    KEY_STATE_RELEASE
} KeyState;

typedef struct {
    KeyState state;
    uint32_t press_time;
    uint8_t key_id;
} KeyContext;

#define LONGPRESS_TIME_MS 1000
#define DEBOUNCE_TIME_MS  20

void KeyFSM_Init(KeyContext *ctx) {
    ctx->state = KEY_STATE_IDLE;
    ctx->press_time = 0;
}

void KeyFSM_Event(KeyContext *ctx, uint8_t event, uint32_t now) {
    switch (ctx->state) {
        case KEY_STATE_IDLE:
            if (event == KEY_PRESS) {
                ctx->press_time = now;
                ctx->state = KEY_STATE_DEBOUNCE;
            }
            break;

        case KEY_STATE_DEBOUNCE:
            if (event == KEY_RELEASE) {
                ctx->state = KEY_STATE_IDLE;
            } else if (event == KEY_TIMEOUT && (now - ctx->press_time) >= DEBOUNCE_TIME_MS) {
                ctx->state = KEY_STATE_PRESSED;
                On_KeyPressed(ctx->key_id);
            }
            break;

        case KEY_STATE_PRESSED:
            if (event == KEY_RELEASE) {
                ctx->state = KEY_STATE_RELEASE;
            } else if (event == KEY_TIMEOUT && (now - ctx->press_time) >= LONGPRESS_TIME_MS) {
                ctx->state = KEY_STATE_LONGPRESS;
                On_KeyLongPressed(ctx->key_id);
            }
            break;

        case KEY_STATE_LONGPRESS:
            if (event == KEY_RELEASE) {
                ctx->state = KEY_STATE_RELEASE;
            }
            break;

        case KEY_STATE_RELEASE:
            if (event == KEY_TIMEOUT) {
                On_KeyReleased(ctx->key_id);
                ctx->state = KEY_STATE_IDLE;
            }
            break;
    }
}

案例2:菜单系统

typedef enum {
    MENU_MAIN,
    MENU_SETTINGS,
    MENU_ABOUT,
    MENU_SETTINGS_DISPLAY,
    MENU_SETTINGS_SOUND
} MenuState;

typedef struct {
    MenuState state;
    uint8_t selection;
} MenuContext;

static const char *menu_items[][4] = {
    [MENU_MAIN]               = {"Start", "Settings", "About", NULL},
    [MENU_SETTINGS]           = {"Display", "Sound", "Back", NULL},
    [MENU_SETTINGS_DISPLAY]  = {"Brightness", "Timeout", "Back", NULL},
    [MENU_SETTINGS_SOUND]     = {"Volume", "KeySound", "Back", NULL},
    [MENU_ABOUT]              = {"Version 1.0", "", "Back", NULL},
};

void Menu_HandleKey(MenuContext *ctx, uint8_t key) {
    switch (key) {
        case KEY_UP:
            if (ctx->selection > 0) ctx->selection--;
            break;

        case KEY_DOWN:
            if (menu_items[ctx->state][ctx->selection + 1] != NULL) {
                ctx->selection++;
            }
            break;

        case KEY_ENTER:
            Menu_Navigate(ctx);
            break;

        case KEY_BACK:
            Menu_Back(ctx);
            break;
    }
    Menu_UpdateDisplay(ctx);
}

void Menu_Navigate(MenuContext *ctx) {
    switch (ctx->state) {
        case MENU_MAIN:
            if (ctx->selection == 0) StartProcess();
            else if (ctx->selection == 1) ctx->state = MENU_SETTINGS;
            else if (ctx->selection == 2) ctx->state = MENU_ABOUT;
            ctx->selection = 0;
            break;
        // ... 其他状态处理
    }
}

设计建议

1. 状态定义要完整无遗漏

// 好的实践:使用枚举确保所有状态都被处理
typedef enum {
    #define STATE(name) name,
    #include "states.def"
    #undef STATE
    STATE_COUNT
} State;

2. 处理所有可能的转换

使用断言或默认分支处理非法转换:

switch (state) {
    case STATE_A: ...; break;
    case STATE_B: ...; break;
    default:
        ASSERT(0, "Unexpected state transition");
}

3. 进入和退出动作

为每个状态定义清楚的生命周期:

void State_Enter(State *s) {
    // 初始化状态资源
}

void State_Exit(State *s) {
    // 清理状态资源
}

4. 状态机文档化

编写清晰的状态转换文档:

/*
 * 状态机: UART通信管理
 *
 * 状态:
 *   IDLE       - 空闲,等待发送或接收
 *   SENDING    - 正在发送数据
 *   RECEIVING  - 正在接收数据
 *   ERROR      - 通信错误
 *
 * 转换:
 *   IDLE + send_req -> SENDING
 *   IDLE + recv_data -> RECEIVING
 *   SENDING + complete -> IDLE
 *   ANY + error -> ERROR
 *   ERROR + reset -> IDLE
 */

调试技巧

  1. 日志输出:状态转换时输出日志
  2. 断点调试:在状态切换处设置断点
  3. 可视化:使用状态机可视化工具
  4. 测试用例:为每个状态转换编写测试

总结

状态机是嵌入式开发中不可或缺的利器:

  • 将复杂逻辑分解为清晰的状态和转换
  • 提高代码可维护性和可测试性
  • 多种实现方式,适用于不同复杂度场景
  • 结合设计文档,获得更好的开发体验

掌握状态机设计,让你的嵌入式代码更加专业可靠。