当前位置: 首页 > news >正文

【中微半导体】嵌入式C语言,函数指针表驱动状态机( 代码风格抽象,在 C 里模拟了“对象“、“多态“的效果)

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述



1️⃣ 先看数据结构

typedef struct
{union _State_Context_Flag flag;    // 状态标志位(bit)unsigned short int State_Identifier; // 当前状态编号void (*FuncPtr[6])();               // 各个状态对应的处理函数数组void (*ContextPtr)();               // 状态机调度函数
}_State_Context;

所以一个 _State_Context 变量(比如 State_Context)里面装了:

  • flag → 各种状态标志(bit 位存储)
  • State_Identifier → 当前状态编号(0=初始化,1=等待,2=准备,3=运行,4=故障,5=停止)
  • FuncPtr[6] → 一个函数指针数组,存放 6 个函数(分别处理 6 种状态)
  • ContextPtr → 一个额外的“总调度函数指针”

2️⃣ 默认初始化(STATE_CONTEXT_DEFAULTS)

宏定义:

#define STATE_CONTEXT_DEFAULTS {  0,                           // flag = 0 (清零)CM_INITIAL_STATE,            // 当前状态 = 0(初始态)(void (*)(unsigned int))Initial_Deal_Func, // FuncPtr[0] = 初始态函数(void (*)(unsigned int))Waiting_Deal_Func, // FuncPtr[1] = 等待态函数(void (*)(unsigned int))Ready_Deal_Func,   // FuncPtr[2] = 准备态函数(void (*)(unsigned int))Run_Deal_Func,     // FuncPtr[3] = 运行态函数(void (*)(unsigned int))Fault_Deal_Func,   // FuncPtr[4] = 故障态函数(void (*)(unsigned int))Stop_Deal_Func,    // FuncPtr[5] = 停止态函数(void (*)(unsigned int))State_Context_Func // ContextPtr = 状态机调度函数
}

于是:

_State_Context State_Context = STATE_CONTEXT_DEFAULTS;

等价于:

State_Context.flag.all = 0;                // 标志位清零
State_Context.State_Identifier = 0;        // 当前状态 = 初始态
State_Context.FuncPtr[0] = Initial_Deal_Func;
State_Context.FuncPtr[1] = Waiting_Deal_Func;
State_Context.FuncPtr[2] = Ready_Deal_Func;
State_Context.FuncPtr[3] = Run_Deal_Func;
State_Context.FuncPtr[4] = Fault_Deal_Func;
State_Context.FuncPtr[5] = Stop_Deal_Func;
State_Context.ContextPtr = State_Context_Func;

3️⃣ 数据演练:状态机如何运行

第一步:初始化

State_Context.flag.all = 0;       // 标志位全清
State_Context.State_Identifier = 0; // 当前状态 = CM_INITIAL_STATE

第二步:运行调度函数

假设主循环里调用:

State_Context.ContextPtr(&State_Context);

这等价于:

State_Context_Func(&State_Context);

第三步:在 State_Context_Func() 里会调度

通常 State_Context_Func() 逻辑是这样的:

void State_Context_Func(State_Context_Handle h) {unsigned short s = h->State_Identifier; // 取当前状态h->FuncPtr[s](h);  // 执行对应的状态处理函数
}

假设 State_Identifier = 0,那么:

h->FuncPtr[0](h);

会调用:

Initial_Deal_Func(h);

第四步:状态切换

Initial_Deal_Func() 里,可能会这样:

void Initial_Deal_Func(State_Context_Handle h) {printf("运行初始化流程...\n");h->flag.bit.Initial_Ok = 1;   // 设置标志位:初始化完成h->State_Identifier = 1;      // 切换到等待态
}

于是:

  • 标志位 Initial_Ok 被置位
  • 状态编号 State_Identifier 变成 1

第五步:下一轮调度

下次再执行:

State_Context.ContextPtr(&State_Context);

就会运行:

h->FuncPtr[1](h);

即:

Waiting_Deal_Func(h);

等待态逻辑执行完,可能把 State_Identifier 改成 2(进入就绪态)……
依此类推,就完成了 状态机自动跳转


✅ 总结

这种写法的关键点是:

  1. 结构体保存当前状态编号和所有状态处理函数指针
  2. State_Context_Func() 根据 State_Identifier 调用不同的函数。
  3. 各状态处理函数可以修改 State_Identifier,实现状态跳转。

所以它就像一个 “跳转表” + “调度器”,而不是一大堆 if/else


