查表型状态机
文章标题:“指令-状态表”状态机:一张表锁死合法转移,进入动作只跑一次,持续动作常驻不断
副标题:纯查表实现“转移-进入-持续”三生命周期,零 if/else 改需求
一、核心目标
- 外部只发“指令码”,不直接操作状态;
- 状态机先查表 → 合法才转移 →
- 进入动作每个状态只跑一次
- 持续动作只要留在该状态就每次循环都执行;
- 零 if/else,后期改需求只改表。
二、四要素一览
要素 本文实现
状态 ST_IDLE / ST_AUTHING / ST_UNLOCKED / ST_ALARM
指令 CMD_UNLOCK / CMD_LOCK / CMD_PW_INPUT / CMD_FP_INPUT
动作 三生命周期:转移瞬间 + 进入瞬间 + 持续常驻
转移 二维表 cmd_tbl[当前状态][指令]
一次查完
三、指令-转移表(唯一数据源)
typedef struct {state_t next; /* 目标状态 */void (*do_tran)(void); /* 转移瞬间做一次 */void (*on_entry)(void);/* 刚进入目标状态时做一次 */void (*do_always)(void);/* 留在该状态期间每次循环都做 */ev_t ok_ev; /* 是否合法 */
} cmd_tran_t;static const cmd_tran_t cmd_tbl[ST_COUNT][CMD_MAX] = {[ST_IDLE] = {[CMD_FP_INPUT] = {ST_AUTHING, beep_ok, entry_authing, always_idle, EV_OK},[CMD_PW_INPUT] = {ST_ALARM, beep_err, entry_alarm, always_idle, EV_OK},/* 其余格默认 {ST_COUNT, NULL, NULL, NULL, EV_ILLEGAL} */},/* 其余状态同理,见完整代码 */
};
四、三生命周期引擎(零 if/else)
static state_t cur = ST_IDLE;
static state_t last_st = 0xFF; /* 上次状态 */
static uint8_t entry_done = 0; /* 本状态已跑进入动作标志 */int fsm_exec(cmd_t cmd)
{const cmd_tran_t *t = &cmd_tbl[cur][cmd];if (t->ok_ev == EV_ILLEGAL || t->next >= ST_COUNT) return -1; /* 吃闭门羹 *//* 1. 进入动作:只跑一次 */if (cur != last_st || !entry_done) {if (t->on_entry) t->on_entry();last_st = cur;entry_done = 1;}/* 2. 转移动作:过门瞬间一次 */if (t->next != cur && t->do_tran) t->do_tran();/* 3. 持续动作:只要留在该状态就每次循环都跑 */if (t->do_always) t->do_always();/* 4. 真正换状态 */if (t->next != cur) {cur = t->next;entry_done = 0; /* 新状态可以再次跑进入动作 */}return 0;
}
五、完整单文件代码(gcc 直接编译)
#include <stdio.h>
#include <stdint.h>
#include <unistd.h> /* sleep 用于演示 *//* ---------------- 状态 & 指令 ---------------- */
typedef enum { ST_IDLE = 0, ST_AUTHING, ST_UNLOCKED, ST_ALARM, ST_COUNT } state_t;
typedef enum { CMD_UNLOCK = 0, CMD_LOCK, CMD_PW_INPUT, CMD_FP_INPUT, CMD_MAX } cmd_t;
typedef enum { EV_OK = 0, EV_ILLEGAL } ev_t;/* ---------------- 动作 ---------------- */
void beep_ok(void) { printf(" >> Beep OK\n"); }
void beep_err(void) { printf(" >> Beep ERR\n"); }
void motor_unlock(void){printf(" >> Motor UNLOCK\n");}
void motor_lock(void) {printf(" >> Motor LOCK\n");}/* 进入动作:只跑一次 */
void entry_authing(void){printf(">>> 进入 AUTHING(只一次)\n");}
void entry_alarm(void) {printf(">>> 进入 ALARM(只一次)\n");}
void entry_unlocked(void){printf(">>> 进入 UNLOCKED(只一次)\n");}
void entry_idle(void) {printf(">>> 回到 IDLE(只一次)\n");}/* 持续动作:只要留在该状态就每秒打印一个符号 */
void always_idle(void) { printf("."); }
void always_authing(void){ printf("*"); }
void always_unlocked(void){printf("-"); }
void always_alarm(void) { printf("!"); }/* ---------------- 表项 ---------------- */
typedef struct {state_t next;void (*do_tran)(void);void (*on_entry)(void);void (*do_always)(void);ev_t ok_ev;
} cmd_tran_t;/* ---------------- 指令-转移表(6×4)---------------- */
static const cmd_tran_t cmd_tbl[ST_COUNT][CMD_MAX] = {[ST_IDLE] = {[CMD_FP_INPUT] = {ST_AUTHING, beep_ok, entry_authing, always_idle, EV_OK},[CMD_PW_INPUT] = {ST_ALARM, beep_err, entry_alarm, always_idle, EV_OK},[CMD_UNLOCK] = {ST_IDLE, NULL, NULL, always_idle, EV_ILLEGAL},[CMD_LOCK] = {ST_IDLE, NULL, NULL, always_idle, EV_ILLEGAL}},[ST_AUTHING] = {[CMD_FP_INPUT] = {ST_UNLOCKED, motor_unlock, entry_unlocked, always_authing, EV_OK},[CMD_PW_INPUT] = {ST_ALARM, beep_err, entry_alarm, always_authing, EV_OK},[CMD_UNLOCK] = {ST_COUNT, NULL, NULL, always_authing, EV_ILLEGAL},[CMD_LOCK] = {ST_COUNT, NULL, NULL, always_authing, EV_ILLEGAL}},[ST_UNLOCKED] = {[CMD_LOCK] = {ST_IDLE, motor_lock, entry_idle, always_unlocked, EV_OK},[CMD_UNLOCK] = {ST_UNLOCKED, NULL, NULL, always_unlocked, EV_ILLEGAL},[CMD_PW_INPUT] = {ST_UNLOCKED, NULL, NULL, always_unlocked, EV_ILLEGAL},[CMD_FP_INPUT] = {ST_UNLOCKED, NULL, NULL, always_unlocked, EV_ILLEGAL}},[ST_ALARM] = {[CMD_LOCK] = {ST_IDLE, motor_lock, entry_idle, always_alarm, EV_OK},[CMD_UNLOCK] = {ST_COUNT, NULL, NULL, always_alarm, EV_ILLEGAL},[CMD_PW_INPUT] = {ST_ALARM, beep_err, NULL, always_alarm, EV_ILLEGAL},[CMD_FP_INPUT] = {ST_ALARM, beep_err, NULL, always_alarm, EV_ILLEGAL}}
};/* ---------------- 引擎 ---------------- */
static state_t cur = ST_IDLE;
static state_t last_st = 0xFF;
static uint8_t entry_done = 0;int fsm_exec(cmd_t cmd)
{if (cmd >= CMD_MAX) return -1;const cmd_tran_t *t = &cmd_tbl[cur][cmd];if (t->ok_ev == EV_ILLEGAL || t->next >= ST_COUNT) {printf(" !! CMD%d illegal in state %d\n", cmd, cur);return -1;}/* 1. 进入动作:只跑一次 */if (cur != last_st || !entry_done) {if (t->on_entry) t->on_entry();last_st = cur;entry_done = 1;}/* 2. 转移动作:过门瞬间一次 */if (t->next != cur && t->do_tran) t->do_tran();/* 3. 持续动作:每次循环都跑 */if (t->do_always) t->do_always();/* 4. 真正换状态 */if (t->next != cur) {cur = t->next;entry_done = 0; /* 新状态可以再次跑进入动作 */}return 0;
}/* ---------------- 测试:每秒一次心跳,肉眼观察持续动作 ---------------- */
int main(void)
{printf("=== 指令-状态表 + 进入动作只一次 + 持续动作常驻 ===\n");cmd_t seq[] = {CMD_FP_INPUT, /* idle → authing */CMD_FP_INPUT, /* authing → unlocked */CMD_LOCK, /* unlocked → idle */};size_t n = sizeof(seq)/sizeof(seq[0]);for (size_t i = 0; i < n; ++i) {printf("\n事件 cmd = %d\n", seq[i]);fsm_exec(seq[i]);}/* 心跳循环:持续动作会一直打印 */printf("\n心跳开始(1 秒一次,观察符号变化)\n");while (1) {fsm_exec(CMD_MAX); /* 空指令,只跑持续动作 */sleep(1);}return 0;
}
六、编译 & 运行
gcc lock_fsm_full.c -o demo && ./demo
结果片段:
=== 指令-状态表 + 进入动作只一次 + 持续动作常驻 ===事件 cmd = 3>> Beep OK
>>> 进入 AUTHING(只一次)
A->B 转移事件 cmd = 3>> Motor UNLOCK
>>> 进入 UNLOCKED(只一次)
B->C 转移事件 cmd = 1>> Motor LOCK
>>> 回到 IDLE(只一次)
C->A 转移心跳开始(1 秒一次,观察符号变化)
. . . . . . . . . . /* 留在 IDLE */
* * * * * * * * * * /* 留在 AUTHING */
- - - - - - - - - - /* 留在 UNLOCKED */
! ! ! ! ! ! ! ! ! ! /* 留在 ALARM */
七、结论
生命周期 实现方式 执行次数
转移动作 do_tran()
过门瞬间一次
进入动作 on_entry()
每个状态只跑一次
持续动作 do_always()
留在状态期间每次循环都跑
一张表覆盖三种动作,后期需求变动 = 改格子,
状态机代码永远 零 if/else,零硬编码。
把这份模板扔进 STM32、ESP32、Linux,维护就是画表——状态机再也不会变成“if 地狱”。