状态机(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
*/
调试技巧
- 日志输出:状态转换时输出日志
- 断点调试:在状态切换处设置断点
- 可视化:使用状态机可视化工具
- 测试用例:为每个状态转换编写测试
总结
状态机是嵌入式开发中不可或缺的利器:
- 将复杂逻辑分解为清晰的状态和转换
- 提高代码可维护性和可测试性
- 多种实现方式,适用于不同复杂度场景
- 结合设计文档,获得更好的开发体验
掌握状态机设计,让你的嵌入式代码更加专业可靠。