好问题 👍
你现在已经理解了代码的执行过程,下一步就是理解 这种写法为什么在嵌入式里常见,以及它的价值。


🏷 这种方式叫什么?

这种写法一般叫做:

  • 函数指针状态机 (Function Pointer State Machine)
  • 或者 表驱动状态机 (Table-Driven State Machine)

因为它的核心是:
用一个 函数指针表 (FuncPtr[]) 存储各个状态的处理函数,
再用 状态编号 (State_Identifier) 决定调用哪个函数。


✅ 意义(为什么嵌入式喜欢这样写?)

在嵌入式 C 里,状态机随处可见,比如:

  • 电机控制流程:初始化 → 预充电 → 就绪 → 运行 → 故障
  • 通信协议处理:IDLE → 接收头 → 接收数据 → 校验 → 结束
  • UI 菜单导航:主界面 → 设置 → 参数修改 → 返回

传统写法就是一堆 ifswitch

switch(state) {case INIT: Initial_Deal_Func(); break;case WAIT: Waiting_Deal_Func(); break;...
}

但是 表驱动 + 函数指针 写法,把这些逻辑抽象了:

State_Context.FuncPtr[state](...);

这就有几个优势。


⚖️ 优点

  1. 结构清晰、易扩展

    • 新增状态,只需要写一个新的处理函数 + 把函数指针放进表里,不用改一大堆 switch
    • 状态数量多时,代码不会爆炸。
  2. 高可维护性

    • 每个状态逻辑独立在一个函数里,不会写成几百行大 switch-case
    • 模块化更强,方便调试。
  3. 执行效率高

    • switch 可能会被编译器优化成跳转表,但函数指针数组直接就是跳转表 → 执行快。
    • 在实时系统里,这种确定性很重要。
  4. 更接近面向对象的思想

    • 每个状态函数就像“对象的方法”,状态切换靠 State_Identifier,逻辑清晰。
    • 在 C 里模拟了“多态”的效果。

⚖️ 缺点

  1. 不易理解(学习成本高)

    • 初学者一眼看不懂,尤其是 函数指针数组 + 调度函数,会觉得绕。
  2. 调试麻烦

    • 如果指针赋错,可能直接跳到野函数地址 → MCU 硬件跑飞。
    • switch-case 难跟踪。
  3. 灵活性过高,容易滥用

    • 写状态机时应该保持清晰,如果把所有逻辑都“丢到函数指针里”,有时反而失去整体把握。
  4. 对小项目可能过度设计

    • 如果状态就两个,比如 “开 / 关”,用 if 可能更简单。

🎯 总结一句话

这种写法是 函数指针表驱动状态机
意义在于 让状态机更模块化、易扩展、执行高效
但代价是 代码风格抽象,调试复杂,学习门槛高


http://www.dtcms.com/a/344656.html

相关文章:

  • 【日常学习】2025-8-22 类属性和实例属性+小白学调试
  • 数据结构 -- 树
  • Vue3+Ant-design-vue+SSE实现实时进度条
  • 前端快讯看这里
  • 基于导频的OFDM系统的信道估计(使用LS估计算法)
  • 突击复习清单(高频核心考点)
  • 【C++高阶六】哈希与哈希表
  • 线程池拒绝策略踩坑
  • uniappx与uniapp的区别
  • 【UniApp打包鸿蒙APP全流程】如何配置并添加UniApp API所需的鸿蒙系统权限
  • MySQL B+树索引使用
  • QT之QSS的使用方法和常用控件的样式设置
  • Qt 的事件类QEvent及其他子类事件的开发详解:从基础到实践的全方位指南
  • 高并发用户数峰值对系统架构设计有哪些影响?
  • Qt-窗口类部件
  • 极验demo(float)(一)
  • 数据结构:队列 二叉树
  • vivo“空间计算-机器人”生态落下关键一子
  • 码蹄杯进阶
  • 笔试——Day46
  • 基于SpringBoot+Vue框架的高校论坛系统 博客论坛系统 论坛小程序
  • 企业版Idea 无快捷键的启动方式
  • 和AI Agent一起读论文——A SURVEY OF S ELF EVOLVING A GENTS(五)
  • 如何监控和管理微服务之间的调用关系
  • 微信开发者工具:更改 AppID 失败
  • Unreal Engine Class System
  • 滑动窗口+子串+普通数组算法
  • Spring AI调用本地大模型实战
  • 【LINUX】CentOS7在VMware15中,从命令行界面切换到图形界面的异常汇总
  • Day10 Go语言深入学习(2)