# efsm **Repository Path**: mengplus/efsm ## Basic Information - **Project Name**: efsm - **Description**: 这个项目提供了一个用于实现有限状态机(Finite State Machine, FSM)的简单框架,用于管理状态和处理事件。该框架支持状态的初始化、退出、周期性任务执行,以及状态切换和事件处理。 - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2024-01-31 - **Last Updated**: 2026-09-07 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # State Machine Framework (EFSM) ## 项目简介 EFSM(Event-based Finite State Machine,基于事件的有限状态机)是一个轻量级、灵活的嵌入式系统状态机框架。 **核心特性:** - **事件驱动**:基于事件触发状态切换,支持系统命令与用户事件分离 - **层级状态机(HSM)**:通过 `super` 指针 + `delegate` 回调实现父态委托,事件自动上抛 - **步骤系统**:支持线性执行、时间限制、条件跳转、switch/case 多路分支、结构化循环(while/do-while/for)、break 终止 - **事件队列**:可实例化的异步事件队列,支持中断投递 + 主循环排空,零拷贝设计 - **模块化设计**:状态/步骤可自定义,支持便捷宏(EFSM_MANAGE_OF 等)简化开发 - **轻量级**:代码体积约 1.5-2 KB(视编译优化等级) --- ## API 参考 ### 状态机管理 (efsm_manage_t) | 函数 | 说明 | |------|------| | `efsm_manage_init(obj)` | 初始化状态机结构体(清零) | | `efsm_register(obj)` | 注册到全局链表,自动调用 `ops->init` | | `efsm_remove(obj)` | 从链表移除,自动调用 `ops->exit` | | `efsm_manage_tick()` | 遍历所有注册状态机,调用 `ops->tick` | | `efsm_manage_tick_user(obj)` | 手动触发单个状态机的 tick | **注意:** `efsm_manage_init` 会清零整个结构体,包括 `user_data`。如需保留 `user_data`,请在 init 后重新设置。 ### 状态切换 | 函数 | 说明 | |------|------| | `efsm_transition(obj, next_state)` | 切换到新状态(自动调用旧状态 exit 和新状态 init) | | `efsm_manage_get_state(obj)` | 获取当前状态指针 | | `efsm_manage_get_userdata(obj)` | 获取用户数据 | **hold_on 机制:** 设置 `obj->hold_on = 1` 后,`efsm_transition` 将被阻止。 ### 事件处理 | 函数 | 说明 | |------|------| | `efsm_event_process(obj, cmd, param)` | 处理事件(系统命令 0x00-0xFF,用户命令 0x100+) | | `efsm_event_broadcast(cmd, param)` | 广播事件到所有注册状态机 | | `efsm_manage_control(obj, cmd, param)` | 调用状态机的 control 回调 | | `efsm_event_queue_init(q)` | 初始化事件队列实例 | | `efsm_event_queue_post(q, obj, cmd, param)` | 投递异步事件(零拷贝,param 存活期须至排空前) | | `efsm_event_queue_drain(q)` | 排空队列:依次对各事件执行 `efsm_event_process` | | `efsm_event_queue_is_empty(q)` | 判断队列是否为空 | **系统命令:** - `EFSM_CMD_STOP` - 停止 tick 执行(stop=1) - `EFSM_CMD_START` - 恢复 tick 执行(stop=0) - `EFSM_CMD_HOLD` - 设置 hold_on 标志 ### 层级状态机(HSM 父态委托) `efsm_state_t` 提供 `super`(父状态指针)与 `efsm_state_ops_t.delegate` 回调,实现**轻量父态委托**: - `delegate` 返回 `true` = 已处理(消费事件,停止上抛);返回 `false` = 未处理,框架沿 `super` 向父状态委托。 - 若某状态未设置 `delegate`:沿用传统 `action`(一旦设置即消费,不与父态交互,保持向后兼容)。 - 事件处理自**当前状态**向上逐级尝试,直到某状态消费或到顶(`super` 为 NULL)。 ```c static bool parent_delegate(efsm_state_t *obj, uint32_t cmd, efsm_param_t *param) { if (cmd == CMD_COMMON) { /* 公共处理 */ return true; } return false; /* 未处理,继续上抛 */ } static const efsm_state_ops_t parent_ops = { .delegate = parent_delegate, /* ... */ }; efsm_state_t parent_state = { .ops = &parent_ops, .super = NULL }; efsm_state_t child_state = { .ops = &child_ops, .super = &parent_state }; /* 父态委托 */ ``` ### 事件队列(可实例化) 队列是**独立对象**(`efsm_event_queue_t`),由调用方声明实例并管理归属: 每个状态机可持有一个队列,也可多个状态机共享一个队列,实现多状态机/多域隔离。 ```c efsm_event_queue_t my_q; /* 每状态机一个队列 */ efsm_event_queue_init(&my_q); /* 中断/其他上下文投递,主循环安全点统一排空 */ efsm_event_queue_post(&my_q, &fsm, 0x100, NULL); /* ... */ while (!efsm_event_queue_is_empty(&my_q)) efsm_event_queue_drain(&my_q); ``` ### 便捷辅助宏(可选,不影响原始 API) 在状态/步骤处理函数内简化"取管理器/用户数据/名称/命令"的样板: | 宏 | 等价替换 | 说明 | |----|----------|------| | `EFSM_MANAGE_OF(st)` | `obj->parent` | 从状态指针取所属状态机管理器 | | `EFSM_USERDATA_OF(st)` | `obj->parent->user_data` | 取用户数据(parent 空返回 `NULL`;返回 `void*` 需强转) | | `EFSM_STATE_NAME(st)` | `obj->ops->name` | 取状态名称(ops 空返回 `"?"`) | | `EFSM_USER_CMD(n)` | `EFSM_STATE_USER_CMD_BASE+n` | 用户命令快速构造 | ```c typedef struct { const char *who; } Ctx; static void action(efsm_state_t *obj, uint32_t cmd, efsm_param_t *param) { Ctx *ctx = (Ctx *)EFSM_USERDATA_OF(obj); /* 替代 obj->parent->user_data 强转 */ if (cmd == EFSM_USER_CMD(1)) { /* 替代 EFSM_STATE_USER_CMD_BASE+1 */ efsm_transition(EFSM_MANAGE_OF(obj), &next); /* 替代 obj->parent */ } } ``` ### 步骤系统 (efsm_step_t) 步骤系统用于在状态内部线性执行多个操作。 | 宏 | 说明 | |----|------| | `EFSM_STEP_DEFAULT(name, type, set, check)` | 基础步骤定义 | | `EFSM_STEP_TIMED_DEFAULT(name, set, check, min, max)` | 带时间限制的步骤 | | `EFSM_STEP_CONDITIONAL_DEFAULT(name, set, check, true, false)` | 带条件跳转的步骤 | | `EFSM_STEP_CASE(key, jump)` | 多路分支 case 表项 | | `EFSM_STEP_SWITCH_DEFAULT(name, key, case_vec, case_num, default)` | 多路分支步骤(switch/case) | | `EFSM_STEP_LOOP_DEFAULT(name, mode, cond, body, body_num)` | 循环块(while/do-while),body 为子步骤表 | | `EFSM_STEP_FOR_DEFAULT(name, cond, init, incr, body, body_num)` | for 循环块(含 init/incr) | | `EFSM_STEP_ADDR(step)` | 获取步骤指针(用于 step_vec 数组) | **步骤类型:** - `EFSM_STEP_BASE` - 基础步骤,check 返回 true 时前进 - `EFSM_STEP_TIMED` - 时间限制步骤,支持 min/max 时间约束 - `EFSM_STEP_CONDITIONAL` - 条件跳转步骤,根据 check 结果跳转 - `EFSM_STEP_SWITCH` - 多路分支步骤,按 key 回调返回值匹配 case 表跳转到 N 个目标之一 - `EFSM_STEP_WARNING` - **保留,未实现**(异常处理预留;设定该类型会触发 ERROR 不前进) - `EFSM_STEP_CUSTOM` - **保留,未实现**(自定义预留;设定该类型会触发 ERROR 不前进) - `EFSM_STEP_LOOP` - 结构化循环块(容器),内嵌 body 子步骤表,由条件驱动重复 **步骤执行函数:** - `efsm_step_init(ss, vec, num, tick)` - 初始化步骤状态 - `efsm_step_process(ss, tick)` - 执行当前步骤,返回 `true` 推进中、`false` 步骤表结束;配合 `efsm_step_result()` 细分结束原因(`DONE`/`BREAK`/`ERROR`) - `efsm_step_set(ss, step, tick)` - 手动跳转到指定步骤 - `efsm_step_break(ss)` - 请求终止当前步骤表(break) - `efsm_step_result(ss)` - 查询最近一次执行结果 `efsm_step_result_t` > **前提**:`tick` 参数须**单调非减**(同一运行器内不反复回退),内部用无符号减法计算经过时间。 --- ## 使用示例 ### 示例 1:基本状态切换 ```c #include "efsm.h" /* 定义状态 */ static void state_a_init(efsm_state_t *obj) { printf("Enter A\n"); } static void state_a_exit(efsm_state_t *obj) { printf("Exit A\n"); } static void state_a_action(efsm_state_t *obj, uint32_t cmd, efsm_param_t *param) { if (cmd == 0x100) efsm_transition(obj->parent, &state_b); } static const efsm_state_ops_t ops_a = { .name = "A", .init = state_a_init, .exit = state_a_exit, .action = state_a_action, }; efsm_state_t state_a = {.ops = &ops_a}; static void state_b_action(efsm_state_t *obj, uint32_t cmd, efsm_param_t *param) { if (cmd == 0x101) efsm_transition(obj->parent, &state_a); } static const efsm_state_ops_t ops_b = { .name = "B", .action = state_b_action, }; efsm_state_t state_b = {.ops = &ops_b}; /* 使用状态机 */ efsm_manage_t fsm; efsm_manage_init(&fsm); efsm_register(&fsm); efsm_transition(&fsm, &state_a); efsm_event_process(&fsm, 0x100, NULL); // 切换到 B ``` ### 示例 2:步骤管理(servo-F401 风格) ```c /* 步骤定义 */ static bool step_cfg_set(efsm_state_step_t *self) { /* 下发配置 */ return true; } static bool step_cfg_check(efsm_state_step_t *self) { return config_done(); } static bool step_start_set(efsm_state_step_t *self) { /* 发送启动 */ return true; } static bool step_start_check(efsm_state_step_t *self) { return start_done(); } /* 步骤数组 */ static efsm_step_t g_steps[] = { EFSM_STEP_DEFAULT("cfg", EFSM_STEP_BASE, step_cfg_set, step_cfg_check), EFSM_STEP_DEFAULT("start", EFSM_STEP_BASE, step_start_set, step_start_check), }; static const_efsm_step_t g_step_vec[] = { EFSM_STEP_ADDR(g_steps[0]), EFSM_STEP_ADDR(g_steps[1]), }; /* 状态处理 */ static void start_action(efsm_state_t *obj, uint32_t cmd, efsm_param_t *param) { /* 由"state 成员指针"取回所属步骤运行器容器,不依赖成员布局 */ efsm_state_step_t *ss = EFSM_STEP_RUNNER(obj); if (!efsm_step_process(ss, rt_tick_get_millisecond())) { /* 所有步骤完成,切换到下一个状态 */ efsm_transition(obj->parent, &state_running); } } ``` ### 示例 3:定时步骤 ```c static bool wait_check(efsm_state_step_t *self) { /* 等待 1000ms 后返回 true */ uint32_t elapsed = rt_tick_get_millisecond() - self->state.timestamp; return elapsed >= 1000; } static efsm_step_timed_t g_wait_step = EFSM_STEP_TIMED_DEFAULT( "wait", NULL, wait_check, 0, 2000 /* 最多等 2s */ ); static const_efsm_step_t g_vec[] = { EFSM_STEP_ADDR(g_wait_step), }; ``` ### 示例 4:条件跳转 ```c static bool check_condition(efsm_state_step_t *self) { return some_condition_is_true(); } static efsm_step_conditional_t g_cond_step = EFSM_STEP_CONDITIONAL_DEFAULT( "check", NULL, check_condition, 2, 1 /* 条件真跳 2 步,假跳 1 步 */ ); static const_efsm_step_t g_vec[] = { &g_step1, EFSM_STEP_ADDR(g_cond_step), &g_step3, }; ``` ### 示例 5:多路分支(switch/case) > **注意**:`default_jump=0` 表示**停留在当前步骤**(不前进),而非前进 1 步。 `EFSM_STEP_SWITCH` 按 `key` 回调返回值匹配 case 表,一次跳到 N 个目标之一。 适合按枚举/档位/命令做多路分派(二路的 CONDITIONAL 不够时用这个)。 ```c /* 返回当前要分派的档位/枚举 */ static int32_t current_gear(efsm_state_step_t *self) { return read_gear_select(); /* 0=空挡 1/2/3=档位 */ } /* 档位对应处理步骤(各自为一段 BASE 表) */ static const efsm_step_switch_case_t g_gear_cases[] = { EFSM_STEP_CASE(1, 2), /* 档位1 -> 前进2步 */ EFSM_STEP_CASE(2, 3), /* 档位2 -> 前进3步 */ EFSM_STEP_CASE(3, 4), /* 档位3 -> 前进4步 */ }; static efsm_step_switch_t g_gear_switch = EFSM_STEP_SWITCH_DEFAULT( "gear", current_gear, g_gear_cases, 3, 1 /* 未匹配(0/空挡)默认前进1步 */ ); static const_efsm_step_t g_vec[] = { EFSM_STEP_ADDR(g_gear_switch), /* [0] 此处按档位分派 */ &g_gear1, /* [1] 空挡处理 */ &g_gear1a, /* [2] 档位1目标 */ &g_gear2a, /* [3] 档位2目标 */ &g_gear3a, /* [4] 档位3目标 */ }; ``` - `jump` 为相对当前步骤的偏移(前进为正、后退为负),与 CONDITIONAL 一致。 - `key` 为 NULL 时视为未命中,直接走 `default_jump`(0=前进1步)。 ### 示例 6:结构化循环块(while/do-while/for) > **补充**:源码还提供了 `EFSM_STEP_DOLOOP_DEFAULT` 宏,专门用于 do-while 语义(先执行一轮再判条件)。 循环块把"一整段要重复的步骤"独立成子步骤表,作为外层表中的一个**容器步骤**; 外层表增删步骤不影响循环内部,无需手算 CONDITIONAL 相对偏移。 适合多通道重复测量、轮询等待、计次重试等场景。 ```c /* 一个"测量周期"的步骤体(独立子表) */ static bool cycle_body_set(efsm_state_step_t *self) { /* 取气/测量... */ return true; } static bool cycle_body_check(efsm_state_step_t *self) { return done(); } static efsm_step_t g_cycle_step = EFSM_STEP_DEFAULT("cycle", EFSM_STEP_BASE, cycle_body_set, cycle_body_check); static const_efsm_step_t g_cycle_body[] = { &g_cycle_step }; /* 可多个步骤 */ /* 循环条件:是否还有下一个通道要测 */ static bool more_channel(efsm_state_step_t *self) { return has_next_channel(); } /* do-while:先测完一个周期,再判是否还有下一通道 */ static efsm_step_loop_t g_channel_loop = EFSM_STEP_LOOP_DEFAULT("channel_loop", EFSM_LOOP_DO, more_channel, g_cycle_body, 1); /* 外层表:循环只占一个位置;往表头/表尾插别的步骤都不会让循环错位 */ static const_efsm_step_t g_vec[] = { &g_prepare, EFSM_STEP_ADDR(g_channel_loop), /* 等价 do{...}while(more_channel) */ &g_finish, }; ``` - `while(cond){body}` 用 `EFSM_LOOP_WHILE`(先判后进,条件假则 body 一次不执行)。 - `for(init;cond;incr){body}` 用 `EFSM_STEP_FOR_DEFAULT`(含 init/incr 回调,计数/复位用)。 - body 内可用任意步骤(BASE/TIMED/CONDITIONAL/嵌套 LOOP),逐 tick 推进,节奏与顶层一致。 - 循环体多步/含等待时,外层每次 tick 只推进 body 一步;单步即时完成时可能同 tick 连续多轮, 条件不满足即退出,不会死等。 **与 CONDITIONAL 的选用建议**:段内一次性判断/小重试保留 CONDITIONAL; "循环重复一段流程"优先用 LOOP,避免相对偏移随表布局错位。 --- ## 常见问题 ### Q: `efsm_manage_tick()` 会跳过哪些状态机? A: 被 `EFSM_CMD_STOP` 停掉的状态机(`stop=1`)会被跳过,不会调用其 `ops->tick`。 ### Q: `EFSM_CMD_HOLD` 如何使用? A: 需要传入 `uint32_t*` 类型的 param: ```c uint32_t hold = 1; // 1=锁定,0=释放 efsm_param_t param = EFSM_PARAM_DEFAULT(EFSM_PARAM_U32, sizeof(uint32_t), 1); efsm_event_sys(&fsm, EFSM_CMD_HOLD, ¶m); ``` ### Q: `efsm_manage_init` 会清掉 `user_data` 吗? A: 是的。`efsm_manage_init` 使用 `memset` 清零整个结构体。如果需要保留 `user_data`,请在 init 后重新设置: ```c fsm.user_data = &my_data; efsm_manage_init(&fsm); fsm.user_data = &my_data; // 重新设置 ``` ### Q: `efsm_register` 的 `ops->init` 是什么? A: `efsm_register` 会调用 `obj->ops->init(obj)`,这是**状态机管理器**的初始化回调(`efsm_manage_ops_t`),不是**状态**的初始化回调(`efsm_state_ops_t`)。 - `obj->ops->init` - 状态机管理器初始化(可选) - `obj->pstate->ops->init` - 当前状态的进入回调 ### Q: `efsm_step_process` 返回 false 是什么含义? A: 表示所有步骤执行完毕,或者步骤索引超出范围。在状态机中,通常用 `!efsm_step_process(...)` 判断步骤完成,然后切换到下一个状态。 ### Q: 如何在状态中访问用户数据? A: 通过 `obj->parent->user_data`: ```c static void state_action(efsm_state_t *obj, uint32_t cmd, efsm_param_t *param) { MyData *data = (MyData *)obj->parent->user_data; // 使用 data } ``` --- ## 资源占用 > **注意**:以下数据为初始版本估算,后续新增 LOOP/SWITCH/HSM/事件队列等功能后实际体积会有增长。 ``` .text (代码): 约 1.5-2 KB(视编译优化等级) - efsm.c: ~800-1000 字节 - efsm_step.c: ~700-900 字节 ``` --- ## 许可证 MIT License --- **日期**: 2026-06-07