状态机引擎内部:回调注册表、子流程模块与事件系统如何协同

Babel36acl 嵌入式实战 无~ 49 次阅读 预计阅读时间: 11 分钟 发布于 1 天前 最后更新于 1 小时前 2327 字


架构与重构 状态机

状态机引擎内部:回调注册表、子流程模块与事件系统如何协同

前面写过"回调注册表替代 switch-case"的模式介绍。但回调注册表只是一个框架——真正复杂的是多个子流程模块(sm_*)、事件系统(psh_fire_event)、命令网关(packer_command_gateway)如何整体协同。

本文从状态机引擎的内部视角,展示这些模块是如何装配起来的。

整体架构

┌──────────────────────────────────────┐
│         app_packer_sm.c              │  ← APP 层门面
│  app_packer_sm_init()                │     初始化所有 sm_* 模块
│  app_packer_sm_task_once()           │     包装 psh_task_once()
└──────────────────┬───────────────────┘
                   │ 调用
┌──────────────────▼───────────────────┐
│        packer_state_handler.c        │  ← Service 层核心
│                                      │
│  psh_task_once() {                   │
│    1. 同步运行时标志                  │
│    2. 消费边界命令 (CONTROL/RESET)   │
│    3. 检查直接进 ERROR 的条件         │
│    4. 检查停机强制回 IDLE             │
│    5. 调用当前状态的 on_run()         │
│  }                                   │
│                                      │
│  psh_register(&handler)              │
│  psh_transition(state, delay_ms)     │
│  psh_fire_event(event)               │
└──┬───────────────┬───────────────┬───┘
   │ 注册回调       │ 触发事件       │ 查询访问
   ▼               ▼               ▼
┌──────────┐ ┌──────────┐ ┌──────────────┐
│ sm_*.c   │ │ 其他模块  │ │ packer_*     │
│ 子流程    │ │ 事件处理  │ │ 执行器/配置  │
└──────────┘ └──────────┘ └──────────────┘

sm_* 子流程模块的注册模式

每个子流程模块(如 sm_bag_flow.c、sm_seal_flow.c、sm_self_check.c)都遵循相同的结构:

定义静态回调函数

// sm_bag_flow.c
static uint8_t s_motion_seen = 0;

static void bag_out_on_enter(packer_state_t prev_state) {
    s_motion_seen = 0;
    packer_actuator_start_bag_out();
}

static void bag_out_run(void) {
    if (!s_motion_seen && packer_actuator_is_axis_running(1)) {
        s_motion_seen = 1;
    }
    if (s_motion_seen && packer_actuator_is_axis_done(1)) {
        psh_transition(PACKER_STATE_PREHEAT, delay_ms);
    }
    if (check_timeout()) {
        psh_enter_error(APP_ALARM_BAG_TIMEOUT);
    }
}

static void bag_out_on_exit(packer_state_t next_state) {
    // 清理(如果需要)
}

注册到处理器

static const packer_state_handler_t s_handlers[] = {
    {PACKER_STATE_BAG_CALIB_CHECK,  NULL,                    bag_calib_check_run,  NULL, "BAG_CALIB_CHECK"},
    {PACKER_STATE_BAG_CALIB_CLEAR,  bag_calib_clear_enter,   bag_calib_clear_run,  NULL, "BAG_CALIB_CLEAR"},
    {PACKER_STATE_BAG_CALIB_SEEK,   bag_calib_seek_enter,    bag_calib_seek_run,   NULL, "BAG_CALIB_SEEK"},
    {PACKER_STATE_BAG_OUT,          bag_out_on_enter,        bag_out_run,          bag_out_on_exit, "BAG_OUT"},
    // ...
};

void sm_bag_flow_init(void) {
    psh_register_batch(s_handlers, sizeof(s_handlers) / sizeof(s_handlers[0]));
}

在 app_packer_sm_init() 中统一初始化

void app_packer_sm_init(void) {
    psh_init();

    sm_self_check_init();   // 注册自检状态的回调
    sm_bag_flow_init();     // 注册出袋流程的回调
    sm_seal_flow_init();    // 注册封口流程的回调

    psh_log_registry();     // 打印所有已注册的处理器

    // 注册跨模块回调
    sm_printer_service_init();
    packer_printer_comm_init();
    psh_register_cancel_printer_cb(sm_printer_service_cancel);
    psh_register_arm_printer_bag_cb(sm_printer_service_arm_bag);
    psh_register_state_change_cb(app_packer_sm_on_state_change);
}

psh_task_once 的完整执行流

这是状态机引擎的心脏。每个 10ms 周期执行:

