嵌入式 C 设计模式 · 状态模式:干掉你的巨型 switch-case
系列专栏:嵌入式 C 设计模式实战
本篇:状态模式(State Pattern)
🔍 问题:蓝牙耳机的连接状态机
先看一段真实代码。一个蓝牙耳机的连接管理:
c
void bt_event_handler(bt_event_t evt) {
switch (bt_state) {
case BT_POWER_OFF:
if (evt == EVT_POWER_ON) {
hci_init();
start_advertising();
bt_state = BT_ADVERTISING;
}
break;
case BT_ADVERTISING:
if (evt == EVT_CONNECT_REQ) {
hci_connect_accept();
start_sco_link();
bt_state = BT_CONNECTED;
} else if (evt == EVT_TIMEOUT) {
enter_sleep();
bt_state = BT_SLEEP;
}
break;
case BT_CONNECTED:
if (evt == EVT_DISCONNECT) {
start_advertising();
bt_state = BT_ADVERTISING;
} else if (evt == EVT_AUDIO_START) {
start_a2dp_stream();
/* 注意:这里没有状态转换?bug! */
}
break;
// ... 还有更多的 case ...
}
}
这个代码有什么问题?
- 一个函数越来越长 --- 每加一个状态,switch 多一个 case;每加一个事件,每个 case 多一个 if
- 转换逻辑散落各处 --- 你永远不知道从 A 到 B 的转换是不是在别的地方也有一份
- 容易漏掉处理 --- 比如上面
EVT_AUDIO_START在BT_CONNECTED状态里该转换到BT_STREAMING,但忘了写 - 非法状态转换无法拦截 --- 如果有人在
BT_POWER_OFF状态收到了EVT_AUDIO_START,会发生什么? - 多人协作灾难 --- 两个人同时改这个函数,merge conflict 是必然的
核心问题:状态和行为耦合在一起,还塞在同一个函数里。
💡 模式:状态模式
一句话
每个状态单独写一个文件/结构体,状态之间的转换由状态自己决定。
核心思想
┌─────────────────┐
│ 状态上下文 │ 持有当前状态指针
│ (Context) │
│ ┌─────────┐ │
│ │ 状态 A │ │ 每个状态是一个独立的对象
│ │ 状态 B │ │ 状态之间互相知道怎么转换
│ │ 状态 C │ │
│ └─────────┘ │
└─────────────────┘
上电
EVT_POWER_ON
EVT_CONNECT_REQ
EVT_TIMEOUT
EVT_WAKE
EVT_DISCONNECT
EVT_POWER_OFF
EVT_AUDIO_START
EVT_AUDIO_STOP
POWER_OFF
ADVERTISING
CONNECTED
SLEEP
STREAMING
- 把每个状态的处理逻辑封装到独立的"状态对象"中
- 状态之间自己决定"下个状态是谁"
- 上下文只需要调用当前状态的接口,不用管具体逻辑
在 C 语言中的实现方式
C 语言没有虚函数,我们用 struct + 函数指针 来实现多态:
c
/* 每个状态提供一个接口表 */
typedef struct {
void (*enter)(void); /* 进入该状态时调用 */
void (*exit)(void); /* 离开该状态时调用 */
void (*handle_event)(int); /* 处理事件 */
const char *name; /* 调试用 */
} state_t;
🖥️ 代码:用状态模式重构蓝牙连接管理
第一步:定义状态接口
c
/* state_machine.h */
#ifndef STATE_MACHINE_H
#define STATE_MACHINE_H
#include <stdint.h>
#include <stdbool.h>
/* 事件定义 */
typedef enum {
EVT_POWER_ON,
EVT_POWER_OFF,
EVT_CONNECT_REQ,
EVT_DISCONNECT,
EVT_AUDIO_START,
EVT_AUDIO_STOP,
EVT_TIMEOUT,
EVT_ERROR,
} event_t;
/* 状态接口表 */
typedef struct state_s state_t;
struct state_s {
void (*enter)(void);
void (*exit)(void);
state_t* (*handle_event)(event_t evt); /* 返回下一个状态 */
const char *name;
};
/* 状态机上下文 */
typedef struct {
state_t *current;
state_t *previous;
uint32_t entry_time; /* 进入当前状态的时间戳,用于超时判断 */
} state_machine_t;
/* 状态机 API */
void sm_init(state_machine_t *sm, state_t *init_state);
void sm_dispatch(state_machine_t *sm, event_t evt);
state_t* sm_get_current(state_machine_t *sm);
const char* sm_get_state_name(state_machine_t *sm);
#endif
第二步:实现状态机引擎
c
/* state_machine.c */
#include "state_machine.h"
#include <stdio.h>
void sm_init(state_machine_t *sm, state_t *init_state) {
sm->current = init_state;
sm->previous = NULL;
sm->entry_time = 0; /* 实际项目中用硬件定时器 */
printf("[SM] Init -> %s\n", init_state->name);
if (init_state->enter) {
init_state->enter();
}
}
void sm_dispatch(state_machine_t *sm, event_t evt) {
if (!sm || !sm->current) return;
state_t *next = sm->current->handle_event(evt);
if (next && next != sm->current) {
/* 状态转换 */
printf("[SM] %s --[%d]--> %s\n",
sm->current->name, evt, next->name);
if (sm->current->exit) {
sm->current->exit();
}
sm->previous = sm->current;
sm->current = next;
sm->entry_time = 0; /* 重置计时 */
if (next->enter) {
next->enter();
}
}
/* 如果 next == NULL,表示该事件在当前状态被忽略 */
/* 如果 next == current,表示留在当前状态(比如重复的通知) */
}
state_t* sm_get_current(state_machine_t *sm) {
return sm ? sm->current : NULL;
}
const char* sm_get_state_name(state_machine_t *sm) {
return sm && sm->current ? sm->current->name : "NULL";
}
第三步:定义具体状态
power_off 状态:
c
/* bt_state_power_off.c */
#include "state_machine.h"
#include <stdio.h>
/* 前置声明其他状态 */
extern state_t bt_state_advertising;
extern state_t bt_state_connected;
static state_t* on_power_off(event_t evt) {
switch (evt) {
case EVT_POWER_ON:
printf(" [PowerOff] Power ON -> start HCI\n");
hci_init();
return &bt_state_advertising; /* 转换到 advertising 状态 */
case EVT_TIMEOUT:
printf(" [PowerOff] Timeout -> enter deep sleep\n");
// enter_deep_sleep();
return NULL; /* NULL = 忽略,留在当前状态 */
default:
printf(" [PowerOff] Ignore event %d (not powered on yet)\n", evt);
return NULL;
}
}
static void enter_power_off(void) {
printf(" >>> Enter POWER_OFF\n");
hci_shutdown();
led_set_color(LED_OFF);
}
static void exit_power_off(void) {
printf(" <<< Exit POWER_OFF\n");
}
state_t bt_state_power_off = {
.enter = enter_power_off,
.exit = exit_power_off,
.handle_event = on_power_off,
.name = "POWER_OFF"
};
advertising 状态:
c
/* bt_state_advertising.c */
#include "state_machine.h"
#include <stdio.h>
extern state_t bt_state_power_off;
extern state_t bt_state_connected;
extern state_t bt_state_sleep;
static state_t* on_advertising(event_t evt) {
switch (evt) {
case EVT_CONNECT_REQ:
printf(" [Adv] Connect request received\n");
hci_accept_connection();
return &bt_state_connected;
case EVT_TIMEOUT:
printf(" [Adv] Advertising timeout\n");
return &bt_state_sleep;
case EVT_POWER_OFF:
printf(" [Adv] Power OFF\n");
return &bt_state_power_off;
default:
return NULL;
}
}
static void enter_advertising(void) {
printf(" >>> Enter ADVERTISING\n");
hci_start_advertising();
led_set_color(LED_FAST_BLINK); /* 快闪表示可配对 */
}
static void exit_advertising(void) {
printf(" <<< Exit ADVERTISING\n");
hci_stop_advertising();
}
state_t bt_state_advertising = {
.enter = enter_advertising,
.exit = exit_advertising,
.handle_event = on_advertising,
.name = "ADVERTISING"
};
connected 状态:
c
/* bt_state_connected.c */
#include "state_machine.h"
#include <stdio.h>
extern state_t bt_state_advertising;
extern state_t bt_state_streaming;
extern state_t bt_state_power_off;
static state_t* on_connected(event_t evt) {
switch (evt) {
case EVT_DISCONNECT:
printf(" [Connected] Disconnected\n");
return &bt_state_advertising;
case EVT_AUDIO_START:
printf(" [Connected] Audio stream start\n");
a2dp_open_stream();
return &bt_state_streaming;
case EVT_POWER_OFF:
printf(" [Connected] Power OFF\n");
hci_disconnect();
return &bt_state_power_off;
default:
return NULL;
}
}
static void enter_connected(void) {
printf(" >>> Enter CONNECTED\n");
led_set_color(LED_SLOW_BLINK);
}
static void exit_connected(void) {
printf(" <<< Exit CONNECTED\n");
}
state_t bt_state_connected = {
.enter = enter_connected,
.exit = exit_connected,
.handle_event = on_connected,
.name = "CONNECTED"
};
streaming(A2DP 流传输)状态:
c
/* bt_state_streaming.c */
#include "state_machine.h"
#include <stdio.h>
extern state_t bt_state_connected;
extern state_t bt_state_power_off;
static state_t* on_streaming(event_t evt) {
switch (evt) {
case EVT_AUDIO_STOP:
printf(" [Streaming] Audio stream stop\n");
a2dp_close_stream();
return &bt_state_connected;
case EVT_DISCONNECT:
printf(" [Streaming] Disconnected during streaming\n");
a2dp_close_stream();
return &bt_state_advertising;
case EVT_POWER_OFF:
printf(" [Streaming] Power OFF\n");
a2dp_close_stream();
hci_disconnect();
return &bt_state_power_off;
default:
return NULL;
}
}
static void enter_streaming(void) {
printf(" >>> Enter STREAMING\n");
led_set_color(LED_SOLID);
}
static void exit_streaming(void) {
printf(" <<< Exit STREAMING\n");
}
state_t bt_state_streaming = {
.enter = enter_streaming,
.exit = exit_streaming,
.handle_event = on_streaming,
.name = "STREAMING"
};
第四步:使用
c
/* main.c */
#include "state_machine.h"
state_machine_t g_bt_sm;
extern state_t bt_state_power_off;
int main(void) {
system_init();
sm_init(&g_bt_sm, &bt_state_power_off);
while (1) {
event_t evt = get_next_event();
sm_dispatch(&g_bt_sm, evt);
}
}
📊 重构前后对比
| 维度 | 重构前 | 重构后 |
|---|---|---|
| 单个函数长度 | 200+ 行 | 每个状态 < 50 行 |
| 新增状态 | 改 switch,加 case,担心影响其他 case | 新建一个 .c 文件,注册到状态表 |
| 新增事件 | 每个 case 加一个 if | 只改关心的状态的函数 |
| 非法转换 | 无拦截,运行时可能崩溃 | 每个状态自行决定合法事件,非法事件直接忽略 |
| 多人协作 | 都在改同一个函数,merge conflict | 各改各的文件,零冲突 |
| 调试 | printf 只能在函数里散落添加 | enter/exit 有统一 hook,转换有日志 |
🔄 进阶:层次状态机
项目做大了你会发现一个问题------多个状态有相同的行为。
例如 BT_CONNECTED 和 BT_STREAMING 都会响应 EVT_DISCONNECT,代码是重复的。
解法:层次状态机(HSM,Hierarchical State Machine)
c
/* 父状态:处理所有"已连接"状态的公共行为 */
static state_t* on_connected_base(event_t evt) {
switch (evt) {
case EVT_DISCONNECT:
/* 公共断开处理 */
return &bt_state_advertising;
case EVT_POWER_OFF:
return &bt_state_power_off;
default:
return NULL;
}
}
具体状态处理自己的专属事件,处理不了的传给父状态:
c
static state_t* on_streaming(event_t evt) {
switch (evt) {
case EVT_AUDIO_STOP:
return &bt_state_connected;
default:
return on_connected_base(evt); /* 交给父状态处理 */
}
}
层次状态机在蓝牙协议栈里用得很多------HCI、GATT、A2DP 的协议状态机几乎全是 HSM。
✍️ 练习
-
加一个新状态: 给这个状态机加一个
BT_RECONNECTING(断线重连)状态。从BT_CONNECTED收到EVT_LINK_LOSS进入,尝试重连成功后回到BT_CONNECTED,失败回到BT_ADVERTISING -
思考题: 你现在的项目里,哪个 switch-case 最大?如果用状态模式重构,会拆成几个文件?
-
可选: 给状态机加一个
state_history[10]环形缓冲区,记录最近 10 次状态转换,调试时打印出来
总结
状态模式是嵌入式开发最重要的模式,没有之一。
蓝牙协议栈、按键处理、充电管理、OTA 升级------几乎每个嵌入式模块本质上都是一个状态机。用状态模式来组织代码,不是"锦上添花",而是"基本操作"。
核心:每个状态一个文件,状态转换由状态自己决定。
下篇预告:策略模式(Strategy Pattern)------ 如何优雅地切换 EQ、编解码器、ANC 模式?