void psh_task_once(void) {
    // 1. 同步运行时标志(从 EventGroup 读取全局控制位)
    psh_sync_runtime_flags();

    // 2. 消费边界命令(CONTROL stop/start, RESET_FAULT)
    //    这些命令由 ProtoTask 通过命令网关投递
    if (psh_try_consume_control_command()) {
        // 消费了命令,可能触发了状态切换
        return;
    }

    // 3. 检查 E03/E05 等直接进入 ERROR 的条件
    //    这些条件由 packer_fault_latch 统一仲裁
    if (packer_fault_latch_get_code() != APP_ALARM_NONE) {
        if (s_state != PACKER_STATE_ERROR) {
            psh_enter_error(packer_fault_latch_get_code());
            return;
        }
    }

    // 4. 检查停机命令是否需强制回 IDLE
    if (psh_stop_should_force_idle()) {
        psh_transition(PACKER_STATE_RESET, reset_delay);
        return;
    }

    // 5. 调用当前状态的 on_run 回调
    //    如果是 IDLE 状态,内部还会尝试启动 pending 动作流
    const packer_state_handler_t *h = psh_find_handler(s_state);
    if (h != NULL && h->on_run != NULL) {
        h->on_run();
    }
}

事件系统:子流程间的解耦通信

有时候一个状态的 on_run 需要询问其他模块"你要不要优先处理"。这就是事件系统的用途:

// 定义事件类型
typedef enum {
    PSH_EVENT_BOOT_BAG_CALIB_COMPLETE = 0,  // 上电自检袋口校准完成
    PSH_EVENT_PRESS_OPEN_DELEGATE,           // 进入 PRESS_OPEN → 询问自检是否优先
    PSH_EVENT_MAX
} psh_event_t;

// 注册事件处理器(模块 A)
void sm_self_check_init(void) {
    psh_register_event_handler(PSH_EVENT_PRESS_OPEN_DELEGATE,
                                self_check_on_press_open);
}

// 触发事件(模块 B)
// sm_seal_flow.c 的 PRESS_OPEN on_enter 中:
if (psh_fire_event(PSH_EVENT_PRESS_OPEN_DELEGATE)) {
    // 事件已被消费(自检模块接管了 PRESS_OPEN 处理)
    return;
}
// 事件未被消费,走默认 PRESS_OPEN 逻辑

事件系统的返回值决定了调用方是否继续默认路径:1=已消费(停止默认),0=未被处理(继续默认)。这避免了模块之间的硬编码相互依赖。

状态切换的完整链路

某个 on_run() 中调用了 psh_transition(NEXT_STATE, timeout_ms):

  psh_transition() {
    1. 查找新状态的处理器
    2. 调用旧状态的 on_exit(NEXT_STATE)
    3. 更新全局 s_state = NEXT_STATE
    4. 设置新状态的超时 deadline
    5. 调用新状态的 on_enter(OLD_STATE)
    6. 通知状态变更回调
    7. 设置 s_state_changed 标志(供 MotorTask 通知链使用)
  }

此时:
  - MotorTask 会在下一周期收到通知 → 重新查动作表
  - app_packer_proto 会在下次查询时返回新的阶段码
  - packer_status_snapshot 的 running 标志由 on_run 内部维护

子阶段枚举的用途

某些状态(如 PRESS_CLOSE)包含多段固定脉冲动作。不是增加更多状态,而是用子阶段枚举:

typedef enum {
    APP_PRESS_CLOSE_STAGE_IDLE = 0,
    APP_PRESS_CLOSE_STAGE_AXIS2_RETRACT,
    APP_PRESS_CLOSE_STAGE_AXIS34_CLOSE,
    APP_PRESS_CLOSE_STAGE_DONE
} app_press_close_stage_t;

// PRESS_CLOSE.on_run():
void press_close_run(void) {
    switch (psh_get_press_close_stage()) {
    case AXIS2_RETRACT:
        if (packer_actuator_is_axis_done(2)) {
            psh_set_press_close_stage(AXIS34_CLOSE);
            packer_actuator_start_dual_axis();
        }
        break;
    case AXIS34_CLOSE:
        if (packer_actuator_is_dual_axis_done()) {
            psh_set_press_close_stage(DONE);
            psh_set_active_flow(FLOW_NONE);  // 清 flow
            psh_transition(NEXT, delay);
        }
        break;
    }
}

子阶段状态保存在 psh 的共享状态中,其他模块可以通过 psh_get_press_close_stage() 只读查询。

状态机框架(psh)只维护"当前状态"和"跨模块共享状态"。具体每个状态怎么做事的逻辑,全部在 sm_*.c 中。事件系统让模块之间不需要相互 include 头文件。

此作者没有提供个人介绍。
最后更新于 2026-05-30